本文目录导读:

- 后它变了?我是不是对“不可变”有什么误解?">案例一:“String 到底是不是不可变的?”**
Java中String类型的值不可变,为什么我s = "hello"; s = "world";后它变了?我是不是对“不可变”有什么误解? - 案例二:“Java 到底是值传递还是引用传递?”**
Java都是值传递,那为什么我传了一个List对象进方法,在里面清空后又添加了新元素,外面打印list也变了? - 案例三:“为什么Integer 127 比较相等,128 就不等了?”**
for (int i=0; i<200; i++) { Integer a=i; Integer b=i; System.out.println(a==b); }为什么i=127的时候是true,i=128的时候是false? - 案例四:“HashMap中的死循环(Java 7及以前)”**
我用了一个单例的HashMap作为缓存,在高并发下CPU飙升100%,怎么回事? - 案例五:“你听过‘面向接口编程’吗?那我这个接口怎么new不了?”**
List list = new ArrayList(); 这不是面向接口吗?那为什么我写的接口MyInterface mi = new MyInterface();报错了? - 案例六:“try-catch-finally 中的 return 问题”**
finally块中return了,catch中的return还会执行吗?finally中return会导致try中的return失效吗? - 如果你在论坛发问,建议遵循的格式:
这里为你整理了一些Java技术论坛(如Stack Overflow、V2EX、Reddit、知乎、开源中国、JavaEye等)中非常经典、高频且具有代表性的“问答案例”。
这些案例通常是因为概念混淆、底层机制不清晰、或者对语法糖的误解而导致的。
“String 到底是不是不可变的?”** Java中String类型的值不可变,为什么我 s = "hello"; s = "world";后它变了?我是不是对“不可变”有什么误解?
典型回答 / 分析: 这是一个经典的入门级概念混淆。
- 误解点: 提问者认为
s本身是“String对象”。 - 正解:
s是一个引用(Reference),存放在栈内存中。"hello"和"world"是两个不同的 String对象,存放在堆内存的字符串常量池中。- 执行
s = "world";,并不是把"hello"这个对象改成了"world",而是把s这个指针指向了另一个对象"world"。 - 不可变性指的是:String对象内部的
char[] value数组被final修饰,并且没有提供任何修改内部字符的方法,你无法直接操作"hello"[0] = 'H'。
- 类比: 你拿着写着“小明”的身份证,后来你换了张写着“小红”的身份证,身份证本身(String对象)的内容没变,是你手里的卡(引用)变了。
“Java 到底是值传递还是引用传递?”** Java都是值传递,那为什么我传了一个List对象进方法,在里面清空后又添加了新元素,外面打印list也变了?
典型回答 / 分析: Java官方唯一承认的就是 值传递(Pass-by-Value)。
- 误解点: 当传递一个对象时,提问者认为传递的是对象本身(引用传递)。
- 正解:
- 传递的确实是“值”,但这个值是一个“引用的副本”。
- 在方法
void foo(List<Integer> list)中,参数list是外部list变量的一个 拷贝,这两个变量指向的是堆内存中的同一个对象。 - 当你通过这个拷贝
list.add(100)时,操作的是那个真实的对象,外部变量再去看那个对象,自然发生了变化。 - 如何证明是值传递? 如果在方法内部执行
list = new ArrayList<>();(让形参指向新对象),外部的list引用是不会指向这个新对象的,如果是“引用传递”,外部引用也会跟着变。
- 一句话总结: 你拿到的是对象门牌的影印件(值传递),但你和门外的人都通过这份影印件找到了同一个房间,所以你们看到房间里的家具是一样的,如果你把影印件换成另一块门牌的影印件,外面的人手里的原版门牌并不会变。
“为什么Integer 127 比较相等,128 就不等了?”** for (int i=0; i<200; i++) { Integer a=i; Integer b=i; System.out.println(a==b); } 为什么i=127的时候是true,i=128的时候是false?
典型回答 / 分析: 这是自动装箱和Integer缓存机制的问题。
- 背景:
Integer a = i;实际上执行了Integer.valueOf(i)。 - 源码机制:
valueOf方法内部有缓存池(默认 -128 ~ 127)。i在这个范围内,直接返回缓存池中已有的同一个Integer对象。- 如果超出范围,则会
new Integer(i)创建一个新的对象。
- 现象解释:
i = 127:a和b指向了缓存池中同一个Integer对象, 比较引用地址,为true。i = 128:a和b指向了堆中两个不同的Integer对象, 比较引用地址,为false。
- 最佳实践: 包装类对象比较值是否相等,必须使用
.equals()方法,而不是 。a.equals(b)在任何情况下都是true。
“HashMap中的死循环(Java 7及以前)”** 我用了一个单例的HashMap作为缓存,在高并发下CPU飙升100%,怎么回事?
典型回答 / 分析: 这是一个经典的并发问题。
- 背景: 前提是 Java 7 及之前版本(JDK7)。
- 原因:
HashMap在扩容(resize())时,采用的是头插法,在单线程下没问题,但在多线程并发插入导致扩容时,多个线程同时操作链表,容易产生环形链表。 - 现象:
- 当后续线程执行
get()操作,遍历到链表节点时,会陷入死循环(因为链表不再是单向的,形成了环)。 - CPU 使用率飙升至 100%。
- 当后续线程执行
- 解决方案(从老到新):
- Java 8+: 改用了尾插法 + 红黑树,从算法上解决了死循环问题(但仍存在数据丢失等问题,依然线程不安全)。
- 标准答案: 使用
ConcurrentHashMap。
“你听过‘面向接口编程’吗?那我这个接口怎么new不了?”** List list = new ArrayList(); 这不是面向接口吗?那为什么我写的接口 MyInterface mi = new MyInterface(); 报错了?
典型回答 / 分析: 语法与设计理念的混淆。
- 误解点: 提问者把“面向接口编程(引用指向接口类型)”和“接口可以实例化”混为一谈。
- 正解:
List list = new ArrayList();是正确的。list是接口类型(面向接口),但new ArrayList()是实例化了一个具体的实现类。new MyInterface()是试图直接实例化一个接口,接口没有构造方法,也没有实现体,Java语法不允许。- 匿名内部类/ Lambda 的“假象”:
MyInterface mi = new MyInterface() { public void doSomething() { ... } };这是合法的,但这里new的不是一个接口,而是一个匿名内部类,这个类实现了该接口。new操作符后面跟的是这个匿名类的构造器。- Lambda表达式同理,它是函数式接口的快捷实现。
- 引用可以用接口类型,但对象必须是某个具体的类(或匿名类)的实例。
“try-catch-finally 中的 return 问题”** finally块中return了,catch中的return还会执行吗?finally中return会导致try中的return失效吗?
典型回答 / 分析: 这是一个非常经典的面试/笔试陷阱题。
- 规则:
finally块总会被执行(除非JVM退出)。finally块中有return语句,它会覆盖掉try或catch块中的return。
- 细节(关于返回值):
try中有return x;,finally中对x进行了修改但 没有 return,finally中修改的是值的副本(基本类型)或者引用指向的对象内容(引用类型),最终返回的是try中准备返回的那个值(或引用)的“快照”。- 绝对不要在
finally块中写return语句,它会吞掉异常,并且让代码逻辑变得难以理解和维护。
- 示例:
public static int test() { int i = 1; try { return i; // 保存i的副本(1),准备返回 } finally { i = 2; // 修改的是i本身,不是副本 // 如果这里写 return i; 则会返回2 } } // 结果是1
如果你在论坛发问,建议遵循的格式:
-
[Java 8] HashMap在扩容时为什么是线程不安全的?(带版本和环境信息) - 问题描述: 清楚描述你想实现什么,以及出现了什么现象(报错、结果不对、性能差)。
- 提供代码: 不要截图(因为别人没法复制),用代码块格式粘贴最小可复现的代码片段。
- 提供报错信息: 完整的堆栈日志(Stack Trace)。
- 尝试过的: 你搜了哪些关键词,尝试了什么解决方案,结果如何。
如果你有具体的Java问题,也欢迎直接在这里提问。