# 第五章 常用类
# 5.1 包装类
# 5.1.1 包装类的分类
(1)包装类是针对八种基本数据类型相应的引用类型
(2)有了类的特点就可以调用类中的方法
# 5.1.2 包装类和基本数据的转换
这里以 Integer 和 int 演示包装类和基本数据类型的相互转换
(1)JDK5 以前的手动装箱和拆箱方式,装箱:基本类型 -> 包装类型;拆箱:包装类型 -> 基本类型
(2)JDK5 以后的自动装箱和拆箱方式:自动装箱底层调用的是 valueOf 方法,比如 Integer.valueOf()
public class PackingAndUnboxing {
public static void main(String[] args) {
//演示 int <---> Integer 的装箱和拆箱
//jdk5 之前是手动装箱和拆箱
//手动装箱 int ---> Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱 Integer ---> int
int i = integer.intValue();
//jdk5 之后就可以自动装箱和自动拆箱
int n2 = 200;
//自动装箱 int ---> Integer
Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
//自动拆箱 Integer ---> int
int n3 = integer2; //底层使用的是 intValue() 方法
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5.1.3 包装类测试题
(1)下面的代码是否正确,为什么?
Double d = 100d; //正确,因为会自动装箱,调用的方法是:Double.valueOf(100d);
Float f = 1.5f; //正确,因为会自动装箱,调用的方法是:Float.valueOf(1.5f);
2
(2)如下两个题目输出结果相同吗?各是什么?
Object obj1 = true ? new Integer(1) : new Double(2.0); //三元运算符是一个整体,因为后面有个 Double,会整体提升基本类型,所以是 1.0
System.out.println(obj1); //1.0
2
Object obj2;
if (true) {
obj2 = new Integer(1);
} else {
obj2 = new Double(2.0);
}
System.out.println(obj2); //1
2
3
4
5
6
7
# 5.1.4 包装类型和 String 类型的相互转换
以 Integer 和 String 转换为例,其它类似
public class Method {
public static void main(String[] args) {
//包装类型的 Integer 转换为 String 类型
Integer i = 100; //自动装箱
//方式一
String str1 = i + "";
//方式二
String str2 = i.toString();
//方式三
String str3 = String.valueOf(i);
//String 类型转换为包装类型的 Integer
String str4 = "1234";
//方式一
Integer i1 = Integer.parseInt(str4); //使用到自动装箱
//方式二
Integer i2 = new Integer(str4); //利用构造器
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 5.1.5 包装类的常用方法
Integer 类的常用方法:
Integer.MAX_VALUE方法返回最大值
Integer.MIN_VALUE方法返回最小值
Character 类的常用方法:
Character.isDigit方法判断是不是数字
Character.isLetter方法判断是不是字母
Character.isUpperCase方法判断是不是大写
Character.isLowerCase方法判断是不是小写
Character.isWhitespace方法判断是不是空格
Character.toUpperCase方法字符转成大写
Character.toLowerCase方法字符转成小写
# 5.1.5 Integer 的创建机制
比如有 Integer m = 1;
底层逻辑是:Integer m = Integer.valueOf(1);
查看 valueOf 的源码可知,当传递的形参的范围在 -128~127 时就直接返回这个参数并赋值给 m,当传递的参数范围不在 -128~127 时就会新建一个 Integer 对象并返回,即返回 new Integer(参数);
public class Test {
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //false,判断出对象不相同
Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true,数值相同对象相同
Integer x = 128;
Integer y = 128;
System.out.println(x == y); //false,对象不相同
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 5.1.6 Integer 类的测试题
@Test
public void test1() {
//测试题1
Integer i1 = new Integer(127);
Integer i2 = new Integer(127);
System.out.println(i1 == i2); //false
//测试题2
Integer i3 = new Integer(128);
Integer i4 = new Integer(128);
System.out.println(i3 == i4); //false
//测试题3
Integer i5 = 127; //自动装箱
Integer i6 = 127;
System.out.println(i5 == i6); //true
//测试题4
Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8); //false
//测试题5
Integer i9 = 127;
Integer i10 = new Integer(127);
System.out.println(i9 == i10); //false
//测试题6
Integer i11 = 127;
int i12 = 127;
System.out.println(i11 == i12); //true,只要有基本数据类型,判断的就是值是否相同
//测试题7
Integer i13 = 128;
int i14 = 128;
System.out.println(i13 == i14); //true,只要有基本数据类型,判断的就是值是否相同
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 5.2 String 类
先看一个问题:Java 中的字符串常量是对象吗?看 DeepSeek 的回答
# 5.2.1 String 类的理解
- String 对象用于保存字符串,也就是一组字符序列
- 字符串常量对象是用双引号括起来的字符序列
- 字符串的字符使用 Unicode 字符编码,一个字符【不区分字母还是汉字】占两个字节
- String 类较常用的构造方法:
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[] a, int startIndex, int count);
String s5 = new String(byte[] b);
2
3
4
5
- String 类实现了接口 Serializable 和接口 Comparable
- String 是 final 类,不能被其它的类继承
- String 类有属性
private final char[] value;
用于存放字符串内容,注意这里的 value 是一个 final 类型,不可以被修改,即 value 不能指向新的地址,但是内容是可以变化的
String name = "jack";
name = "tom"; //可以改变内容
final char[] value = {'a', 'b', 'c'};
char[] v2 = {'t', 'o', 'm'};
value[0] = 'H'; //可以改变内容
value = v2; //不可以修改地址
2
3
4
5
6
# 5.2.2 创建 String 对象的两种方式
# 5.2.2.1 方式一
直接赋值 String s = "zhishu";
# 5.2.2.2 方式二
调用构造器 String s2 = new String("zhishu");
# 5.2.2.3 区别
方式一:先从常量池看是否有 "zhishu" 的数据空间,如果有,直接指向;如果没有则重新创建,然后指向,s 最终指向的是常量池的空间地址
方式二:先在堆中创建空间,里面维护了 value 属性,指向常量池的 "zhishu" 空间,如果常量池没有 "zhishu" 则重新创建;如果有,直接通过 value 指向,最终指向的是堆中的空间地址
# 5.2.3 字符串的特性
- String 是一个 final 类,代表不可变的字符序列
- 字符串是不可变的,一个字符串对象一旦被分配,其内容是不可变的,比如以下语句创建了几个对象?
String s1 = "hello";
s1 = "haha";
//答案是创建了两个字符串对象
//当执行 s1 = "haha" 时,不是把 hello 改为 haha,而是又创建了一个 haha 字符串对象,然后 s1 指向 haha 这个字符串对象
2
3
4
5
- 那么
String a = "hello" + "abc";
创建了几个字符串对象?答案是只有一个对象,因为编译器会对String a = "hello" + "abc";
进行优化,优化成String a = "helloabc";
- 那么以下代码创建了几个字符串对象?
String a = "hello"; //创建 "hello" 字符串对象
String b = "abc"; //创建 "abc" 字符串对象
String c = a + b; //创建了几个对象?
//答案是一共有三个对象
//前面的 String a = "hello" + "abc"; 是常量相加,看的是常量池,但是 String c = a + b; 是变量相加,要看堆中
//这里的 String c = a + b; 的底层逻辑是:
//1. 先创建一个 StringBuilder sb = new StringBuilder(); 这样在堆中就会有一个 StringBuilder 对象,并且 StringBuilder 对象维护了一个 value 属性
//2. 执行 sb.append("hello");
//3. 执行 sb.append("abc");
//4. 执行完 append 后 sb 对象就在常量池创建好了 helloabc 字符串对象,然后 sb 的 value 属性指向该常量,然后执行 String c = sb.toString(); 方法让 c 指向 sb 的 value 属性
2
3
4
5
6
7
8
9
10
11
12
- 提问:下列程序运行的结果是什么,并画出内存布局图
public class Test1 {
String str = new String("zhishu");
final char[] ch = {'j', 'a', 'v', 'a'};
public void change(String str2, char ch2[]) {
str2 = "Python";
ch2[0] = 'h';
}
public static void main(String[] args) {
Test1 test1 = new Test1();
test1.change(test1.str, test1.ch);
System.out.println(test1.str);
System.out.println(test1.ch);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
画内存布局图时注意:引用类型【对象、数组】是在堆中的,只要是调用一个方法,那么这个方法就会在栈中产生一个新栈
内存布局图:
先画出执行完 Test1 test1 = new Test1();
但还没执行 test1.change(test1.str, test1.ch);
的内存布局图
再画出执行 change 方法时把实参地址赋给形参的布局图
再画出执行完 change 方法里的 str2 = "Python";ch2[0] = 'h';
后的布局图
# 5.2.4 String 类的常用方法
因为 String 类是保存字符串常量的,每次更新都需要创建这个新的字符串常量对象,即需要重新开辟空间,效率较低,因此 Java 设计者提供了 StringBuilder 和 StringBuffer 来增强 String 的功能,并提高效率
截取自:String类的20种常见方法_string方法-CSDN博客 (opens new window)
# 5.3 StringBuffer 类
# 5.3.1 StringBuffer 类的基本介绍
- StringBuffer 代表可变的字符序列,可以对字符串内容进行增删,很多方法与 String 相同,但是 StringBuffer 是可变长度的,StringBuffer 是一个容器
- StringBuffer 的直接父类是 AbstractStringBuilder,StringBuffer 也实现了 Serializable
- 在父类中 AbstractStringBuilder 中有属性
char[] value
这个和 String 类的 value 属性不一样,这个没有 final 修饰,所以该 value 数组存放的字符串的内容在堆中而不是在常量池中
# 5.3.2 String VS StringBuffer
- String 保存的是字符串常量,里面的值不能更改,每次 String 类的更新实际上就是更改地址,效率较低
- StringBuffer 保存的是字符串变量,里面的值是可以更改,每次 StringBuffer 的更新实际上可以更新内容,不用每次更新地址,效率较高
# 5.3.3 StringBuffer 的构造器
StringBuffer() //构造一个其中不带字符的字符串缓冲区,其初始容量为 16 个字符
StringBuffer(CharSequence seq) //构造一个字符串缓冲区,它包含与指定的 CharSequence 相同的字符
StringBuffer(int capacity) //构造一个不带字符,但具有指定初始容量的字符串缓冲区,即对 char[] 大小进行指定
StringBuffer(String str) //构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容,该缓冲区的大小,即 char[] 的大小为 str.length() + 16
2
3
4
5
6
7
# 5.3.4 String 和 StringBuffer 的相互转换
- 从 String 转换到 StringBuffer
String str = "hello tom";
//方式一:使用构造器
//注意:返回的 stringBuffer 才是 StringBuffer 的对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式二:使用的是 append 方法
StringBuffer stringBuffer2 = new StringBuffer();
stringBuffer2 = stringBuffer2.append(str);
2
3
4
5
6
7
8
- 从 StringBuffer 转换到 String
StringBuffer stringBuffer = new StringBuffer("止束");
//方式一:使用 StringBuffer 提供的 toString 方法
String s = stringBuffer.toString();
//方式二:使用构造器来解决
String s2 = new String(stringBuffer);
2
3
4
5
6
# 5.3.5 StringBuffer 的常用方法
截取自:StringBuffer类中常用方法 (opens new window)
# 5.4 StringBuilder 类
# 5.4.1 基本介绍
- StringBuilder 是一个可变的字符序列,此类提供了一个与 StringBuffer 兼容的 API,但不保证同步【StringBuilder 不是线程安全的】,该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候,如果可能,建议优先采用该类,因为在大多数实现中它比 StringBuffer 要快
- 在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法以接受任意类型的数据
- StringBuilder 的方法没有做互斥的处理,即没有 synchronized 关键字,因此在单线程的情况下使用 StringBuilder
# 5.4.2 常用方法
StringBuilder 和 StringBuffer 均代表可变的字符序列,方法是一样的,所以使用和 StringBuffer 一样
# 5.4.3 String、StringBuffer、StringBuilder 的比较
String:不可变字符序列,效率低,但是复用率高
StringBuffer:可变字符序列,效率较高,线程安全
StringBuilder:可变字符序列,效率最高,线程不安全
# 5.5 Math 类
# 5.5.1 基本介绍
Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数
# 5.5.2 常用方法
截取自:Java中的常见类Math (opens new window)
# 5.6 Arrays 类
# 5.6.1 Arrays 类基本介绍
Arrays 类里面包含了一系列静态方法,用于管理或操作数组【比如排序和搜索】
# 5.6.2 常用方法
- toString 方法:返回数组的字符串形式
- sort 方法:对数组内元素进行排序
public class Main {
public static void main(String[] args) {
Integer[] arr = {-1, 3, 1, 4, 8};
//因为数组是引用类型,所以传参传的是地址,所以会影响到原来的 arr
//Arrays.sort(arr);
//实现定制排序
//sort 方法是重载的,可以通过传入一个接口 Comparator 来实现定制排序
//调用定制排序时需要传入两个参数:(1) 待排序的数组 arr (2) 实现了 Comparator 接口的匿名内部类,要求实现 compare 方法
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(arr));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
binarySearch 方法:通过二分搜索法进行查找,要求必须排好序
copyOf(T[] original, int newLength)
方法:返回一个新的、长度为 newLength 的数组,其中 original 是要被复制的数组,这个数组可以是任何 Java 数组类型,newLength 是新数组的长度,必须是一个正整数,如果新数组的长度比原数组短,则只复制原始数组中的前 newLength 个元素;如果新数组的长度比原数组长,则新数组剩下的元素填为 nullfill 方法:数组元素的填充,用你指定的值填充到数组的所有值
equals 方法:比较两个数组元素内容是否完全一致
asList 方法:将一组值转换成 List
# 5.7 System 类
- exit 方法:退出当前程序
- currentTimeMillens 方法:返回当前时间距离 1970年1月1日 00:00:00 的毫秒数
- gc 方法:运行垃圾回收机制
# 5.8 BigInteger 类和 BigDecimal 类
- add 方法:相加
- subtract 方法:相减
- multiply 方法:相乘
- divide 方法:相除
应用场景:BigInteger 适合保存比较大的整型,BigDecimal 适合保存精度更高的浮点型