JAVA中的装箱拆箱问题

JAVA中的装箱拆箱问题

 

# 装箱拆箱

 

引言

做为 Java 程序员,在日常的开发过程中我们经常需要对基本的数据类型进行包装装箱进行使用,比如把 int 基本类型变成 Integer 包装类,很多时候会让我们觉得这样的操作有些繁琐,写代码的时候不够顺滑,那么 Java 为什么要这么做呢?

分析

这是 Java(或者很多 OO 语言)面临的共同设计问题。OO 语言都希望能“万物皆对象”,并以此为基础来设计整个语言。比如

  • 会提供基类 Object,以及基于这个基类的类型系统;
  • 有了 Object,就能统一对内存的管理——在托管堆上管理,并且实现 GC;
  • 可以支持多态的方法调用。比如写 foo.doSomethinig()这样的代码时,是要去根据 foo 的类型去查找真正要调用的 doSomethinig 到底是哪个;顺着这个思路,可以在基类 Object 中塞一些非常基础的方法(toString、equals 等),并让上层可以“直接复用”或者“覆写“;

一切看上去很美好。

但现实中的大量代码不需要像对象那样在托管堆上分配内存,不需要 GC,不需要“方法”。他们仅仅需要在栈上快速的分配空间,利用最基本的运算符进行运算、比较和赋值。【对象】对于这类代码显得非常多余且低效。想象下要对一堆整数进行排序,直接分配一段内存,再用快排这类算法直接“过一边“内存上的值就可以了。这远比整出一堆【对象】再通过其引用折腾来折腾去,再 GC 掉好得多。如果你做过高性能计算之类的程序,相信你明白我在说啥。

这就引入了一个两难的问题,一方面期望用“万物皆对象”的方式来统一语言设计,但另一方面又不得不考虑对“值类型”数据提供支持

对于这个问题,一般都采用 boxing/unboxing 的方式实现。即对于每一种数据类型(primitive type),提供一个对应的引用类型(reference type)。把值类型转换成引用类型对象,相当于创建一个“Wraper”对象,被称为装箱(boxing)。反过来,把一个引用类型里的值取出来,被称为拆箱 (unboxing)。

Integer a = new Integer(1); // 装箱int b = a.intValue(); // 拆箱

boxing/unboxing 是反直觉的。本来程序员考虑 1、true,2.35 这样的值就可以了,但就因为其内存模型不一样,不得不考虑到底要用值还是要用对象。而且最郁闷的是,程序员经常一会只想用值,一会想用对象(比如想调用个方法)

Java 1.4 之前,boxing/unboxing 是必须手工做的。也就是程序员必须手写上面的代码,不胜其烦。

Java 1.5 终于提供了“auto” boxing/unboxing,即让编译器自动帮助插入 boxing/unboxing 代码。这样就可以像下面这样写代码了。

Character c = 'c'; // 'c'这个值被自动装箱,并把变量c赋值为装箱后的对象的引用。

List<Integer> list = new ArrayList<Integer>();

list.add(1); // 1 这个值被自动装箱,并把装箱好的对象引用塞给listint v = list.get(0); // list里的第0个对象被取出,自动拆箱,把值赋给v

这的确避免了很多繁琐的 boxing/unboxing 代码。

但到目前为止 Java 里始终不能这么做:

1.toString(); // Java里编译错误

这个代码在 C#和 JavaScript 里都是合法的。原理很简单,只要把 1 直接装箱成一个对象,然后在上面调用方法就行了。基于 JVM 的 kotlin 和 groovy 也都支持这么干。

一些问题

Java 的另外一个问题是始终无法对 primitive type 做真正的 collection。Java 里提供的 collection 里只能存引用类型。这会让一些场景很不方便。C#早期和 Java 一样,但随后提供了“真范型“并重新提供了一套新的 Collection,彻底解决了这个问题。Java 始终没有做这件事。

顺便提一句 C#提供了值类型 struct,可以让程序员直接管理数据的内存布局,甚至还可以“pin”住一个对象在内存中的位置,避免其因为 GC 导致内存位置变化。这些 Java 都没有提供。因此 Java 始终也无法满足系统编程领域的需求。

**最好的 boxing/unboxing 就是让程序员感受不到它们的存在,并自动产生最有效率,最低代价的代码。**而 Java 明显没做到这点,只能捏着鼻子用了。

扩展下,boxing/unboxing 本质上是由于“万物皆对象”落地时带来的一个有点恶,但不得不面对和解决的问题。但如果,不再要求“万物皆对象”,是否可以呢?

以 1.toString(); 这个代码为例子。假如不从 OO 角度出发,仅仅是作为一种函数的语法糖。当编译器碰到这段代码,就直接去找一个

toString(int self);

的函数是否可以呢?

进一步的,提供:

toString(char self);

toString(boolean self);

toString(float self);

……(char self);

是否可行呢?
其实这就是GO语言的思路,Java 有 String.valueOf(x)可以放各种基本类型。全局函数是没有的,因为这个没法界定所属的类,全局标识符也容易导致名字冲突,破坏现有的兼容性,高人一等的标识符做法在JAVA中是不妥的。但实际上 Kotlin 就有全局函数,至于是JAVA的思路更好还是GO与Kotlin这样的新兴语言思路更好,就仁者见仁智者见智了

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值