总结

1-装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。例如:在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

public class Main {
public static void main(String[] args) { Integer i = 10;
int n = i;
}
}

一、为什么需要包装类

虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。 沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。这种借助于非面向对象技术的做法有时也会带来不便。比如:

  1. 编码过程中只接收对象的情况,比如List中只能存入对象,不能存入基本数据类型;比如一个方法的参数是Object时,不能传入基本数据类型,但可以传入对应的包装类; 比如泛型等等。
  2. 基本数据类型没有toString()方法等

Java中对每种基本类型都有一个对应的包装类:

  • byte -> Byte
  • short -> Short
  • int -> Integer
  • long -> Long
  • float -> Float
  • double -> Double
  • boolean -> Boolean
  • char -> Character
    其中需要注意int对应的是Integer,char对应的Character,其他6个都是基本类型首字母大写即可。

每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的方法。包装类对象一经创建,TODO: 其内容(所封装的基本类型数据值)不可改变(类似于String类,可看源码)。详见: Java 基础 - 如何理解String不可变

 TODO: COMPARING https://zhuanlan.zhihu.com/p/65538963

一.什么是装箱?什么是拆箱?

  在前面的文章中提到,Java为每种基本数据类型都提供了对应的包装器类型,至于为什么会为每种基本数据类型提供包装器类型在此不进行阐述,有兴趣的朋友可以查阅相关资料。在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);

  而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

Integer i = 10;

  这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。

  那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

Integer i = 10;  //装箱
int n = i; //拆箱

  简单一点说,装箱就是  自动将基本数据类型转换为包装器类型;拆箱就是  自动将包装器类型转换为基本数据类型。

  下表是基本数据类型对应的包装器类型:

int(4字节) Integer
byte(1字节) Byte
short(2字节) Short
long(8字节) Long
float(4字节) Float
double(8字节) Double
char(2字节) Character
boolean(未定) Boolean

二.装箱和拆箱是如何实现的

  上一小节了解装箱的基本概念之后,这一小节来了解一下装箱和拆箱是如何实现的。

  我们就以Interger类为例,下面看一段代码:

public class Main {
public static void main(String[] args) { Integer i = 10;
int n = i;
}
}

  反编译class文件之后得到如下内容:

  

  从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

  其他的也类似,比如Double、Character,不相信的朋友可以自己手动尝试一下。

  因此可以用一句话总结装箱和拆箱的实现过程:

  装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。

三.面试中相关的问题

  虽然大多数人对装箱和拆箱的概念都清楚,但是在面试和笔试中遇到了与装箱和拆箱的问题却不一定会答得上来。下面列举一些常见的与装箱/拆箱有关的面试题。

1.下面这段代码的输出结果是什么?

public class Main {
public static void main(String[] args) { Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200; System.out.println(i1==i2);
System.out.println(i3==i4);
}
}

  也许有些朋友会说都会输出false,或者也有朋友会说都会输出true。但是事实上输出结果是:

true
false
true
false

  为什么会出现这样的结果?输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:

public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}

  而其中IntegerCache类的实现为:

private static class IntegerCache {
static final int high;
static final Integer cache[]; static {
final int low = -128; // high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h; cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
} private IntegerCache() {}
}

  从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

  上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。

2.下面这段代码的输出结果是什么?

public class Main {
public static void main(String[] args) { Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0; System.out.println(i1==i2);
System.out.println(i3==i4);
}
}

  也许有的朋友会认为跟上面一道题目的输出结果相同,但是事实上却不是。实际输出结果为:

false
false
false
false

  至于具体为什么,读者可以去查看Double类的valueOf的实现。

  在这里只解释一下为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

  注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

     Double、Float的valueOf方法的实现是类似的。

3.下面这段代码输出结果是什么:

public class Main {
public static void main(String[] args) { Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true; System.out.println(i1==i2);
System.out.println(i3==i4);
}
}

  输出结果是:

true
true
true
true

  至于为什么是这个结果,同样地,看了Boolean类的源码也会一目了然。下面是Boolean的valueOf方法的具体实现:

