java 从第一个版本开始,就为每种基本类型提供了封装的类,以便可以将其当作类而非基本数据类型使用。
比如 List、Map 这些类,都是操作 Object,无法操作基本数据类型。你无法用 int 作为 Map 的 key 或 value,所以 java 允许让 int 封装成其封装类 Integer 后来作为 Map 的 key 或 value。
java 提供了自动为每种基本数据类型和其对应的封装类之间的自动转换功能。
- 自动封箱(auto boxing):从基本类型到封装类;
- 自动拆箱(auto unboxing):从封装类到基本类型。
java 是通过创建实例(如 Integer.valueOf(int类型数据)
来对 int 进行封箱,或直接 new Integer(int类型数据)
)或返回缓存住的实例来实现自动封箱的,通过调用相应的转换方法实现自动拆箱的(如 int 是通过 Integer 调用 .intValue()
来拆箱获得)。
-
所谓的“返回缓存实例”:
java 创建一个实例是需要一系列复杂操作的:调用构造方法、分配一块内存等。
而拆箱和封箱又是频繁发生的事情,所以 java 会缓存住一些实例,比如说 Integer,java 会缓存住256个Integer实例(-128~127,通过 Integer 的内部类 IntegerCache 类来实现,在类加载时创建了 256 个Integer实例),进行自动封箱时,java 会先去找找有没有这个缓存的实例,没有的话再去创建。
// java 会对 -128~127 进行缓存,即类加载时,缓存 256 个 Integer 对象
Integer i1 = 1;
Integer j1 = 1;
System.out.println(i1==j1); // true
// == 判断的是是否为同一个对象,因此 i1 和 j1 是同一个对象
// 可见直接将数字 1 封箱,其实并没有创建新的对象,而是直接用了缓存的 Integer 对象
Integer j2 = new Integer(1);
System.out.println(i1==j2); // false
// 因此,用 new Integer(1) 的方式是真的创建了一个新的 Integer 对象,因此和缓存中的 Integer 对象不是同一个对象
// 检验缓存上界
Integer i127 = 127;
Integer i128 = 128;
Integer j127 = 127;
Integer j128 = 128;
System.out.println(i127==j127); // true
System.out.println(i128==j128); // false
// 说明的确上限只换存到了 127
// 检验缓存下界
Integer i_128 = -128;
Integer i_129 = -129;
Integer j_128 = -128;
Integer j_129 = -129;
System.out.println(i_128==j_128); // true
System.out.println(i_129==j_129); // false
// 说明的确下限只缓存到了 -128
[!warning] 封装类之间是无法相互转换的
基本类型 int 是可以自动转换成 double 的,甚至 double 可以强制转换成 int(只是会造成精度丢失:直接截取整数部分,而非对小数四舍五入成整数)。
但 Integer 与 Double 互相无法自动转换或强制类型转换。
另外封装类型也是不可变的。如 Integer 是没有 .set()
方法的。这些和 String 是一样的,对象一旦创建出来就是不能变的(当然,你可以让同一个引用指向另一个对象,但是对象本身是不可改变的)。所以可以放心地将封装类型作为 Map 的 key。
数字基本类型的封装类和常用方法
和数字相关的基本数据类型为:byte、short、int、long、float、double;
和数字相关的基本数据类型对应的封装类依次为:Byte、Short、Integer、Long、Float、Double。
// Byte
public final class Byte extends Number implements Comparable<Byte> {...}
// Short
public final class Short extends Number implements Comparable<Short> {...}
// Integer
public final class Integer extends Number implements Comparable<Integer> {...}
// Long
public final class Long extends Number implements Comparable<Long> {...}
// Float
public final class Float extends Number implements Comparable<Float> {...}
// Double
public final class Double extends Number implements Comparable<Double>{...}
可以看到,数字基本类型的封装类,都是 Number 类的子类。
下面以 int 与 Integer 为例,介绍数字基本类型的自动拆箱、自动封箱,以及常用方法。
import java.util.HashMap;
import java.util.Map;
public class WrapperClassesForNumber {
public static void main(String[] args) {
int a = 100;
// new Integer() 可以接收 int 或 String 来创建一个 Integer 实例
Integer i1 = new Integer(a);
Integer i2 = new Integer("123");
// Integer i3 = "345"; // 报错
// Integer.valueOf() 接收一个 int,返回一个 Integer,但是这个 Integer 能直接赋值给 int 引用b,这是因为这个赋值过程 java 会自动拆箱。
int b = Integer.valueOf(a);
// Integer.parseInt() 则是接收一个 String,返回的本身就是一个 int,然后赋值给一个 int 引用c,所以这里没有自动拆箱。
int c = Integer.parseInt("321");
System.out.println("----------自动封箱和拆箱----------");
// java 提供了自动为每种基本数据类型和其封装类之间的转换功能
// 拆箱就是把封装类的值取出来,赋给其对应的基本类型。
// 封箱则是把基本类型封装成其对应的封装类的一个实例。
// 自动,则是指 java 会在有这种转换需求的时候,自动帮你完成这种转换(封箱或拆箱)
Integer ab = 200; // 自动封箱
int cd = ab; // 自动拆箱
// 自动封箱为 Integer,作为 Map 中的 key。
Map<Integer, String> int2Str = new HashMap<>();
// 你不能写成 Map<int,String> int2Str,声明类型是需要用封装类的,但用的时候,java 会帮你自动封箱,把传入的 int 封装成 Integer// .put() 按定义是要传入 Integer 与 String 的
int2Str.put(1, "一");
int2Str.put(2, "二");
int2Str.put(3, "三");
// .get() 按定义是要传入 Object 的,这里同样是将 int 自动封装成了 Integer,然后赋给 Object。
System.out.println(int2Str.get(1)); // 一
// 自动拆箱为 int,并给 key 赋值
for (int key : int2Str.keySet()) {
// .keySet() 迭代出来的元素是 Integer,你让它赋值给 int 引用(key),java 就会对其自动拆箱成 int。
System.out.println(key);
}
/*
1
2
3
*/
// Integer 封装类的常用方法
System.out.println("----------常用方法----------");
// Integer.toBinarySring() 接收 int,返回这个整数的二进制,并将这个二进制数用 String 方式返回
System.out.println(Integer.toBinaryString(1024)); // 10000000000
System.out.println(Integer.toBinaryString(-1024)); // 11111111111111111111110000000000
// Integer.toOctalString() 接收 int,返回这个整数的八进制,并将这个八进制数用 String 方式返回
System.out.println(Integer.toOctalString(1024)); // 2000
System.out.println(Integer.toOctalString(-1024)); // 37777776000
// Integer.toHexString() 接收 int,返回这个整数的十六进制,并将这个十六进制数用 String 方式返回
System.out.println(Integer.toHexString(1024)); // 400
System.out.println(Integer.toHexString(-1024)); // fffffc00
System.out.println("----------Number类----------");
// 所有和数字相关的封装类,都继承自 Number 抽象类
Number num = 9; // 这里其实就是用父类 Number 引用指向子类 Integer 实例,但实际传入的是 int,因此这里 java 也是做了自动封箱,把 int 封装成 Integer。
num = new Integer(12345);
Number numD = 9.99; // 同样,这里是父类 Number 引用指向子类 Double 实例,而传入的是 double,所以 java 也是做了自动封箱,把 double 封装成 Double。
numD = new Double(12.345);
// 使用 Nubmer 类可以方便地使用数字类型的转换
// 当然所有的类都继承了 Nubmer 的这些转换方法
System.out.println("使用Number将Double转为byte:" + numD.byteValue()); // 使用Number将Double转为byte:12
System.out.println("使用Number将Double转为short:" + numD.shortValue()); // 使用Number将Double转为short:12
System.out.println("使用Number将Double转为int:" + numD.intValue()); // 使用Number将Double转为int:12
System.out.println("使用Number将Double转为long:" + numD.longValue()); // 使用Number将Double转为long:12
System.out.println("使用Number将Double转为float:" + numD.floatValue()); // 使用Number将Double转为float:12.345
System.out.println("使用Number将Double转为double:" + numD.doubleValue()); // 使用Number将Double转为double:12.345
/*
其实所谓的自动拆箱,就是 java 自动调用了该基本数据类型的封装类的 .xxxValue() 方法,返回了对应的基本数据类型。
比如 Integer 自动拆箱成 int,就是调用了 Integer 的实例方法 .intValue(),返回了 int 类型的值。
*/
// 自动拆箱可能会引起NPE异常(NullPointerException)
int2Str.put(null, "无");
System.out.println(int2Str.get(null)); // 无
// 上面这两句并没有涉及自动拆箱或封箱,因为没有发生基本类型和引用类型的转换。
// 自动拆箱为 int,并给 key 赋值,但是有一个 key 为 null,null 是无法转成一个 int 的
// (注意:null 不是 0,0是实实在在的一个数,0 的 int 是可以和 Integer 自动转换的,但 null 其实是“空”,是引用类型的缺省值,但并不能转成 int)。
// 其实自动拆箱,后面是 java 帮我们调用了对应的方法,在这里就是 intValue() 这个方法
// 所以当引用为 null 的时候自动拆箱,相当于是调用 null 的方法,所以这时候会发生 NPE
// 对于其他封装类型的自动拆箱,也是一样的。
// for (int key : int2Str.keySet()) {
// // 这里因为 .keySet() 迭代出来的元素中除了有 Integer 还有一个 null,
// // Integer 可以自动拆箱成 int,但 null 转成 int 时,同样自动调用 .intValue() 时,因为无法对 null 调用实例方法,就会报错 NPE。
// System.out.println(key);
// }
// /*
// 运行报错:NullPointerException
// */
/*
其实这里引出了一种用法:就是,数字基本类型是没有 null 的说法的,
声明一个数字基本类型的成员变量,默认就赋予一个缺省值 0,你无法区分这个 0 是初始值还是你后面赋的值,你无法让它是空的(来表明你从来没有对它赋值)。
这时候,你就可以考虑不使用数字的基本类型,而使用其对应的封装类。其缺省值就是 null,可以用来表明你从没有对它赋值。
所以在有数据交换的时候,建议使用封装类。这样就允许你把它设置成 null,来表明你从没有对它赋过值。
*/
}
}
字符基本类型的封装类和常用方法
字符基本类型 char 对应的封装类是 Character。
Character 中有很多叫 isXXX()
的静态方法,比较实用。
而至于字符基本类型和其封装类的自动封箱和拆箱,就不赘述了,和数字类型的拆箱封箱是一样的,都是 java 在我们需要的时候会自动完成转换。
public class WrapperClassesForChar {
public static void main(String[] args) {
// char 对应的封装类是 Character,里面有很多 isXXX 方法比较实用,
// 比如判断字符是否为数字 isDigit() 可以接收 char 或 int,返回 boolean。
System.out.println(Character.isDigit('A')); // false
System.out.println(Character.isDigit('啊')); // false
System.out.println(Character.isDigit('9')); // true
System.out.println(Character.isDigit('0')); // true
// System.out.println(Character.isDigit('123')); // 报错,因为 char 是单字符的
// 其实isDigit()一般常用的是 char 作为形参,也比较直观判断一个字符是否是数字
// isDigit() 接收的是 int 的话,含义有点绕,是判断这个 int 对应的 unicode 对应的编码是否用来表示数字(因为可能是用来表示一个字母、符号而非数字)
System.out.println(Character.isDigit(0x06f8)); // true (因为'u06F0' ~ 'u06F9', 扩展阿拉伯语,印度语数字)
System.out.println(Integer.toString(0x06f8)); // 1784
System.out.println(Character.isDigit(1784)); // true
System.out.println(Character.isDigit(9)); // false
System.out.println(Character.isDigit(37)); // false
System.out.println(0x37); // 55
System.out.println(Character.isDigit(55)); // true
System.out.println(Character.isDigit(0x37)); // true (因为'u0030' ~ 'u0039', ISO-LATIN-1 数字('0' through '9'))
/*
这是因为
'u0030' ~ 'u0039', ISO-LATIN-1 数字('0' through '9')
'u0660' ~'u0669', 阿拉伯 - 印度文数字
'u06F0' ~ 'u06F9', 扩展阿拉伯语,印度语数字
'u0966' ~ 'u096F', Devanagari 数字
'uFF10' ~ 'uFF19', 全角数字
当然也不只有这些范围是数字,还有别的范围也有数字
*/
// isXXX 还有诸如判断字符大小写的 isLowerCase() 与 isUpperCase() ,
// 也都是可以接收 char 或 int,但常用的都是接收 char,
// 接收 int 的含义判断也和 isDigit 类似,比较绕,是判断对应unicode表示的字符是否是大写/小写字母,这里就不深究了
System.out.println(Character.isLowerCase('A')); // false
System.out.println(Character.isLowerCase('a')); // true
System.out.println(Character.isUpperCase('A')); // true
System.out.println(Character.isUpperCase('a')); // false
// 非字母的字符自然没有所谓的大小写,所以通通都是 false。
System.out.println(Character.isLowerCase('3')); // false
System.out.println(Character.isUpperCase('3')); // false
System.out.println(Character.isUpperCase('0')); // false
System.out.println(Character.isUpperCase('一')); // false
}
}
布尔基本类型的封装类和常用方法
布尔基本类型 boolean 的封装类是 Boolean。
由于 boolean 比较特殊,只有两个值 true 和 false,所以 Boolean 特意定义了两个静态常量:
Boolean.TRUE
和 Boolean.FALSE
(注意都是字母大写的,不仅仅是首字母)。
public class WrapperClassesForBoolean {
public static void main(String[] args) {
System.out.println("----------静态变量----------");
System.out.println(Boolean.TRUE); // true
System.out.println(Boolean.FALSE); // false
// 其实这里也是自动拆箱了,因为这两个静态变量的类型是 Boolean,而非其对应的基本类型 boolean。
System.out.println(Boolean.TRUE == true); // true
System.out.println(Boolean.FALSE == false); // true
// 上面两句同样是自动拆箱了
System.out.println("----------valueOf----------");
// 只有不区分大小写的 true 才是 true,剩下的字符串都是 false
System.out.println(Boolean.valueOf("true")); // true
System.out.println(Boolean.valueOf("TruE")); // true
System.out.println(Boolean.valueOf("tRue")); // true
System.out.println(Boolean.valueOf(" true")); // false (因为多了一个空格)
System.out.println(Boolean.valueOf("false")); // false
System.out.println(Boolean.valueOf("abcd")); // false
}
}