Javascript 装箱Boxing

自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。

如果你在Java1.5下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections)中放入原始类型值,因为集合只接收对象。通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用Integer,Double,Boolean等这些类我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的bug。

本文将介绍,什么是自动装箱和拆箱,自动装箱和拆箱发生在什么时候,以及要注意的事项。

什么是自动装箱和拆箱

自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。

自动装箱拆箱要点

  • 自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
  • 自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。

何时发生自动装箱和拆箱

自动装箱和拆箱在Java中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么Java会自动讲这个原始类型值转换成与之对应的对象。最经典的一个场景就是当我们向ArrayList这样的容器中增加原始类型数据时或者是创建一个参数化的类,比如下面的ThreadLocal。

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1); //autoboxing - primitive to object

intList.add(2);//autoboxing

ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>();

intLocal.set(4);//autoboxing

int number = intList.get(0);// unboxing
int local = intLocal.get();// unboxing in Java

举例说明

上面的部分我们介绍了自动装箱和拆箱以及它们何时发生,我们知道了自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。为了更好地理解这两种情况,我们举例进行说明。

赋值时

这是最常见的一种情况,在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。

//before autoboxing
  Integer iObject = Integer.valueOf(3);
  int iPrimitive = iObject.intValue();
  //after java5
  Integer iObject2 = 3;//autobxing - primitive to wrapper conversion
  int iPrimitive2 = iObject2;//unboxing - object to primitive conversion

方法调用时

这是另一个常用的情况,当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。

public static Integer show(Integer iParam){
  System.out.println("autoboxing example - method invocation i: " + iParam);
  return iParam;
}
//autoboxing and unboxing in method invocation
show(3);
//autoboxing
int result = show(3);//unboxing because return type of method is Integer 

show方法接受Integer对象作为参数,当调用show(3)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);中result为int类型,所以这时候发生自动拆箱操作,将show方法的返回的Integer对象转换成int值。

自动装箱的弊端

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。

Integer sum = 0;
for(int i = 1000; i < 5000; i++) {
  sum += i;
}

上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下:

sum = sum.intValue() + i;
Integer sum = new Integer(result);

由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。

重载与自动装箱

当重载遇上自动装箱时,情况会比较有些复杂,可能会让人产生有些困惑。在1.5之前,value(int)和value(Integer)是完全不相同的方法,开发者不会因为传入是int还是Integer调用哪个方法困惑,但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index)remove(Object)两种重载,我们可能会有一点小小的困惑,其实这种困惑是可以验证并解开的,通过下面的例子我们可以看到,当出现这种情况时,不会发生自动装箱操作。

public void test(int num){
  System.out.println("method with primitive argument");
}
public void test(Integer num){
  System.out.println("method with wrapper argument");
}
//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value);
//no autoboxing
Integer iValue = value;
autoTest.test(iValue);//no autoboxing
Output:method with primitive argument
       method with wrapper argument

要注意的事项

自动装箱和拆箱可以使代码变得简洁,但是其也存在一些问题和极端情况下的问题,以下几点需要我们加强注意。

对象相等比较

这是一个比较容易出错的地方,”==“可以用于原始值进行比较,也可以用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals方法。看一个能说明问题的例子。