public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

  而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:

    public static final Boolean TRUE = new Boolean(true);

    /**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);
 public static final Boolean TRUE = new Boolean(true);

    /**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);

  至此,大家应该明白了为何上面输出的结果都是true了。

4.谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

  当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:

  1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;

  2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

5.下面程序的输出结果是什么?

public class Main {
public static void main(String[] args) { Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L; System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}

  先别看输出结果,读者自己想一下这段代码的输出结果是什么。这里面需要注意的是:当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。明白了这2点之后,上面的输出结果便一目了然:

true
false
true
true
true
false
true
true
false
true
true
true
false
true

  第一个和第二个输出结果没有什么疑问。第三句由于  a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。

如果对上面的具体执行过程有疑问,可以尝试获取反编译的字节码内容进行查看。

----------------------------

参考:https://www.cnblogs.com/dolphin0520/p/3780005.html

参考:https://zhuanlan.zhihu.com/p/65538963

作者:Matrix海子   
本博客中未标明转载的文章归作者Matrix海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Java 基础 - 装箱, 拆箱的更多相关文章

  1. java自动装箱拆箱总结

    对于java1.5引入的自动装箱拆箱,之前只是知道一点点,最近在看一篇博客时发现自己对自动装箱拆箱这个特性了解的太少了,所以今天研究了下这个特性.以下是结合测试代码进行的总结. 测试代码: int a ...

  2. JAVA自动装箱拆箱与常量池

    java 自动装箱与拆箱 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的 ...

  3. Java 自动装箱/拆箱

    自动装箱/拆箱大大方便了基本类型(8个基本类型)数据和它们包装类的使用 自动装箱 : 基本类型自动转为包装类(int >> Integer) 自动拆箱: 包装类自动转为基本类型(Integ ...

  4. Java自动装箱拆箱

    一.装箱.拆箱定义 如果一个int型量被传递到需要一个Integer对象的地方,那么,编译器将在幕后插入一个对Integer构造方法的调用,这就叫做自动装箱.而如果一个Integer对象被放到需要in ...

  5. java的装箱拆箱是什么?

    是什么? 自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱. 为什么 把 ...

  6. 深入理解Java自动装箱拆箱机制

    1.自动装箱与拆箱的定义 装箱就是自动将基本数据类型转换为包装器类型(int-->Integer): 拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int). Java中 ...

  7. 那些年一起踩过的坑 — java 自动装箱拆箱问题

    坑在哪里?   我们都知道Java的八种基本数据类型:int, short, long, double, byte, char, float, boolean   分别有各自对应的包装类型:Integ ...

  8. java之装箱拆箱

    参考http://how2j.cn/k/number-string/number-string-wrap/22.html 封装类 所有的基本类型,都有对应的类类型 比如int对应的类是Integer ...

  9. java基础1.5版后新特性 自动装箱拆箱 Date SimpleDateFormat Calendar.getInstance()获得一个日历对象 抽象不要生成对象 get set add System.arrayCopy()用于集合等的扩容

    8种基本数据类型的8种包装类 byte Byte short Short int Integer long Long float Float double Double char Character ...

随机推荐

  1. js模块化的历史

    了解模块化开发的历史,可以帮助我们理解 模块化开发的形式是怎么样的,对我们深入学习模块化开发会有很大的帮助: 一.服务器端JS的模块化 nodeJS的出现   ------官网: http://nod ...

  2. PHP之数据连接方法(二)

    首先API接口,无非就是通过该程序去处理数据的数据,及判断数据的准确性. 因此我们需要一个DBTool的操作方法. DBTool地址:https://github.com/gfarmhuang/DBT ...

  3. Ubuntu 奇怪踩坑记录

    仓库ppa问题 当你使用 apt-get update 时候,提示 仓库 "http://ppa.launchpad.net/hzwhuang/ss-qt5/ubuntu bionic Re ...

  4. Ubuntu 更新国内镜像源失败

    Ubuntu 更新国内镜像源失败 首先打开系统原来的/etc/apt/sources.list 查看,原来的仓库地址是 https 还是 http 如果是http那么说明本机的 CA 证书有问题,运行 ...

  5. Centos7 PXE Server Install Script

    #安装前配置好centos和epel yum源 #网卡ip和localip一致 localip="192.168.88.200" eth_name='eth0' dnsmasq_i ...

  6. WordTEX

    https://www.andrew.cmu.edu/user/twildenh/wordtex/

  7. <el-table>序号逐次增加,翻页时继续累加,解决翻页时从编号1开始的情况

    注释: scope.$index     当前序号 cuePage 表示当前页码 pageSize   表示每页显示的条数

  8. nodejs 在MYSQL 数据库中插入和查询数据

    插入前的数据库: 插入后的数据库: 输出结果: demo var mysql = require('mysql'); var connection = mysql.createConnection({ ...

  9. lterator遍历

    iterator是一种接口机制,为各种不同的数据结构提供统一的访问机制 作用: 1.为各种数据结构,提供一个统一的.简便的访问接口: 2.使得数据结构的成员能够按某种次序排列 3.ES6创造了一种新的 ...

  10. canvas画布导出图片并下载

    近期在学习关于画布知识,关于 画布导出图片, 在导出jpeg格式的图片时,会发现图片背景色变成了黑色,原因是画布透明的地方 默认转成了黑色,可以在绘制画布前设置透明处背景色为白色. // 背景色转换成 ...