1. 基本类型的包装器类型:
1) Java并不是严格的面向对象语言就是因为存在8中基本类型数据(int、char、double、boolean等),它们并不是类对象;
2) 提供这8中基本类型主要是为了照顾C程序员的传统习惯;
3) 为了让这8中基本类型也能面向对象,Java为其提供了包装器类型,名称都是各个基本类型的首字母大写(Long、Double、Float、Byte、
Short等),而int和char比较特殊,其包装器类型是Integer和Character,需要单独记忆;
4) 现在,这些包装器类型都是面向对象的类了,具有对象方法可以进行很多高级的操作,而不是简单的基本类型,都是引用类型了;
2. 自动装箱/拆箱:
1) JDK 1.5之前,基本类型数据和包装器类型数据之间相互转换、赋值非常麻烦,需要很多方法来实现,但现在Java提供了自动装箱和拆箱功能,使这个过程非常方便;
2) 自动装箱:可以直接将基本类型数据赋给其对应的包装器类型数据,底层自动隐式地包装成包装器类型数据,例如,Integer a = 5;
i. 注意,自动装箱一定要类型严格匹配!例如:Integer a = 5.5;错误,5.5是double,和Integer冲突!编译报错!
ii. 即使是符合基本类型范围从小到大的规则(byte < char < int < long < float < double)也不能自动装箱,例如:Double d = 5;也不行!!5是int,和Double类型冲突!!
iii. 即自动装箱的类型必须是严格的类型对应,即一个基本类型数值只能赋给其对应的包装器类型才行;
iv. 本质就是,赋值号左边是面向对象的类型,而包装器类型没有基本类型的从小到大范围之说!!
3) 自动拆箱:和自动装箱过程相反,可以直接把一个包装器类型赋给一个基本类型变量,例如,int a = new Integer(5);
i. 由于自动拆箱过程中,赋值号左边的是基本类型,而基本类型是有从小到大范围之说的,因此自动拆箱可以类型不严格匹配,但只要符合基本类型的从小到大范围即可;
ii. 例如:double d = new Integer(5); // double范围比int大,所以可以这样自动拆箱
iii. 自动拆箱的原理就是先取出包装器对象中的基本类型值,然后再赋给相应的基本类型,因此上面的式子中,等号左边其实是先拆箱获取其基本类型数值(即int val = 5),然后再将这个val赋给double d;
4) 只要是赋值的地方都可以发生自动装箱/拆箱:如方法的参数传递也是一种赋值的过程(值传递),因此也支持自动装箱/拆箱,例如
void func(int a); // obj.func(new Integer(5); 自动拆箱
void func(Integer a); // obj.func(5); 自动装箱
3. 包装器的构造器、字符串和数值之间的相互转换:
1) 包装器的构造器有两个版本:
i. Type(type val); // 以基本类型作为参数来构造相应的包装器类型
ii. Type(String val); // 将一个字符串解析成相应的包装器类型
!例如:Integer a = new Integer(5); Integer a = new Integer("5");
!!其实,以基本类型作为参数来构造隐含了另一种重载版本,由于自动拆箱的存在,你可以用包装器类型对象来构造另一个包装器对象,因为从Type -> type是自动拆箱的过程,例如:
Integer a = new Integer(new Integer(5)); // 并不是Java重载了一个接受包装器类型的构造器版本,而是自动拆箱的原因,会先将Integer(5)拆箱成5,然后调用Integer(int val)构造器来构造!
2) 包装器的字符串参数版本其实就是一个将字符串转化成数值类型的方法,而各个包装器类型也提供了一个静态方法用于将字符串解析成基本数据类型:
public static type Type.parseType(String val); // type指代基本类型
!例如:public static int Integer.parseInt(String val); // 使用,int a = Integer.parseInt("5");
!!注意,使用构造器解析得到的是包装器类型,使用parse静态工具方法解析得到的是基本类型;
!!!要根据需要的返回值的类型(是包装器类型还是基本类型)来决定使用哪种解析方式;
3) 同理String也提供了一系列静态方法将基本类型数据解析成字符串:valueOf方法
i. 常用的单数据解析:public static String.valueOf(type val); // type指代基本类类型
!例如:public static String.valueOf(int val); // 使用,String s = String.valueOf(5);
ii. 将char数组连接成字符串:public static String.valueOf(char[] data[, int offset, int count]);
4) 其实最简单的数值转换成字符串的方法:
i. 所有的包装器类型都重写了toString方法,因此toString可以直接得到解析后的字符串;
ii. 基本类型支持自动装箱;
iii. 以上两点就决定了数值类型转换成字符串类型可以这样转换:和一个字符串相加即可!
!!例如:
Integer a = new Integer(5); a + ""; // 调用toString后相加
int a = 5; -> a + ""; // 以下两个都是先将a自动装箱成包装器类型,然后调用toString后相加
5 + "";
4. 数值型和字符串型相互转化的方式总结:
1) 数值 -> 字符串:
i. 基本类型 -> 字符串:String.valueOf
ii. 包装器类型 -> 字符串:还是String.valueOf,因为可以自动拆箱
iii. 通用方法:将数值和""相加即可(两种类型都适用)
2) 字符串 -> 数值:
i. 字符串 -> 基本类型:Type.parseType
ii. 字符串 -> 包装器类型:包装器类型的构造器
5. 有了包装器类型就可以使用instanceof运算符了:
1) 示例:
Object o = 5;
if (o instanceof Integer) {
int a = (Integer)o;
}
2) 小结:把基本类型赋给其对应的包装器类型的父类也是可以自动装箱的!!
3) 但是上面如果改成int a = o;就会编译报错!
!小结:把运行时类型为包装器类型但编译时类型不会其对应的基本类型的的变量赋给对应的基本类型是不会自动拆箱的!!会直接编译报错!!
6. 数值型的大小比较:用符号比较、用包装器类型的静态工具方法比较
1) 用符号比较:即>、<、>=、<=比较运算符对数值型(基本类型、包装器类型)比较;
i. 符号两边可以是任意数值型的基本类型或包装器类型,两种类型可以混用;
ii. 例如:5.5 > new Integer(5)等;
iii. 比较运算符的规则:如果两边有包装器类型,那就先自动拆箱成基本类型,然后再进行比较;
iv. 我们都知道,比较运算符是可以混合比较各种类型的数值的(比如double和int比,byte和char比等等);
2) 由于符号比较比较混乱,允许混合类型比较,但有时需要在类型严格匹配的情况下进行比较使代码更加严谨,而各个包装器类型都提供了一个compare静态工具方法,可以保证在类型一致的情况下进行比较:
i. 原型:public static int Type.compare(type x, type y);
ii. x iii. Type是包装器类型,type是Type对应的基本类型; iv. 由于自动拆箱,因此也可以传包装器类型; 7. 相等比较: 1) 直接用包装器对象的equals方法比较完全没有任何问题,通常更推荐用这种方法比较,不会出现任何错误; 2) 使用==比较: i. 如果==一边是基本类型一边是包装器类型,那么包装器类型会自动拆箱成其对应的基本类型进行比较; !!在这种情况下就变成了纯的基本类型之间的比较,而基本类型的==比较是允许混合类型的,因此允许这样写:6.0 == new Integer(6); !!实质比较的是基本类型数值; ii. 如果==两边都是包装器类型,那就是纯引用类型之间的==比较了,那==比较的是两者的地址(是否指向同一个对象); a. 和instanceof一样,两边的编译类型至少要具有父子关系才行,不能离谱到任意类型,比如new String() == new Date(),这两者用脚趾头想都是不可能相等的,因此直接编译报错! b. 由于是比较两者的地址,因此像这样的比较返回的是false:new Integer(5) == new Integer(5),因为是两个不同的对象,地址不同; c. 但是由于自动装箱的存在可能会出现一些令人费解的意外: !!这个道理告诉我们,包装器对象之间的相等比较一定要用equals对象方法!!!
!!这其实是由自动装箱的算法决定的:由于-128-127(char范围)的整数太常用了(根据经验),因此Integer在静态初始化代码中创建了一块缓存,将-128-127包装成Integer存在这个缓存中,如果是通过自动装箱并且范围是-128-127得到的Integer就直接指向该缓存中的对象,因此上面两个符合这个标准,所以指向的是缓存区的同一个对象,而后两个由于128超出了这个范围,所以相当于使用new重新创建了两个对象,因此地址不同,比较结果为false;
Integer a = 5;
Integer b = 5;
out.println(a == b); // true
Integer x = 128;
Integer y = 128;
out.println(x == y); // false