资讯动态

Java基本类型与包装类型:从自动装箱到NPE实战全解析

发布时间:2026/9/9 20:10:46 来源:尧图企业网站定制
Java面试里有一道题明明背得滚瓜烂熟但每次被问都能感觉到面试官在等你说出某个隐藏的坑。这道题就是包装类型和基本类型的区别是什么包装类型与基本类型一个是对象一个是普通值这两个概念从JDK 1.0到JDK 21一直在基础面试题里霸榜。你搜索Java面试题这套八股文必出现你搜索Java基础它也是绕不开的第一课。我今天不打算只给你背一套标准答案而是从字节码层面、缓存设计、项目里的NPE事故这几个角度把这道题彻底讲透。不管你是刚学Java的初学者还是准备跳槽的求职者又或者是带新人的老手这篇文章都能给你一些值得记录的东西。1. 先弄清楚两个主角基本类型和包装类型到底是什么1.1 八大基本类型与它们的对象版本Java的基本类型总共有八个它们都有对应的包装类型。这不是什么冷知识但很多人在面试时容易说的是八大基本类型却忘了它们的名字。这里把对应关系完整列一遍基本类型占用字节默认值包装类型缓存范围部分boolean未严格定义falseBooleantrue/falsebyte10Byte-128~127short20Short-128~127char2\u0000Character0~127int40Integer-128~127long80LLong-128~127float40.0fFloat无double80.0dDouble无从这张表能看出几个信息。首先除了 float 和 double 没有缓存机制其余六种都有对应的缓存范围。其次boolean 的包装类型就两个实例TRUE 和 FALSE这是常量谁都改不了。第三char 的缓存范围是 0 到 127正好覆盖 ASCII 码表。面试时要记住基本类型是值类型它们直接存储数值本身包装类型是引用类型它们是对象存储的是指向堆内存的地址。这句话是整个区别的核心。1.2 JDK 5之前没有自动装箱的日子很多初学者可能以为 int 和 Integer 之间的转换是天然无缝的写起来也确实是这么回事Integer num 100;直接赋值就能编译通过。但这是 JDK 5 引入自动装箱Autoboxing和自动拆箱Unboxing之后才有的便利。在 JDK 5 之前你只能这样写Integer num Integer.valueOf(100); int value num.intValue();是的手动装箱手动拆箱。那时候 Java 集合框架只能存 Object你想往 List 里放一个数字得先包装成 Integer 对象取出来还得手动调 intValue()。所以当时的代码看起来会比较啰嗦但也正因为啰嗦程序员对装箱拆箱是有感知的。现在语法糖铺好了反而坑多了。我在带新人时经常说一句话所谓自动装箱拆箱是编译器帮你写代码不是 JVM 帮你写代码。这个区别很重要它解释了后面几乎所有的坑。为什么重要因为编译器插入的代码不是无条件的它是按固定逻辑来处理的而这个逻辑在某些边界场景下会产生你意想不到的结果。2. 两者到底差在哪核心区别全景拆解2.1 存储位置栈、堆与值传递的三角关系这是最经典的考点之一也是一个经久不衰的面试问题基本类型的变量在栈上包装类型的对象在堆上。这句话本身没问题但不能说得太死特别是在 Java 8 之后JVM 引入了逃逸分析等技术可以让某些对象在栈上分配甚至做标量替换。所以严格来说包装类型的对象大部分情况在堆上但 JVM 如果判定对象没有逃逸也可能在栈上分配。不过面试时你可以先说标准答案再补充这个 JVM 优化细节会显得你了解更深。再往下挖一层还有值传递的问题。Java 只有值传递这一点是所有基础题的元规则。基本类型传递的是值的副本包装类型传递的是引用的副本。这句话经常被拿来当考题public static void main(String[] args) { int a 1; Integer b 1; change(a, b); System.out.println(a); // 1 System.out.println(b); // 1 } public static void change(int x, Integer y) { x 2; y 2; // 这里发生了什么 }如果只看结果两个输出都是 1好像没什么问题。但第二行y 2其实做了三件事拆箱拿到 1加 1 变成 2再装箱成一个新对象。这个方法内的 y 指向了新的对象和外面的 b 没有任何关系。换句话说修改引用类型的引用本身不会影响外部只有修改引用指向的对象内部状态才会影响外部。但 Integer 对象内部的值是 final 的不可变所以你什么都改不了。这里还延伸出一个概念包装类型是不可变类。Integer、Long、Boolean 这些类内部的值字段都是 final 修饰的一旦创建就不能改变。为了保证这一点很多操作实际上都会创建新对象而不是修改原对象。这也是为什么要极力避免在循环里反复做装箱拆箱频繁创建对象的开销是很可观的。2.2 默认值、可空性与泛型支持基本类型有默认值比如 int 默认是 0boolean 默认是 false。包装类型呢默认值是 null。这个差异在数据库映射和 POJO 设计里非常关键。举个实际例子一个用户表的积分字段数据库里允许 NULL也就是说这个用户可能还没有任何积分记录。如果你在 Java 实体类里用int points映射查询出来没有值int 就是 0你就分不清积分为0和没有积分记录。但如果你用Integer points查询出来没有值它就是 null语义清晰不会混淆。阿里巴巴的开发手册里明确写过这样的建议数据库的查询结果可能为 null 的字段在 POJO 类里必须使用包装类型。这一点不是凭空定的而是在大量线上事故里总结出来的教训。然后说泛型。Java 的泛型是在编译期通过类型擦除实现的底层必须是 Object 类型。这就导致一个结果泛型参数不能是基本类型。你写Listint完全编译不通过只能写ListInteger。同样的MapString, Integer可以MapString, int不可以。这也是为什么集合框架离不开包装类型的原因。那为什么 Java 不直接支持泛型基本类型呢说的是设计取舍。在 Java 语言早期泛型是通过 erasure 方式做的为了兼容已有代码JVM 层面不认识泛型概念只认 Object。如果要让基本类型也支持泛型就得在 JVM 层面做改动这会破坏二进制兼容性。后来 Project Valhalla 一直在做这件事提出了内联类Inline Class的概念目的之一就是让值语义和泛型能更好地结合。但这个项目迟迟没有进入主流所以现阶段你写 Java集合里存数字基本绕不开包装类型。2.3 性能对比你要为方便付出的代价很多人忽视性能差异因为在业务开发里一次查询就走几十毫秒包装类型那点开销根本不算什么。但在高频计算、大量循环、或批量数据处理的场景里装箱拆箱的代价会被放大。一次自动装箱的背后是调用 valueOf() 工厂方法或 new Integer()一次自动拆箱的背后是调用 intValue() 这类方法。操作本身不复杂但涉及对象分配对象分配意味着内存占用和 GC 压力。一个 int 占 4 字节一个 Integer 对象在开启压缩指针的 64 位 JVM 上大约占 16 字节注意这是未对齐堆大小实际会更多。差四倍的启动内存如果集合里存了一千万个 Integer这个差距就是几十 MB 到上百 MB 的量级。再叠加不可变性带来的问题任何数值变更都是新对象。如果循环一亿次累加Integer sum 0; for (int i 0; i 100000000; i) { sum i; // 每次循环拆箱 加法 装箱 }这段代码表面上看是一个累加实际上一亿次循环可能创建了上亿个临时 Integer 对象。用基本类型 int 写同一段逻辑时间可能差几倍甚至一个数量级。具体数字取决于 JVM 和垃圾回收器但方向是确定的包装类型慢而且慢得不止一点。当然这不是说包装类型不能用于计算而是说在性能敏感的内层循环里尽量用基本类型。现代 JVM 有一些优化手段比如逃逸分析和标量替换可能会把部分装箱后的对象优化掉但你永远不能依赖这种优化来做性能保障。该用基本类型的地方不要犹豫。3. 自动装箱与拆箱嘴上说优化底层全靠编译器3.1 从 javap 反编译看真实逻辑先写一段最简单代码public class BoxDemo { public static void main(String[] args) { Integer num 10; int value num; } }编译之后用 javap -c 反编译会看到字节码里出现了这样的调用0: bipush 10 2: invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 5: astore_1 6: aload_1 7: invokevirtual java/lang/Integer.intValue:()I 10: istore_2看到没有Integer num 10的那一行字节码调用的是Integer.valueOf(10)不是new Integer(10)。int value num的那一行字节码调用的是num.intValue()。这就是自动装箱和自动拆箱的本质编译器替你调用了对应的方法而具体调什么方法是由编译器规范规定的。这个细节在面试里可以往深了说既然自动装箱调用的是 valueOf那 valueOf 内部如果有缓存机制你的代码表现就会和new Integer完全不同。这就自然引出一个经典问题Java 为什么推荐用 valueOf 而不是 new Integer因为 valueOf 在设计上考虑到了高频小数值的复用而 new 是每次都生成新对象。3.2 Integer缓存-128到127背后的设计取舍Integer 类里有一个静态内部类 IntegerCache它会在类加载时创建 -128 到 127 之间的所有 Integer 实例并缓存在数组里。valueOf 方法在接收参数时先判断是否在这个范围内如果在直接返回缓存数组里的对象如果不在才 new 一个新的。源码大致长这样public static Integer valueOf(int i) { if (i IntegerCache.low i IntegerCache.high) return IntegerCache.cache[i (-IntegerCache.low)]; return new Integer(i); }你可能会问为什么偏偏是 -128 到 127这个范围其实不是 Java 规范定的而是 JVM 规范推荐的。IntegerCache.high 本身不是固定的它是可以通过 JVM 参数调整的比如-XX:AutoBoxCacheMax200就能把上限调大。之所以默认选择 127是因为这个范围内的整数值在程序中最常用比如小数组索引、状态码、循环计数器等缓存它们的性价比最高。那么不缓存超过某个范围的整数是不是设计失误其实不是这是性能和内存的权衡。如果把所有 int 都缓存内存会爆掉。如果只缓存个位数又没多大意义。127 这个阈值是基于大量统计数据选出来的经验值是一种比较合理的折中方案。从字节码和缓存源码可以看出自动装箱拆箱绝不是多么神秘的底层黑科技它就是很朴素的语法糖加一个缓存优化但正是这个朴素的机制在特定场景下制造了大量隐蔽的 bug。接下来聊一些更贴近实战的内容。4. 项目中踩过的坑拆箱NPE、 比较、集合里的空值4.1 三目运算符的类型提升陷阱这是一个非常有名的坑很多人第一次遇到时一脸懵。看这段代码MapString, Boolean map new HashMap(); Boolean flag map ! null ? map.get(key) : false;逻辑看起来很简单如果 map 不为空取 key 对应的 Boolean否则给一个 false。但是当 map 是空 HashMap 时map.get(key)返回的是 null这行代码会直接抛 NullPointerException。为什么很多人都以为是 map 为 null 导致的其实不是原因是三目运算符的两个分支类型不一致。map.get(key)的类型是 Booleanfalse的类型是基本类型 boolean。在三目运算符里如果两个分支一个是包装类型一个是基本类型编译器会强制把包装类型拆箱成基本类型再做统一的类型判断。于是map.get(key)返回的 null 被拆箱成 boolean拆箱就是调 booleanValue() 方法null 对象上调用方法NPE 就这么产生了。解决办法也很简单把 false 改成 Boolean.FALSE让两个分支都是包装类型就不会触发拆箱。别看这只是一个小改动在很多公司的代码 Review 里这种坑几乎年年都有。而且一旦出问题排查起来还比较费劲因为异常堆栈会指向那一行但很难一眼发现是三目运算符的类型提升导致的。4.2 Map.get 引发的大事故再看一个更隐蔽的场景。假设我们有一个 Redis 缓存存的是用户积分类型是 Integer。某一天代码写成了这样Integer points (Integer) redisTemplate.opsForValue().get(userId); if (points 100) { // 发放勋章 }如果 key 不存在points就是 null。第四行points 100会触发自动拆箱而在 null 上调用 intValue() 会直接抛 NPE。但这里的 NPE 不算最可怕的最可怕的是它不会发生在测试阶段因为测试环境通常有完整数据它会在线上某个用户首次访问时突然爆出来。这就是包装类型的经典问题自动拆箱前一定要做 null 判断。这个经验值多少钱无数个深夜定位问题的程序员都会告诉你至少值一个大版本发布的时间成本。我曾经接手过一个二手项目里面到处都是Map.get直接拿来比较的写法比如Integer count map.get(count); if (count 0) ...。这类代码在数据都有值的时候跑得欢一旦某个 key 缺失或值为 null就霹雳吧啦地报 NPE。后来我定了一条规矩凡是拿包装类型做比较运算一律先判断 null 或者用 Objects.equals 处理。这听着像废话但能落实下来的团队并不多。4.3 包装类型在循环和热点代码中的性能损耗前面已经提到性能差异这里展开一个实际案例。有一段时间我做报表导出功能需要把一个几十万行的数据表里的金额字段累加起来。最初的实现是用 BigDecimal 和 Double 做的想了想不影响精度就用 Double 包装类型结果导出一次耗时达到十几秒性能卡得很难看。排查后发现热点不是数据库查询而是累加过程反复装箱拆箱。把内部累加循环里的包装类型全部改成基本类型 double只在最后结果返回时转回 Double直接把耗时降到了两秒左右。这还只是把装箱带来的对象分配去掉了一部分如果数据量再往上走差距会更明显。所以在写性能敏感代码时我的原则很明确局部变量能用基本类型绝不用包装类型方法入参和返回值看场景但 POJO 字段遵循可空就包装必填就用基本类型。这里的排序是先保证语义正确不混淆 null 和 0再谈性能优化。大多数业务系统POJO 字段用包装类型完全没问题内层循环里的临时变量用基本类型就足够了。4.4 阿里巴巴开发规范里怎么约定很多团队在做代码规约时直接引用了阿里巴巴开发手册里的几条硬性规定这里也借花献佛列一下POJO 类属性必须使用包装类型因为数据库的查询结果可能是 null包装类型才能表达三态。RPC 方法的返回值和参数必须使用包装类型因为远程接口在异常情况下可能返回 null。所有的局部变量推荐使用基本类型因为局部变量不会存在 null 的问题用包装类型反而增加 NPE 风险和性能开销。这三条不是我拍的是阿里手册里白纸黑字明确过的。实际操作中很多团队也就是这么做的大家在简历上可以写上熟悉阿里开发规范面试官听到这句至少会觉得你有点工程意识。这里稍微展开一点为什么 POJO 用包装类型、局部变量用基本类型会有这么大的呼声核心原因就是默认值。POJO 里的对象大概率要和数据库、外部系统打交道这些外部源是不受 Java 默认值控制的null 是常态。而局部变量的生命周期只在你自己的方法里你完全可以用基本类型并保证它一定会被赋值不存在默认值干扰的问题。5. 面试追问实战从区别到你能撑几轮5.1 高频追问一100 和 100 比较200 和 200 比较结果各是什么这是最经典的 Integer 相等比较题Integer a 100; Integer b 100; System.out.println(a b); // true Integer c 200; Integer d 200; System.out.println(c d); // false第一段输出 true因为 100 在 Integer 缓存范围内a 和 b 引用的是同一个缓存对象。第二段输出 false因为 200 超出缓存范围valueOf 会 new 两个不同的对象 比较的是引用地址自然不相等。这个题考察的就是 Integer 缓存机制是否掌握。进阶一点如果这样写呢Integer e new Integer(100); Integer f new Integer(100); System.out.println(e f); // false答案是 false。因为 new 是强制创建新对象不管值在不在缓存范围内两个对象的地址都不一样。所以用 比较两个 Integer 对象永远是不靠谱的——除非你能确定两个引用指向同一个缓存对象但谁会拿业务逻辑去赌这个呢正确的姿势是使用 equals Integer e new Integer(100); Integer f new Integer(100); System.out.println(e.equals(f)); // trueequals 比较的是对象内部的值而不是引用地址所以只要 intValue 相等就是 true。再顺带提一句Integer 的 equals 实现是先判断目标是否为 Integer 类型再比较 intValue大致逻辑如下public boolean equals(Object obj) { if (obj instanceof Integer) { return value ((Integer) obj).intValue(); } return false; }5.2 高频追问二new Integer(1) 和 Integer.valueOf(1) 的区别这个问题看似简单其实能看出候选人有没有读过源码。核心区别有两个。第一个是对象创建方式不同new Integer(1)每次都会创建一个新对象Integer.valueOf(1)如果参数在缓存范围内直接返回缓存对象不在范围内才 new。第二个是使用场景不同new Integer 已经标记为 deprecated不推荐使用编译器做自动装箱时用的也是 valueOf 而不是 new。所以正常业务代码里你应该用 valueOf 或者直接依赖自动装箱不要手动 new Integer。再补一个点valueOf 的缓存对 Byte、Short、Long、Character 也生效但 Float 和 Double 没有缓存。为什么浮点数没有缓存因为浮点数的取值是连续的几乎不可能只集中在某几个值上缓存 128 个浮点数意义不大。这个逻辑也解释了为什么 Long 的缓存范围和 Integer 一样是 -128 到 127。5.3 高频追问三包装类型怎么安全地做比较这个问题也很常问因为很多新手在比较 Integer 时喜欢用 然后被缓存机制坑了一把。安全的做法分几种情况如果比较的是两个包装类型对象的值用a.equals(b)或者Objects.equals(a, b)。如果确定两边都不会为 null也可以用a.intValue() b.intValue()但前提是做过非空校验。如果是和常量比较可以直接写Objects.equals(value, 100)这样不会触发自动拆箱null 也能正确处理。这里特别提一下 Objects.equals 的优势它是 Java 7 开始提供的工具方法内部对两个参数都做了 null 判断所以Objects.equals(null, 100)返回 false不会抛 NPE。这也是我在业务代码里最常用的一种方式写起来干净不会因为漏判 null 引发事故。5.4 高频追问四数据库里的 int 字段映射到 Java 实体类应该用 int 还是 Integer这个问题我基本上逢新人必问。我给出的标准答案是数据库主键、外键、状态码等字段只要可能为 null 或有特殊含义一律用 Integer如果业务上保证一定不为 null比如自增主键、创建时间戳可以考虑用基本类型但为了规范和统一我建议全部走包装类型。有一个典型踩坑场景数据库里某个字段是int DEFAULT 0Java 实体类里用 Integer 映射查询结果不会为 null因为数据库会自动填 0。但如果你用 int 映射一旦数据库字段被改成可空或者老数据里出现了 null实体类属性就会变成 0你无法区分它是真的 0还是原本是 null。这种隐形的语义污染在报表统计和状态判断里非常容易引发错误的业务逻辑。6. 从源码看包装类型的其他细节6.1 为什么包装类型是不可变的打开 Integer 类的源码你会看到 value 字段是 final 的private final int value;这意味着对象一旦创建它内部的数值就无法修改。任何看起来修改了 Integer 变量的代码实际上都是创建了新的 Integer 对象再让引用指向新对象。例如Integer count 10; count;第一行是装箱创建一个值为 10 的 Integer 对象第二行会先拆箱得到 10加 1 得到 11再装箱创建一个值为 11 的新 Integer 对象然后把引用指向新对象。旧的值为 10 的对象失去了引用变成垃圾等待回收。这种不可变性有什么好处最直接的好处就是安全。因为 Integer 会被大量用作集合元素、缓存键、Map 的 key如果它是可变的那 HashMap 的 key 一旦被修改hashCode 就变了整个 Map 的数据就可能丢失。由于 Integer 不可变作为 key 非常安全。这个点在面试时可以主动说出来会显得你不只是在背八股而是在思考设计意图。6.2 compareTo 与 hashCode 的约定包装类型实现了 Comparable 接口可以直接用于排序和比较。但这里有一个大家容易忽略的点Integer.compareTo内部实际上是用compare(int x, int y)这个方法实现的而Integer.compare的逻辑是public static int compare(int x, int y) { return (x y) ? -1 : ((x y) ? 0 : 1); }这个实现规避了减法溢出的问题。如果你自己写return x - y当 x 是很大的正数、y 是很小的负数时减法结果会溢出导致排序结果错乱。所以如果你在写自定义比较器不要偷懒用减法直接用 Integer.compare 是最稳妥的。hashCode 也有一个细节Integer 的 hashCode 就是它内部的值本身。所以new Integer(100).hashCode()返回 100。这也是为什么 Integer 作为 HashMap 的 key 时表现很稳定只要值不变hashCode 就不会变。6.3 包装类型的 在算术运算中会自动拆箱前面说过的 陷阱如果有一边参与算术运算情况又会不一样。看这段代码Integer a 100; Integer b 100; System.out.println(a b); // true缓存 System.out.println(a b 0); // true右边拆箱成基本类型第二行里b 0是一个算术表达式算术运算要求两个操作数都是基本类型所以 b 会被自动拆箱成 int然后a 100也把 a 拆箱了最后比较的是两个基本类型的数值结果为 true。这种场景如果出现在复杂的业务逻辑里会让人困惑为什么有时 返回 true有时 false其实规则很简单只要触发算术运算就会拆箱只要两边都是对象引用就不拆箱。7. 常见问题与排查技巧实录7.1 NPE 堆栈里看不到业务方法怎么办自动拆箱导致的 NPE堆栈往往指向 JDK 内部的 Integer.intValue 方法而不是你的业务代码。比如Exception in thread main java.lang.NullPointerException at java.lang.Integer.intValue(Integer.java:xxx)这种堆栈对排查问题很不友好因为你根本不知道是哪个业务方法触发的。这里分享一个纯实操心得看到 intValue 相关的 NPE优先检查所有对包装类型做算术运算、比较运算、三目运算符的代码。特别是三目运算符的两边类型不一致时编译器强行拆箱最容易产生 null 调方法。用好 IDE 的 Find Usages把源码里所有可能出现拆箱的位置筛一遍每次都能快速锁定问题。7.2 Lombok 的 Data 也会埋雷很多时候实体类用 Lombok 的 Data 自动生成 getter/setter而 Lombok 生成的方法和手写方法一样遵循 Java 语义不会帮你做判空。比如Data public class User { private Integer age; }如果 age 是 null你调用user.getAge()拿到 null后续if (user.getAge() 18)就会 NPE。这个锅不能甩给 Lombok它只是生成普通方法。真正的问题还是调用方没有判空。遇到这种情况我的排查思路是先看字段在构造函数或 setter 里有没有被赋值再看使用处有没有判空。字段决定语义调用处决定安全。7.3 用 Optional 能不能彻底解决JDK 8 引入了 Optional确实能缓解一部分 null 问题但并不能根治。比如Integer value getXxx(); // 可能是 null Optional.ofNullable(value) .filter(v - v 100) .ifPresent(...);这段代码没问题因为 filter 里的 lambda 不会再让 value 自动拆箱它是在 Optional 内部处理。但如果写成OptionalInteger opt Optional.ofNullable(getXxx()); int v opt.orElse(0); // orElse 里的参数最好给基本类型常量orElse(0) 中的 0 是基本类型这里会发生自动装箱并不会产生 NPE因为 0 装箱成功了。真正要注意的是int v opt.get();这种写法如果 Optional 为空get() 直接抛 NoSuchElementException这不是 NPE但也得小心。所以不要把 Optional 当成万能解它只是把问题从字段层级转移到容器层级。7.4 面试回答模板一句话版本最后给你一个可以在面试里直接用的回答框架控制在 30 秒内说完但每一点都值得展开基本类型是值语义存储的是实际数据默认值由类型决定不支持泛型不能为 null包装类型是引用语义存储的是对象地址默认值为 null支持泛型和集合框架但会有封装、拆箱的性能开销和 NPE 风险。核心区别在于一个是值、一个是对象由此衍生出默认值、存储位置、比较方式、性能、泛型这五个维度的差异。这个模板的妙处在于它不只是背诵而是在背后给了你一个逻辑索引。面试官听到之后就能顺着追问比如那你讲讲 Integer 缓存那你遇到过拆箱 NPE 吗这时候你再把咱们前几章的内容讲出来这题就算答得漂亮了。8. 实际操作后的几点心得体会写到这里核心内容差不多讲完了。回想自己在 Java 这条路上踩过的坑有一点特别想说包装类型和基本类型的区别并不难背真正难的是在每一次写代码时想起这些区别。很多事故回头复盘时发现那完全不是一个高级错误就是简单地把 int 写成了 Integer或者忘记了判空又或者写循环时图方便用了包装类型的累加变量。我自己现在的习惯是实体类属性一律用包装类型遍历循环里涉及累加、统计的临时变量一律用基本类型方法返回和参数传递看能否为 null 来决定类型。遇到别人代码里出现Integer a xx; int b yy; a b这样的混合比较我会停下来先看清边界条件。这些规则听着特别朴素但能落实下去就真的能把线上 NPE 的概率降一个等级。如果你正在准备面试建议先把 Integer 缓存、equals 和 的比较逻辑弄明白再把本文章节 4 里的几个实战案例自己敲一遍。技术面时能讲出真实的踩坑经历比背十道八股文都管用。如果你想深入底层再去看看《深入理解Java虚拟机》里关于对象创建和栈上分配的内容。一步一步来这些东西迟早都是你的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价