public class AutoboxingTest{
 public static void main(String args[]){
   // Example 1: == comparison pure primitive – no autoboxing
int i1 = 1;
int i2 = 1;
System.out.println("i1 == i2 : "+ (i1 == i2));// true
// Example 2: equality operator mixing object and primitive
Integer num1 = 1; // autoboxing
int num2 = 1;
System.out.println("num1 == num2 : " + ( num1 == num2));// true
// Example 3: special case - arises due to autoboxing in Java
Integer obj1 = 1; // autoboxing will call Integer.valueOf()
Integer obj2 = 1;// same call to Integer.valueOf() will return same
// cached Object
System.out.println("obj1 == obj2 : " + (obj1 == obj2));// true
// Example 4: equality operator - pure object comparison
Integer one = new Integer(1);// no autoboxing
Integer anotherOne = new Integer(1);
System.out.println("one == anotherOne : "+( one == anotherOne)); // false}
}
Output:i1 == i2:true
num1 == num2:true
obj1 == obj2:true
one == anotherOne:false

值得注意的是第三个小例子,这是一种极端情况。obj1和obj2的初始化都发生了自动装箱操作。但是处于节省内存的考虑,JVM会缓存-128到127的Integer对象。因为obj1和obj2实际上是同一个对象。所以使用”==“比较返回true。

容易混乱的对象和原始数据值

另一个需要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当我们在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出NullPointerException,如下面的代码:

private static Integer count;//NullPointerException on unboxing
if( count <= 0) {
    System.out.println("Count is not started yet");
}

缓存的对象

这个问题就是我们上面提到的极端情况,在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。

在Java中另一个节省内存的例子就是字符串常量池,感兴趣的同学可以了解一下。

生成无用对象增加GC压力

因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作。

如想了解垃圾回收和内存优化,可以查看本文Google IO:Android内存管理主题演讲记录

总的来说,自动装箱和拆箱着实为开发者带来了很大的方便,但是在使用时也是需要格外留意,避免引起出现文章提到的问题。

时间: 2024-10-11 17:32:14

Javascript 装箱Boxing的相关文章

装箱(boxing)和拆箱(unboxing) [转]

1.  装箱和拆箱 装箱 就是把"值类型"转换成"引用类型": 拆箱 就是把"引用类型"转换成"值类型": 首先,我们要弄明白为什么需要装箱和拆箱.C#的所有类型,包括int.boo等,都继承自System.Object,但是却又有值类型和引用类型之分.这时你要问,int是继承自object类型的,object是引用类型,那为何int不是引用类型而是值类型的呢?这就涉及到装箱和拆箱的概念了. 我们知道对象是创建在堆上的,它的创

第一部分 类型转换、装箱和拆箱

1.类型转换: 隐式转换 显式转换 上下文溢出检查(unchecked(默认)和checked) unchecked(表达式)指隐式转换不报错误. checked(表达式)指隐式转换如果溢出数据,就报错误. 2.装箱(Boxing) int i=10; Object o = i; 3.拆箱(UnBoxing) int j = (int)o;

值类型的装箱与拆箱浅析

值类型的装箱与拆箱浅析 2012-02-23 15:47 by 秋梧, ... 阅读, ... 评论, 收藏, 编辑 阅读目录 前言 值类型的装箱 值类型的拆箱 装箱和拆箱实例 结束语 前言 在.Net 中值类型向引用类型的转换以及从引用类型到值类型的转换是需要装箱(boxing)和拆箱(unboxing)的,这是因为值类型是比引用类型更轻型的一种类型,因为他们不想对象那样在托管队中分配,不会被GC收集,而且不需要通过指针来引用.但是在许多情况下都需要获取对值类型的一个实例的引用.对于在值类型与

CLR via 笔记 5.3 值类型的装箱和拆箱

1.装箱 为了将一个值类型转换成一个引用类型,要使用一个名为装箱(Boxing)的机制. 1.在托管堆中分配好内存.分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量. 2.值类型的字段复制到新分配的堆内存. 3.返回对象的地址.现在,这个地址是对一个对象的引用,值类型现在是一个引用类型. 2.拆箱 包含在已装箱对象中的所有字段都必须复制到值类型变量中,后者在线程栈上.CLR分两步完成这个复制操作. 第一步是获取已装箱的对象中

Interview

下面的题是供大家查漏补缺用的,真正的把这些题搞懂了,才能"以不变应万变". 回答问题的时候能联系做过项目的例子是最好的,有的问题后面我已经补充联系到项目中的对应的案例了. 1.简述 private. protected. public. internal 修饰符的访问权限.  private : 私有成员, 在类的内部才可以访问. protected : 保护成员,该类内部和继承类中可以访问. public : 公共成员,完全公开,没有访问限制. internal: 当前程序集内可以访

c#笔试基础(转载)

技术类面试.笔试题汇总 注:标明*的问题属于选择性掌握的内容,能掌握更好,没掌握也没关系. 下面的参考解答只是帮助大家理解,不用背,面试题.笔试题千变万化,不要梦想着把题覆盖了,下面的题是供大家查漏补缺用的,真正的把这些题搞懂了,才能“以不变应万变”.回答问题的时候能联系做过项目的例子是最好的,有的问题后面我已经补充联系到项目中的对应的案例了. 1.简述 private. protected. public. internal 修饰符的访问权限. private : 私有成员, 在类的内部才可以

.net面试题1[转载]

1.简述private.protected.public.internal修饰符的访问权限. private:私有成员,在类的内部才可以访问. protected:保护成员,该类内部和继承类中可以访问. public:公共成员,完全公开,没有访问限制. internal:当前程序集内可以访问. 2.ADO.NET中的五个主要对象 Connection:主要是开启程序和数据库之间的连接.没有利用连接对象将数据库打开,是无法从数据库中取得数据的.Close和Dispose的区别,Close以后还可以

C#习题大全

C#习题大全 1.String str=new String("a")和String str = "a"有什么区别? String str = "a"; 这个只是一个引用,内存中如果有“a"的话,str就指向它,如果没有才创建如后还用到"a"这个字符串的话并且是这样用: String str1 = "a"; String str2 = "a"; String str2 = &q

.NET面试必备(整理)

1.简述 private. protected. public. internal 修饰符的访问权限. private : 私有成员, 在类的内部才可以访问.public : 公共成员,完全公开,没有访问限制 protected : 保护成员,该类内部和继承类中可以访问. internal: 当前程序集内可以访问. 2.ADO.NET中的五个主要对象 Connection:主要是开启程序和数据库之间的连接.没有利用连接对象将数据库打开,是无法从数据库中取得数据的.Close和Dispose的区别