1 包装类
1.1 概念
- 包装类都在java.lang中,无需额外导入
- 包装类就是把基本数据类型包装成引用类型
- 相较于基本数据类型,包装类型是一个类/属性/方法,通过创建对象来使用,可以实现接口和继承其他类
- 基本数据类型存储的是值本身,直接保存在栈内存中,而包装类是指向存储该类型值的对象的引用,因此保存在堆内存中
1.2 数据类型
表格第一行为数据基本类型,第二行为其对应的包装类型
byte | short | int | long | float | double | char | boolean | void |
---|---|---|---|---|---|---|---|---|
Byte | Short | Integer | Long | Float | Double | Character | Boolean | Void |
- 注意包装类型的默认值为null(因为是创建类的对象),而基本数据类型通常为其对应数据格式的0值
- void类是最终类,没有子类,也没有返回值。构造方法为私有,所以不能创建对象(同时防止被外界错误的创建实例)
Byte Short Integer Long Float Double 等数据类型,都有一个共同的 Number 父类
同时通过查看Java源码得知,每个数据基本类型对应的包装类的缓存范围取决于其valueOf()方法中定义的cache数组长度
- 对于Byte来说,在-128到127之间返回同一个对象。不会超出范围
- 对于Short、Integer、Long来说,在-128到127之间,返回的是同一个对象,超出这个范围,返回新的对象
对于Float、Double来说,每次都是创建新的对象
对于Character来说,0-127返回相同对象,否则返回新的对象
对于Boolean来说,要么返回TRUE、要么返回FALSE
但值得注意的是,与我们平时使用的true和false不同,在源码中,Boolean类返回的TRUE和FALSE是对象,对象指向的值为ture和false(只有在Java源码中这样,我们在实际使用中可不能把他当成对象)
1.3 自动装箱
自动装箱是jdk5引入的特性,底层调用的是valueOf(类型)
Integer i = 10;
在编译时实际上调用了 Integer.valueOf(int) 方法来创建一个 Integer 对象,等价于执行:
Integer i = Integer.valueOf(10);
-
优点:
1.避免了类型进行显示转换,提高代码的简约和美观性
2.Java API的设计趋势统一为对象,这有助于简化API的设计,也有利于开发者的体验统一
3.集合框架(如Map,List,Set,Collection等)只能存储对象,例如可以在List -
缺点:
1.自动装箱带来了极大便利,但同时带来了额外的性能开销,尤其是在处理大量数据的情况下
2.自动装箱的结果是对象,因此可能会产生null值,在编写程序的过程中需要注意处理空值的情况,否则程序可能会抛出NullPointerException异常
3.只有特定范围内(通常为-128到127)的整数在自动装箱时才会被缓存,超出这个范围的值每次都会产生一个新的对象
public langDemo01() {
Integer integer1 = 80;
Integer integer2 = 80;
Integer integer3 = 180;
Integer integer4 = 180;
Integer integer5 = integer4;
System.out.println(integer1 == integer2);
System.out.println(integer3 == integer4);
System.out.println(integer4 == integer5);
}
运行截图:
在Java中,== 运算符比较的是地址而非值,integer1和integer2的值都在-128到127的缓存范围内,因此他们引用的都是一个对象实例,所以integer1 == integer2的结果为true
而integer3和integer4对应的值都在缓存的范围之外,即使值相同,也重新创建了两个对象,所以地址是不同的,结果为false
integer5指向与integer4相同的对象的引用,地址是相同的,结果为true
1.4 自动拆箱
自动拆箱同样是JDK5引入的新特性,他是编译器把包装类对应的对象转换为基本数据类型值的过程,在Java中,每种基本数据类型都有对应的包装类
下面是一些拆箱示例:
1.赋值给基本数据类型
Integer integers = new Integer(10);//已过时的方法
//等价于Int int1 = Integer.valueOf(10);
int int1 = integers;//自动拆箱
//等价于 int int1 = integers.intValue();//手动拆箱
2.通过方法传参
public static void main(String[] args){
printInt(10);
}
public static void printInt(int i) {
System.out.println(i);
}
3.数组初始化
Integer[] integerArray = {1, 2, 3}; // 自动拆箱
for (Integer i : integerArray) {
System.out.println(i);
}
这里,我们在初始化integerArray时,编译器会自动将每个整数字面量转换成Integer对象。然后当我们遍历数组时,可以使用这些对象。
4.方法返回值
public static Integer getInteger() {
return 300;
}
public static void main(String[] args) {
int result = getInteger();
System.out.println(result);
}
当涉及到null值时(因为Integer可以是null),自动拆箱可能会导致NullPointerException,所以在处理可能为null的包装类对象时需要注意处理null值情况
将字符串转换为指定数据类型的用法:
int anInt = Integer.parseInt("134");
double v = Double.parseDouble("3.14");
long l = Long.parseLong("76");
boolean aTrue = Boolean.parseBoolean("TRue"); //通过下图源码得知这里的true不区分大小写
- 使用parseBoolean方法转换时,不必区分大小写,因为其Java源码使用了equalsIgnoreCase方法进行比对
System.out.println(aTrue);
System.out.println(l);
字符串如何转换成char类型?
首先声明一个字符串:
String str = "absdf123";
我们可以先使用String类的toCharArray方法将其转换为字符数组,然后再通过for循环输出字符数组的内容
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.print(chars[i]);
}
也可以直接是用String类的charAt方法根据索引获取字符
for (int i = 0; i < str.length(); i++) {
System.out.print(str.charAt(i));
}
- Java 中的一些基本类型的包装类(如 Integer, Double, Character, Boolean 等),已经重写了 hashCode() 方法来提供合适的哈希码值,让我们看看源码中是如何定义的:
在Interger类中,hashCode()方法直接返回其封装的int值
Double 类的 hashCode()方法将Double值的bit无符号右移32位
由于double 类型在内存中是以64位(8字节)的二进制形式存储,所以首先调用doubleToRawLongBits方法将 double 类型的浮点数直接从内存中的二进制位模式读取出来。将这个二进制位模式原封不动地转换成一个 long 类型的整数,之后将他返回而不考虑任何如NaN等特殊情况
doubleToRawLongBits是Java的一个native方法,因此它的实现依赖于JVM和底层的操作系统。我们无法直接在Java源码中查看该方法的具体实现细节。
与doubleToRawLongBits相似,也是将double类型的浮点数转换为其IEEE754位模式表示,如果value是NaN(not a number),它会对NaN做标准化处理,返回0x7ff8000000000000L。
Character 类的hashCode()方法返回字符的Unicode值
Boolean类的hashCode()方法对于true返回1231,对于false返回1237
在Java中哈希码是32位的,而long类型的数据是64位,Long类的hashCode()方法中通过无符号右移32位,截取32位的高位值作为哈希码返回
对于Short和Byte类,需要将原数据提升到32位(4字节)的int类型并返回
Float类型和Double类型的机制相同,这里不做赘述