Java语言有8种基础数据类型,这些类型的数据都不是对象。有些情况下,必须用对象的形式来表示一个基础数据,例如Java语言要求存入集合的数据都必须是对象,不能是基础类型数据,因此,如int型、double型等基础类型的数据都无法存入集合。为了能够用对象来表示出基础类型的数据,Java语言定义了8种代表基础数据类型的类,这些类被统称为包装类,每一个包装类的对象当中都包含了一个基础数据类型的值。基础数据类型与它们的包装类是一一对应的,下面的表11-1展示了每种基础数据类型所对应的包装类。
表11-1 8种包装类
基础数据类型 | 对应包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
double | Double |
float | Float |
char | Character |
boolean | Boolean |
所有包装类都位于java.lang包下,因此在使用这些类时无需用import关键字引入。在8个包装类当中,除Character和Boolean之外都是表示数字的类。这些表示数字的类都是Number类的子类,各位读者要记住这个细节,因为它们有共同的父类,所以这些类有很多共同的特点。
11.1.1装箱与拆箱
专业上,把一个基础类型的数据转换成一个包装类对象的过程被称为“装箱”,反之,把一个包装类对象转换成一个基础类型的数据的过程称为“拆箱”。实现装箱的最简单操作就是以基础类型数据为参数,调用包装类的构造方法创建出一个包装类的对象,例如:
Integer i = new Integer(1);
但从JDK9开始(注:从JDK1.8之后,JDK的版本不再称为1.9,而是称为9,后面的版本号均以每次加1的规律递增),Java官方不再提倡使用构造方法创建包装类的对象,而提倡使用静态的valueOf()方法创建包装类的对象。大部分valueOf()方法都可以用基础类型的数据和String类对象作为参数创建出包装类对象,而Character类的valueOf()方法仅能以char型数据作为参数。表示整数的包装类的valueOf()方法还能指定整数的进制。下面的【例11_01】展示了各种数据类型的装箱操作实现过程。
【例11_01装箱操作】
Exam11_01.java
public class Exam11_01 {
public static void main(String[] args) {
Integer i1 = Integer.valueOf(20);//以整数为参数创建Integer类对象
Integer i2 = Integer.valueOf("20");//以字符串为参数创建Integer类对象
Integer i3 = Integer.valueOf("20",8);//创建8进制的Integer类对象
Double d1 = Double.valueOf(1.5);//以浮点数为参数创建Double类对象
Double d2 = Double.valueOf("1.8");//以字符串为参数创建Double类对象
Character c1 = Character.valueOf('x');//以字符为参数创建Character类对象
Boolean b1 = Boolean.valueOf(true);//以boolean型数据为参数创建Boolean类对象
Boolean b2 = Boolean.valueOf("false");//以字符为参数创建Boolean类对象
System.out.println("i1:"+i1);
System.out.println("i2:"+i2);
System.out.println("i3:"+i3);//①
System.out.println("d1:"+d1);
System.out.println("d2:"+d2);
System.out.println("c1:"+c1);
System.out.println("b1:"+b1);
System.out.println("b2:"+b2);
}
}
由于Byte、Short、Integer和Long的valueOf()方法的参数形式完全相同,而Double和Float的valueOf()方法参数形式也完全相同,所以例中仅以Integer和Double作为代表进行演示。【例11_01】的运行结果如图11-1所示。
图11-1【例11_01】运行结果
从图11-1可以看出:语句①的执行结果为“i3:16”这说明虚拟机按照程序员的指定,把参数“20”当作了八进制的数字。
把包装类对象转变为基础类型数据的过程称为“拆箱”,所有拆箱的操作都是调用xxxValue()方法实现的,其中“xxx”代表基础数据类型的名称,例如把Integer类对象转变为int型数据所要调用的方法是intValue()。需要注意:表示数字的包装类对象并不是只能转换为其类型所对应的基础类型数据,而是可以转换为任意数字类型,例如可以把一个Double类对象通过调用intValue()转换为一个int型数据。下面的【例11_02】展示了各种包装类对象完成拆箱操作的过程。
【例11_02拆箱操作】
Exam11_02.java
public class Exam11_02 {
public static void main(String[] args) {
Integer i = Integer.valueOf(300);
Double d = Double.valueOf(1.5);
Character c = Character.valueOf('x');
Boolean b = Boolean.valueOf(true);
System.out.println("Integer类对象转变为int型数据:"+i.intValue());
System.out.println("Integer类对象转变为double型数据:"+i.doubleValue());
System.out.println("Integer类对象转变为byte型数据:"+i.byteValue());//①
System.out.println("Double类对象转变为double型数据:"+d.doubleValue());
System.out.println("Double类对象转变为int型数据:"+d.intValue());//②
System.out.println("Character类对象转变为char型数据:"+c.charValue());
System.out.println("Boolean类对象转变为boolean型数据:"+b.booleanValue());
}
}
【例11_02】的运行结果如图11-2所示。
图11-2【例11_02】运行结果
从图11-2可以看出:语句①把300转换为byte型数据会出现错误,方法运行的结果为44,这是因为byte型数仅占1字节的内存空间,它无法表示出300这个数字,因而把超出1字节的部分完全忽略掉了。专业上把数字过大而导致内存无法正确存放的情况称为“溢出”。可以看到,在调用xxxValue()进行转换时,出现溢出的情况下并不会抛出异常,这导致程序员不容易发现转换过程中出现了错误,因此,在转换之前必须先判断一下数字有没有超出表示范围。此外还可以看出:语句②在把Double类对象被转换为int型数据时完全忽略了小数点以后的数值。
当程序员用两个包装类对象进行加、减、乘、除等数学运算时都要先进行拆箱操作,例如:
Integer i1 = Integer.valueOf(5);
Integer i2 = Integer.valueOf("10");
int r = i1.intValue()+i2.intValue();
可以看出:每次运算之前都要先把包装类对象转换成基础数据类型,这样做很麻烦。为解决这个问题,从JDK1.5开始,Java语言引入了“自动装箱拆箱”机制。所谓“自动装箱”就是指程序员不需要通过valueOf()方法来把一个基础类型的数据转变为一个包装类对象,而所谓“自动拆箱”是指程序员不需要调用xxxValue()方法把包装类对象转换为基础类型的数据,这些操作均由虚拟机自动完成。自动装箱拆箱机制使得程序员在代码中可以直接把一个基础类型的数据数据当作一个包装类对象来使用,也可以直接把一个包装类对象当作一个基础类型的数据来使用,下面的【例11_03】展示了自动装箱拆箱的效果。
【例11_03自动装箱拆箱】
Exam11_03.java
public class Exam11_03 {
public static void method(Object o){
System.out.println("参数o为:"+o);
}
public static void main(String[] args) {
Integer i1 = 5;//①自动装箱
Integer i2 = 6;//自动装箱
int r = i1+i2;//②自动拆箱
System.out.println("r:"+r);
method(r);//③
}
}
在【例11_03】中,语句①直接用整数5给Integer类对象i1赋值,这就是一个自动装箱的操作。而语句②没有调用intValue()方法进行拆箱,而是直接把两个Integer类对象i1和i2用+进行加法运算,这就是一个自动拆箱的操作。语句③把一个int型变量当作method()方法的参数,这也是一个自动装箱的操作。读者可自行运行【例11_03】以感受自动装箱拆箱的效果。
需要提醒各位读者:使用一个基础类型的数据为一个包装类对象进行赋值时不会有自动类型转换的操作。例如用一个int型数据直接给一个Double类对象赋值时会产生语法错误,如图11-3所示。
图11-3赋值过程中无法实现自动类型转换
从图11-3可以看出,int型常量1在给Double类对象d1赋值的语句下面出现了代表语法错误的红色波浪线,同样,给Long类对象l1赋值的语句也出现了语法错。程序员必须手动完成类型转换才能完成赋值操作。
11.1.2字符串转为基础数据类型
程序中经常需要把字符串形式的数字转换成基础数据类型,例如用户在购物网站上填写某商品的购买数量,该数量被传递到服务器时就是一个字符串,只有把这个字符串转换为int型数据才能进行常规的数学计算。把字符串转换为基础数据类型的操作要通过包装类的parsexxx()方法实现,其中xxx代表基础数据的类型。需要注意:这些方法在把字符串转换成数字时,如果字符串中出现了一些与数字无关的字符,那么在转换过程中会抛出异常。对于整数而言,在完成转换时还可以指定字符串所表示的整数是哪一种数制。下面的【例11_04】展示了把字符串转为基础数据类型的效果。
【例11_04字符串转换为基础数据类型】
Exam11_04.java
public class Exam11_04 {
public static void main(String[] args) {
String str1 = "123";
String str2 = "123.4";
String str3 = "true";
String str4 = "trUE";
String str5 = "abc";
System.out.println("把\"123\"转换为十进制数:"+Integer.parseInt(str1));
System.out.println("把\"123\"视作八进制数:"+Integer.parseInt(str1,8));
System.out.println("把\"123.4\"转换为double型数据:"+Double.parseDouble(str2));
System.out.println("把\"true\"转换为boolean型数据:"+Boolean.parseBoolean(str3));
System.out.println("把\"trUE\"转换为boolean型数据:"+Boolean.parseBoolean(str4));
System.out.println("把\"abc\"转换为boolean型数据:"+Boolean.parseBoolean(str5));
}
}
【例11_04】的运行结果如图11-4所示。
图11-4【例11_04】运行结果
从图11-4可以看出:把字符串“123”转换为整数时,如果指定这个整数是八进制,虚拟机会把“123”当作一个八进制数。而把一个字符串转换为boolean型数据时,不会因字母的大小写而出现异常,并且如果一个字符串不是“true”这个单词,那么转换的结果都是false。
11.1.3包装类对象与基础数据的比较
包装类对象之间、以及包装类对象和基础类型数据之间都可以直接使用比较运算符进行比较,但在比较过程中所遵循的操作规则却并不完全相同,请看下面的【例11_05】
【例11_05数据的比较】
Exam11_05
public class Exam11_05 {
public static void main(String[] args) {
int a1 = 1, a2 = 2;
Integer i1 = 1;
Integer i2 = new Integer(1);;
Integer i3 = 1;
Integer i4 =Integer.valueOf(1);
Integer i5 = 1000;
Integer i6 = 1000;
Long l1 = 1L;
Long l2 = 1000L;
System.out.println(a1==i1);//①true
System.out.println(a2<i1);//②false
System.out.println(i1==i2);//③false
System.out.println(l1.equals(i1));//④false
System.out.println(i1==i3);//⑤true
System.out.println(i1==i4);//⑥true
System.out.println(i5==i6);//⑦false
System.out.println(l1==a1);//⑧true
System.out.println(l1>a2);//⑨false
}
}
为方便讲述,此处直接把比较结果以注释的形式写到代码中,下面逐一分析比较结果的产生原因。
语句①用==比较一个基础数据和一个包装类对象,比较的结果为true,这是因为当比较运算符两端分别是基础类型的数据和包装类对象时,直接把包装类对象当作基础类型数据进行比较,由于==两端都是数字1,所以比较结果为true。
语句②用<比较一个基础数据和一个包装类对象,比较的结果为false,这个比较结果产生的原因与语句①是相同的,都是因为在比较运算符两端分别是基础类型的数据和一个包装类对象时,把包装类对象当作基础类型数据完成比较操作。
语句③用==比较两个包装类对象,比较结果为false。这是因为如果==两端都是包装类对象时,虚拟机不再比较这两个包装类对象中的数值,而是比较这两个包装类对象是不是同一个对象,因此即使==左右两端包装类对象中的数值是相同的,但它们不是同一个对象,所以比较结果为false。需要注意:!=在左右两端都是包装类对象时,也不去比较它们的数值是否不相等,而是比较它们是否不是同一个对象。但<、<=、>和>=这4个比较运算符在两端都是包装类对象时,又会把包装类对象当作基础类型的数据进行比较。因此可以总结出:当比较运算符两端都是包装类对象时,==和!=使用一套比较规则,而<、<=、>和>=则使用另外一套比较规则。如果希望比较两个包装类对象中的数值是否相等,需要调用equals()方法完成。
但equals()方法也有一定的局限,它要求被比较的两个包装类对象必须在相同类型的情况下才能正确的比较对象中的数值是否相等。在语句④中,调用Long类的equals()方法时,传递的参数是Integer类的对象,这种情况下虽然l1对象和i1对象中的数值都是1,但因被比较的两个对象类型不同导致比较结果为false。此外还需注意:如果直接使用==比较l1和i1,则会因两个对象的类型不同而产生语法错误。
语句⑤使用==比较两个包装类对象,比较结果为true。这是因为与String类一样,Integer类也有常量池,当没有使用new关键字创建包装类对象时,虚拟机会让引用直接指向常量池中的对象。i1和i3都不是使用new关键字创建的对象,因此它们都是常量池中的对象,而常量池中不会出现值不相同的对象,因此i1和i3的值相同时,它们实际上是同一个对象,所以比较结果为true。
语句⑥的比较结果也是true,产生这个比较结果的原因与语句⑤是相同的,被比较的两个对象都不是用new关键字所创建,并且值也相同,因而它们是常量池中的同一个对象,比较结果自然是true。
语句⑦也是直接用==比较两个包装类对象,并且这两个对象也都不是用new关键字创建的,但比较结果为false,这是因为Integer类虽然有常量池,但是这个常量池并不是无限大。Integer类的常量池只保存-128到127这个范围内的Integer对象,这个范围恰好就是byte型整数的数据范围,一旦超出这个范围的Integer类对象,无论对象以什么形式创建,都不会被保存到常量池当中。而i5和i6都是表示1000的整数,这显然已经超出了常量池的保存范围,因此虽然它们并不是以new关键字创建的对象,但是它们也不会被存放到常量池中,因此它们并非是同一个对象,比较结果自然是false。
语句⑧和⑨的比较结果分别是true和false,这两个比较结果产生的原因与语句①和语句②比较结果产生的原因是一样的,只不过把包装类对象的类型由Integer换成了Long。
本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。