Java暗箱操作之自动装箱与拆箱

我以前在写Android项目的时候,估计写得最多最熟练的几句话就是:

创新互联一直通过网站建设和网站营销帮助企业获得更多客户资源。 以"深度挖掘,量身打造,注重实效"的一站式服务,以成都网站建设、网站建设、移动互联产品、营销型网站服务为核心业务。十载网站制作的经验,使用新网站建设技术,全新开发出的标准网站,不但价格便宜而且实用、灵活,特别适合中小公司网站制作。网站管理系统简单易用,维护方便,您可以完全操作网站资料,是中小公司快速网站建设的选择。

  
 
  1. List list = new ArrayList(); list.add(1);   //把一个整数加入到集合中
  2. int i = list.get(0);    //从集合中取出元素

  ArrayList用起来是多么的顺手!当时我只知道尖括号<>里面只能加入大写字母开头的Object类型,不能加入int、char、double这些原始类型,至于原因没研究过,这么规定就这么用呗。

  但是随着对“码农”式无脑学习法的逐渐厌倦,我开始重新审视Java代码内部的东西。

  首当其冲的就是每个项目一定用到的ArrayList。在我的另一篇博客中已经对ArrayList的源码实现做了大体的分析。然而还有几个源码中看不出来,但是确实存在疑点的问题亟待解决。

 
 
  1. List list = new ArrayList();

这句代码中每个元素是Integer类型,那么往list里面add新元素的时候必须为Integer,比如加个String进去,代码下面就会出现红色波浪线。
但是这句list.add(1) 众所周知,代码里面随便写个不带小数点的数字,那它就是个int;把一个int加到一个只能有Integer的List中不报错,不觉得有猫腻吗?

同样地,int i = list.get(0),取出list中索引为0的元素,也应该是个Integer,为什么接收的变量就是个int呢?这是一个多么明显的类型不匹配错误啊!

以前,我确实听说过“包装类”这个概念,但是忽视了它,因为我一直觉得Integer,Float这些东西,说难听点就是摆出来装装逼的,只是因为List不接受int,float类型,迫不得已发明了Integer,Float,实际并没有卵用。

最近看了《Effective Java》里面的一节,名字叫“Prefer primitive types to boxed primitives”。里面罗列了很多原始类型和包装类型混用的例子,搞得我晕头转向的。下面是其中一段代码:

  
 
  1. Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) {      sum += i;   } System.out.println(sum);

据书中讲,这是一段运行效率低到不可救药的代码,你能看出其中的问题吗?
反正我当时看到这段代码就明显感觉到,Java对于原始类型与相应的Object类型的转化,在编译过程中肯定做了什么见不得人的事情……

下面正式引出本文的话题:AutoBoxing and Unboxing(自动装箱&自动拆箱)

看一个最简单的例子:

  
 
  1. Character ch = 'a';  //Character是char的包装类

这里没有出现任何错误,其实编译器在代码优化的时候,暗中转化成了下面的代码:

  
 
  1. Character ch = Character.valueOf('a'); 

这就是说,"="右侧自动调用Character类对应的静态方法构造出了一个Character的实例。
为了进一步说明,这里稍微看一下valueOf方法

  
 
  1. public static Character valueOf(char c) {         return c < 128 ? SMALL_VALUES[c] : new Character(c);     }   //如果字符在缓冲区中,直接取出Character实例,否则要重新构造
  2.    private static final Character[] SMALL_VALUES = new Character[128];  //类中自带一个静态的缓冲区,保存128个常用ASCII码字符对应的Character实例,免去每次重新构造实例的麻烦
  3.    static {         for (int i = 0; i < 128; i++) {             SMALL_VALUES[i] = new Character((char) i);  //调用构造函数
  4.        }     }

对于Integer等其他包装类,自身都带有一个静态的valueOf方法。每次编译器检查到需要把一个int传给Integer时,就自动对代码进行转化。
比如上面的list.add(1),在编译过程中编译器发现要传进去的参数是int,但是要接收的是Integer,于是代码变为:

 
 
  1. list.add(Integer.valueOf(1));

以上就是自动装箱(auto-boxing)的过程。

自动装箱一般在两种情况下会发生(以int和Integer为例):
1、把int作为一个方法的参数传进来,但是方法体里面希望得到的参数是Integer;
2、在赋值过程中,"="左边是Integer变量,右边是int变量。

这样一来,自动拆箱的过程就顺理成章了。看以下代码:

  
 
  1. public static int sumEven(List li) {     int sum = 0;     for (Integer i: li)         if (i % 2 == 0)             sum += i;         return sum; }

在循环体内做了两次拆箱操作,编译器会转换成以下代码:

  
 
  1. public static int sumEven(List li) {     int sum = 0;     for (Integer i: li)         if (i.intValue() % 2 == 0)             sum += i.intValue();         return sum; }

Integer的intValue方法就简单多了,直接返回被包装的int值

  
 
  1. @Override     public int intValue() {         return value;    //value是Integer的成员变量 
  2. }

自动拆箱的用处跟自动装箱正好相反,也是用在参数传递和赋值过程中,这里就不赘述了。
我们再来分析一下那段超级低效的代码吧,经过自动拆装箱转换之后应该是这样子的:

  
 
  1. Long sum = Long.valueOf(0L); for (long i = 0; i < Integer.MAX_VALUE; i++) {      sum = Long.valueOf(sum.longValue() + i);   //低效所在
  2. } System.out.println(sum.toString());

在循环体里面,简简单单只有一句话,竟然包含一次拆箱和一次装箱操作,在经过20多亿次的循环之后,效率损耗得难以置信!
既然拆箱和装箱可以看做“逆运算”,那么为什么还要进行多余的操作呢?直接用原始值运算,然后一次装箱不是更省事吗

 
 
  1. Long sum = 0L; long s = sum; for (long i = 0; i < Integer.MAX_VALUE; i++) {      s += i;   } sum = Long.valueOf(s); System.out.println(sum);

参考资料:https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html

当前文章:Java暗箱操作之自动装箱与拆箱
转载来于:http://www.shufengxianlan.com/qtweb/news37/545237.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联