作者:Alvin
关键字:语法糖 类 对象
参考
前言
我们知道,Java有8中基本数据类型,分别是byte,short,int,long,char,float,double,boolean,但是定义的这些基本数据类型的值只是一个字面量,而字面量的补码是的的确确存储在内存上的一个量,这个量不具有其他的方法属性。但是我们在编程开发中有把一个整型int转换成字符串等的需求,再如当我们需要把数据放到集合中时,我们的基本数据类型是不允许被放入的,而Java中的中心思想就是对象,所以Java将它们封装成对象Byte、Short、Integer、Long、 Character、Float、Double、Boolean,并给出相应的方法。这样当我们有需求的时候我们就可以通过相对应的对象进行调用方法来解决。
一、案例引入
public static void main(String[] args) { Integer i = 10; Integer j = 10; System.out.println(i == j); Integer a = 128; Integer b = 128; System.out.println(a == b); int k = 10; System.out.println(k == i); int kk = 128; System.out.println(kk == a); Integer m = new Integer(10); Integer n = new Integer(10); System.out.println(m == n); }
执行结果
true false true true false
二、写法分析
在进行分析之前我们先来了解一个概念:语法糖
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
Java中的语法糖也是新增了一些语法,使得程序员使用更加方便。但是计算机底层还是使用基本语法来实现的。下面我们讨论几个经常使用到的语法糖,可变参数、自动装箱/拆箱、增强for循环。
我们以Integer类为例,在我们用如下代码编写程序后,当使用Javac.exe工具操作之后,编译器会把我们的这个程序重写按照DRY(Don't repeat yourself)原则进行编译
public class Test { public static void main(String[] args) { Integer num1 = 123;//将一个基本数据类型赋给Integer对象 int num2 = num1;//将一个Integer对象赋给整形变量 } }
对以上代码编译后的Test.class反编译结果如下
public class Test{ public static void main(String[] args) { Integer num1 = Integer.valueOf(123);//装箱操作 int num2 = num1.intValue();//拆箱操作 } }
Java语言中,javac命令可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。如果你去看com.sun.tools.javac.main.JavaCompiler的源码,你会发现在compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的。
可以看到,在Java编译器进行编译的时候会自动把我们的代码重整成符合DRY原则代码并编译成.class文件。在我们定义一个Integer类型的变量后,当我们把整型的值赋值给该Integer类型,实际上在编译后的操作时执行了Integer.valueOf(123);操作,只是我们采用了语法糖写法而简写了方法调用的过程,同理,当把一个Integer的对象用语法糖写法编写的时候,该对象也调用了它的intValue方法进行转换类型。所以我们语法糖写法只是在编写的时候简化了代码,而类型的转换操作在编译器运行的时候会把调用的方法重新补充上。其他的八个基本数据类型各自对应的情况和Integer相同,不再赘述。
其他类似的语法糖写法还有增强for循环(底层还是循环),可变参数,switch对String和枚举的支持等
三、结果分析
了解了上面代码的转换过程,我们从反编译的结果得知:再Java语言中永远遵守不同类型之间不可以进行赋值的规则。置于我们在代码中的编写形式有时只是进行了简写,而最后的执行还要依赖于编译器的解析结果。
四、实现过程
上方就是基本数据类型的自动拆/装箱,他们的装箱遵循以下规则
自动装箱规范要求 byte<= 127、char<=127、-128<=short <=127、-128<=int <=127都被包装到固定的对象中(缓存)。
也就是说在装箱过程中执行valueOf(参数)方法后,如果满足以上条件就会被封装成Integer对象中。valueOf函数如下
从函数可以看出,当在-128到127范围内,会生成同一个对象,在范围之外,会执行new Integer();我们都知道在Java语言中,new
一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。
五、总结
包装类的应用
1、Java中的集合类只能接收对象类型,而Java通过包装类实现了把基本数据类型放入集合操作的目的。并且在放入集合的时候这种封装时自动完成的。
2、包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
3、两个包装类型之间的运算,会被自动拆箱成基本类型进行。
4、三目运算符flag ? i : j;
片段中,三目运算符的语法规范:当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。如果这个时候i的值为null
,那么久会发生NPE。(自动拆箱导致空指针异常)
5、函数参数与返回值
6、Integer中的缓存机制有关。在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。适用于整数值区间-128 至 +127。只适用于自动装箱。使用构造函数创建对象不适用。当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
另外javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过-XX:AutoBoxCacheMax=size修改。实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过java.lang.Integer.IntegerCache.high设置最大值。
六、注意事项
包装类的使用简化了代码,方便了编程者对数据的操作,实现了面向对象思想,但是也引入了一些麻烦,我们要尽量避免,以防对以后的编程中出现负担。
1、包装对象的数值比较,不能简单的使用==
,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals
比较。
2、如果一个for循环中有大量拆装箱操作,会浪费很多资源。
3、有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。