
刚学 Java 的时候很多人都会有点别扭。既然已然存在 int、long这个样子了, 为何还要再度出现一套 、Long、、呢?看着像重复设计写多了还容易出事。比如到此许多人学到之后呢竟然会将包装类视作Java所遗留下来的历史包袱。这话只说对了一半。它确实带着很强的历史痕迹但它也不是无缘无故存在的。说到底, 包装类是Java搭建的一座桥, 这座桥位于两套世界之间, 一边是基本类型, 其追求效率, 另一边是面向对象体系, 该体系内万物讲对象。先说最根上的原因int 不是对象Java 里有一组非常特殊的类型int long double float short byte char boolean这些叫基本类型。和普通类不同的是, 它们没法被调方法, 如果是int, 不能够进行这种操作。并且, 它们不存在继承某个父类这么一回事。它们有着极具直接性的存在价值, 分别是更轻盈, 能够运行时间提升速度到更快, 占用内存也更加的节省。比如一个 int本质上就是一个数值但存在这样一个情况, 它涉及对象, 此对象带着对象头, 还有引用产生的意义。另外, 它的出现很有可能把那些装箱、拆箱以及缓存这些额外的机制被牵扯进来。要是 Java 将全部数字都设定为对象, 那么语言会呈现出“更统一”的状况, 然而运行成本会大幅提高很多。在 Java 诞生的那个时候呀, 特别是, 内存相较于如今更为敏感, 性能也是如此, 并且 JVM 并不具备现阶段这么激进的优化能力, 就是这样。因此, Java作出了保留基本类型的选择, 这一行为本身是不存在问题的, 甚至于还能够表述为是极为务实的。问题在于Java 又偏偏是一门典型的面向对象语言。而面向对象世界里很多地方只认对象。这时候裂缝就出来了。包装类本质上是给基本类型补一个“对象入口”要是仅有int, 不存在, 那么Java的诸多基础能力将会径直断掉一部分。最常见的就是集合。List list new ArrayList(); list.add(1);这里你能写 却不能写List list new ArrayList();并非是集合存心要为难你, 然而泛型这样的一套内容, 原本就是在围绕着引用类型来进行设计构建的。int 不是对象放不进去。于是 Java 需要一个“对象版的 int”这就是 。同理你可以把包装类理解成让基本类型拥有一张进入对象世界的门票。没有这张门票很多 API 根本没法玩。为什么说它不是可有可无包装类真正解决的不只是“集合能不能放”。它解决的是这几类现实问题。1. 集合、泛型、反射、框架参数传递都更偏对象世界Java 大量基础设施都是按对象设计的。例如集合, 泛型, 反射, 注解处理, 序列化, 诸多 ORM 和 Web 框架的参数绑定, 本质上都更倾向于与对象进行交互, 是这样的。不妨试着去想象一番, 要是不存在包装类, 接下来的这些场景都会变得特别别扭:将整套类库改成两套平行体系, 这对于Java来说是不可能的, 仅仅是为了几个基本类型, 就去做这样的改变, 绝无可能实现。所以最自然的做法就是给它们准备“对象形态”。2. 基本类型不能表达“没有值”包装类可以这个问题在业务代码里特别常见。int age 0;这里的 0 到底是什么意思是年龄真的为 0还是“暂时不知道”在很多业务场景里这两件事完全不是一回事。而包装类可以表达这个差异Integer age null;这表示“没有值”。有一种能力, 在数据库字段可空之处, 在前端表单未填写之时, 在接口参数非必填之地, 到处都对其有很大的依靠。所以, 包装类并非仅仅是“将数字转化为对象”这般简单, 它还顺便肩负起了一项颇为实际的职责, 那便是: 去表达 语义。3. 包装类顺便承载了很多工具能力基本类型自己没有方法但包装类有。Integer.parseInt(123); Integer.valueOf(10); Double.compare(1.2, 2.3); Boolean.TRUE这让 Java 不需要额外再发明一堆零散工具函数。针对某一种类别的通常具备的能力, 径直往包装类别上进行挂载, 这般既做到了顺其自然, 同时对记忆而言也颇为简便, 是这样的情形。所以包装类其实还承担了“类型工具箱”的角色。包装类好用但它从来不是“纯值”诸多在线上出现的差错之处, 并非是因为包装类型有没有发挥用处而造成的, 反而是当人们将它视作纯粹的数字情况时才产生的。它不是。看起来像数字实际上是对象。既然身为对象, 那它便会带来对象世界之中的那一系列独一无二的特性, 这些特性包含引用, 包含null, 包含比较规则, 包含缓存, 包含装箱拆箱。此同样是众多Java初学者切实开启领会“值”以及“对象并非同一回事”之处。一个最经典的坑null 拆箱Integer x null; int y x; // NPE很多人第一次看到都会愣一下。x 不是整数吗怎么会空指针因为这里发生了自动拆箱。编译器会帮你把它变成类似下面这样int y x.intValue();而 x 是 null当然直接炸。也就是说包装类能表达“没有值”这是它的优势但一旦你又把它当成基本类型来用这个优势会立刻变成风险。线上最容易出现的形式通常不是上面这段而是这种Integer status null; if (status 0) { ... }看上去像普通比较实际上已经在偷偷拆箱了。另一个高频坑 不比较值比较的是不是同一个对象Integer a 128; Integer b 128; System.out.println(a b); // false System.out.println(a.equals(b)); // true原因不复杂但麻烦的是 还有缓存池。Integer a 127; Integer b 127; System.out.println(a b); // true再次变为成真状态, 是由于处于负一百二十八至一百二十七这段范围, 一般情况下会进行缓存复用。这就很容易把人带沟里你以为 能用结果只是刚好踩在缓存区间里。所以只要是比较包装类的值规矩很简单Objects.equals(a, b)或者至少a.equals(b)别拿 去赌运气。与此同时这同样是致使, a等于128, b等于128, 然而a等于b却并非是true这般情况出现的缘由。性能上包装类确实更重这件事也没必要替它洗。就是比 int 重。不但并非仅仅是“概念里更为复杂”这一情形, 而且是实实在在地比之前更占用内存空间, 还比较轻易地就会触动引发额外的分配操作, 并且也相对更易于导致让CPU缓存的局部性状况变得更差些。比如你做大量数值存储时再加上自动装箱和拆箱循环里频繁来回转换成本也不算小。所以工程上要有一个很朴素的判断包装类不是不能用而是别把它用在它不擅长的位置上。说到底包装类是 Java 的一次折中Java 从来都不是“极端纯粹”的语言。它没有像某些语言那样干脆所有东西都做成对象也没有像更底层的语言那样完全只围绕裸值和内存布局思考。它的路子一直是折中要点一点性能, 也要点一点统一性, 优先实用主义, 把理论上的绝对整洁往后放置。包装类就是这种折中的典型产物。代价也很明显语言会多一层理解成本开发者也得多记几条规则。可是倘若并未拥有这一套设计, 那么当今Java中很多你认为理所当然的代码, 实际上都是没法编写出来的。真正麻烦的从来不是包装类本身而是“忘了它是对象”包装类极易使人陷入误区的所在之处, 并非因其具备复杂性, 而是因其外貌与基本类型极为相像。你看到脑子里常常浮现的是“数字”可 JVM 看到的是对象。一边是值语义直觉一边是对象语义现实。很多 bug就是在这条缝里长出来的。所以我自己更愿意把包装类理解成一句话它并非是 int 的替身, 然而却是存在于对象世界当中的 int 的代理人。理解到这里很多现象就顺了Java 并不是无缘无故多造了一套类型。它只是把“快”和“通用”这两件事硬生生接到了一起。而包装类就是那道接缝。为达成弥补这些缺陷以及自身对于高性能的追求, 进而便产生了项目。Java 27存在出现项目预览的可能性在下一篇之中我将会紧接着撰写未来篇, 去探究其解决这些历史遗留问题的方式手段。假如你身为Java开发者, 或者是后端开发者, 又或者是全栈开发者, 那就关注我一块儿前行。