[{"data":1,"prerenderedAt":533},["ShallowReactive",2],{"\u002Fjava-interview\u002Fjava-basics":3,"java-interview-search:\u002Fjava-interview\u002Fjava-basics":488},{"id":4,"title":5,"body":6,"category":474,"cover":475,"date":476,"description":477,"extension":478,"group":479,"meta":480,"navigation":481,"order":482,"path":483,"seo":484,"sitemap":485,"stem":486,"__hash__":487},"javaInterview\u002Fjava-interview\u002Fjava-basics.md","Java 基础：对象、字符串与泛型",{"type":7,"value":8,"toc":457},"minimark",[9,13,27,46,70,89,96,109,115,132,140,151,164,179,183,209,228,234,238,241,251,254,258,268,279,303,321,325,328,340,343,347,370,373,383,386,405,412,415],[10,11,12],"p",{},"基础题最容易答成背诵。下面每题都按「结论 → 原理 → 追问」组织，重点是把 JLS 里的规则和日常踩过的坑对应起来。",[14,15,17,18,22,23,26],"h2",{"id":16},"_1-和-equals-有什么区别","1. ",[19,20,21],"code",{},"=="," 和 ",[19,24,25],{},"equals"," 有什么区别？",[10,28,29,31,32,34,35,38,39,41,42,45],{},[19,30,21],{}," 比较值本身：基本类型比较数值，引用类型比较是否指向同一个对象。",[19,33,25],{}," 是 ",[19,36,37],{},"Object"," 的方法，默认实现就是 ",[19,40,21],{},"，但 ",[19,43,44],{},"String","、包装类、集合等重写了它，改成比较内容。",[10,47,48,49,51,52,55,56,58,59,61,62,65,66,69],{},"重写 ",[19,50,25],{}," 必须同时重写 ",[19,53,54],{},"hashCode","，否则两个相等的对象可能算进不同的桶。契约是：",[19,57,25],{}," 为真则 ",[19,60,54],{}," 必须相等；反过来不要求。违反契约会让 ",[19,63,64],{},"HashMap","、",[19,67,68],{},"HashSet"," 出现「放进去却查不到」的诡异现象。",[10,71,72,73,76,77,80,81,84,85,88],{},"追问常见于浮点数：",[19,74,75],{},"0.1 + 0.2 == 0.3"," 为 ",[19,78,79],{},"false","，因为二进制无法精确表示十进制小数。金额应使用 ",[19,82,83],{},"BigDecimal","，并且用字符串构造，否则从 ",[19,86,87],{},"double"," 转入时误差已经带入。",[14,90,92,93,95],{"id":91},"_2-为什么-string-设计成不可变三种字符串怎么选","2. 为什么 ",[19,94,44],{}," 设计成不可变？三种字符串怎么选？",[10,97,98,100,101,104,105,108],{},[19,99,44],{}," 类是 ",[19,102,103],{},"final"," 的，内部字符存储在私有字段中（JDK 9 起改为 ",[19,106,107],{},"byte[]"," 加编码标记，以压缩拉丁字符占用），所有看似修改的方法都返回新对象。",[10,110,111,112,114],{},"不可变换来四件事：可以安全共享、可以缓存哈希值（所以适合当 ",[19,113,64],{}," 的 key）、天然线程安全、传参后不会被调用方篡改。代价是频繁拼接会产生大量临时对象。",[10,116,117,118,120,121,124,125,128,129,131],{},"选型上：常量或少量拼接用 ",[19,119,44],{},"；循环内拼接用 ",[19,122,123],{},"StringBuilder","；确实需要多线程共享同一个可变缓冲区时才用 ",[19,126,127],{},"StringBuffer","。绝大多数场景里，把 ",[19,130,123],{}," 限制在方法内部比用同步版本更有效。",[14,133,135,136,139],{"id":134},"_3-new-stringabc-会创建几个对象","3. ",[19,137,138],{},"new String(\"abc\")"," 会创建几个对象？",[10,141,142,143,146,147,150],{},"分情况：若常量池中还没有 ",[19,144,145],{},"\"abc\"","，类加载解析字面量时先创建池中对象，执行 ",[19,148,149],{},"new"," 时再在堆上创建实例，共两个；若池中已有，则只创建堆上那一个。面试时把两种口径讲清楚，比背「两个」更稳妥。",[10,152,153,156,157,76,160,163],{},[19,154,155],{},"intern()"," 的语义是：池中有相等字符串就返回池中引用，否则把当前字符串的引用登记进去并返回。JDK 7 起常量池移到堆中，所以 ",[19,158,159],{},"new String(\"a\").intern() == \"a\"",[19,161,162],{},"true","。",[10,165,166,167,170,171,174,175,178],{},"编译期能折叠的只有字面量拼接，例如 ",[19,168,169],{},"\"a\" + \"b\""," 会直接变成 ",[19,172,173],{},"\"ab\"","。而 ",[19,176,177],{},"new String(\"a\") + new String(\"b\")"," 是运行期拼接，结果不会自动进入常量池。",[14,180,182],{"id":181},"_4-包装类的缓存与自动拆箱有哪些坑","4. 包装类的缓存与自动拆箱有哪些坑？",[10,184,185,186,189,190,193,194,197,198,200,201,203,204,206,207,163],{},"自动装箱调用 ",[19,187,188],{},"Integer.valueOf","，默认缓存 -128 到 127，可用 ",[19,191,192],{},"-XX:AutoBoxCacheMax"," 调大上界。因此两个值为 127 的 ",[19,195,196],{},"Integer"," 用 ",[19,199,21],{}," 比较为 ",[19,202,162],{},"，128 则为 ",[19,205,79],{},"；比较值一律用 ",[19,208,25],{},[10,210,211,212,215,216,219,220,223,224,227],{},"最常见的坑是空指针：",[19,213,214],{},"Integer x = null; x == 1"," 会拆箱，直接抛 NPE。",[19,217,218],{},"Map.get()"," 返回 ",[19,221,222],{},"null"," 后赋给 ",[19,225,226],{},"int","、三目表达式两边类型不一致导致整体提升，也都会引发同类问题。",[10,229,230,233],{},[19,231,232],{},"Long"," 的缓存范围固定为 -128 到 127，不受上述参数影响。金额、订单号这类业务标识不要依赖缓存行为，统一用值比较或明确的基本类型。",[14,235,237],{"id":236},"_5-java-是值传递还是引用传递","5. Java 是值传递还是引用传递？",[10,239,240],{},"Java 只有值传递。基本类型传的是数值副本；引用类型传的是引用的副本，也就是对象地址的拷贝。",[10,242,243,244,246,247,250],{},"所以方法内可以通过这个副本修改对象内容，但把形参重新指向新对象，不会影响调用方的引用。典型例子是：给 ",[19,245,123],{}," 追加内容能生效，而在方法里 ",[19,248,249],{},"sb = new StringBuilder()"," 之后的操作与外部无关。",[10,252,253],{},"追问常考「为什么交换两个对象不生效」：交换的只是两个引用副本。交换数组元素则能生效，因为修改的是数组对象内部的数据。",[14,255,257],{"id":256},"_6-泛型擦除带来了哪些限制pecs-怎么理解","6. 泛型擦除带来了哪些限制？PECS 怎么理解？",[10,259,260,261,263,264,267],{},"Java 泛型是编译期特性，编译后类型参数被擦除到上界（无界时为 ",[19,262,37],{},"），运行时拿不到 ",[19,265,266],{},"List\u003CString>"," 的具体类型实参。",[10,269,270,271,274,275,278],{},"由此产生几条限制：不能 ",[19,272,273],{},"new T()","、不能直接创建泛型数组、不能写 ",[19,276,277],{},"instanceof List\u003CString>","、静态成员不能引用类上的类型参数、基本类型不能作为类型实参。为了维持多态，编译器会生成桥方法。",[10,280,281,282,285,286,289,290,163,292,295,296,299,300,302],{},"通配符遵循 PECS：生产者用 ",[19,283,284],{},"? extends T","，只能读不能写；消费者用 ",[19,287,288],{},"? super T","，可以写但读出来只能是 ",[19,291,37],{},[19,293,294],{},"List\u003C?>"," 不能 ",[19,297,298],{},"add"," 任何非 ",[19,301,222],{}," 元素。",[10,304,305,306,309,310,313,314,317,318,163],{},"还要区分：",[19,307,308],{},"List\u003CInteger>"," 不能赋给 ",[19,311,312],{},"List\u003CNumber>","，这不是擦除造成的，而是泛型不协变。数组是协变的，所以 ",[19,315,316],{},"Object[] arr = new String[1]"," 能通过编译，但写入其他类型会在运行期抛 ",[19,319,320],{},"ArrayStoreException",[14,322,324],{"id":323},"_7-重载和重写有什么区别多态靠什么实现","7. 重载和重写有什么区别？多态靠什么实现？",[10,326,327],{},"重载在编译期按静态类型和参数列表选定，返回值不同不构成重载；重写在运行期按对象实际类型分派。重写要求签名相同、返回类型协变、访问权限不能收窄、不能抛出更宽的受检异常。",[10,329,330,331,65,334,65,337,339],{},"多态依赖对象头里的类型元数据指针和方法表：调用虚方法时按实际类型找到实现。",[19,332,333],{},"private",[19,335,336],{},"static",[19,338,103],{}," 方法不参与动态分派。",[10,341,342],{},"两个容易忽略的点：一是在构造器里调用可重写方法，子类字段尚未初始化，可能读到默认值；二是重载在编译期就固定，父类静态类型看不到子类新增的重载版本。",[14,344,346],{"id":345},"_8-从-java-8-到-21哪些语言特性值得在面试里讲","8. 从 Java 8 到 21，哪些语言特性值得在面试里讲？",[10,348,349,350,353,354,357,358,361,362,365,366,369],{},"JDK 8 带来 Lambda、Stream、",[19,351,352],{},"Optional","、接口默认方法和新的日期时间 API。JDK 10 的 ",[19,355,356],{},"var"," 只做局部变量类型推断，不改变类型系统。JDK 11 标准化了 ",[19,359,360],{},"HttpClient","。JDK 17 是主流 LTS，包含 ",[19,363,364],{},"record","、密封类、",[19,367,368],{},"instanceof"," 模式匹配、文本块和 switch 表达式。",[10,371,372],{},"JDK 21 值得关注 switch 模式匹配（含 record 解构）、虚拟线程、分代 ZGC 和有序集合接口。",[10,374,375,376,378,379,382],{},"回答时最好落到场景：",[19,377,364],{}," 适合 DTO 和值对象，不适合需要可变状态或依赖无参构造的框架实体；虚拟线程适合大量阻塞在 IO 上的任务，不适合 CPU 密集任务，也不适合长时间持有 ",[19,380,381],{},"synchronized"," 的临界区——JDK 21 下会钉住载体线程，JDK 24 才改善这一限制。",[14,384,385],{"id":385},"易错点与自测",[387,388,389,393,402],"ul",{},[390,391,392],"li",{},"不要只背「String 不可变所以线程安全」，要能说出共享、哈希缓存、常量池三个具体收益。",[390,394,395,396,398,399,401],{},"包装类比较、",[19,397,222],{}," 拆箱、",[19,400,83],{}," 构造方式，是笔试里最高频的失分点。",[390,403,404],{},"泛型擦除不等于「运行时完全没有泛型信息」，声明处的信息仍可通过反射读到。",[10,406,407,408,411],{},"自测：① 解释 ",[19,409,410],{},"Integer a = 128; Integer b = 128; a == b"," 的结果；② 说明为什么泛型不能协变而数组可以；③ 说出 JDK 21 虚拟线程不适合哪两类任务。",[14,413,414],{"id":414},"参考资料",[387,416,417,427,441,449],{},[390,418,419,426],{},[420,421,425],"a",{"href":422,"rel":423},"https:\u002F\u002Fdocs.oracle.com\u002Fjavase\u002Fspecs\u002Fjls\u002Fse21\u002Fhtml\u002Findex.html",[424],"nofollow","JLS SE 21：类型、转换与泛型"," — 泛型擦除、装箱转换的规范定义",[390,428,429,434,435,437,438,440],{},[420,430,433],{"href":431,"rel":432},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F21\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Flang\u002FObject.html",[424],"java.lang.Object API"," — ",[19,436,25],{}," 与 ",[19,439,54],{}," 的契约原文",[390,442,443,448],{},[420,444,447],{"href":445,"rel":446},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F444",[424],"JEP 444：Virtual Threads"," — 虚拟线程的适用场景与钉住问题",[390,450,451,456],{},[420,452,455],{"href":453,"rel":454},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F441",[424],"JEP 441：Pattern Matching for switch"," — switch 模式匹配的正式语义",{"title":458,"searchDepth":459,"depth":459,"links":460},"",2,[461,463,465,467,468,469,470,471,472,473],{"id":16,"depth":459,"text":462},"1. == 和 equals 有什么区别？",{"id":91,"depth":459,"text":464},"2. 为什么 String 设计成不可变？三种字符串怎么选？",{"id":134,"depth":459,"text":466},"3. new String(\"abc\") 会创建几个对象？",{"id":181,"depth":459,"text":182},{"id":236,"depth":459,"text":237},{"id":256,"depth":459,"text":257},{"id":323,"depth":459,"text":324},{"id":345,"depth":459,"text":346},{"id":385,"depth":459,"text":385},{"id":414,"depth":459,"text":414},"java-interview",null,"2026-10-24","从 == 与 equals、字符串不可变、包装类缓存、值传递到泛型擦除，梳理 Java 基础题的高频追问和版本边界","md","Java 核心",{},true,"10","\u002Fjava-interview\u002Fjava-basics",{"title":5,"description":477},{"loc":483},"java-interview\u002Fjava-basics","Kom7fnDlE7zg-nc90O-2ZtAhpAmc60c451S7-8MzH5k",[489,493,497,501,505,509,513,517,521,525,529],{"id":483,"title":5,"titles":490,"content":491,"level":492},[],"从 == 与 equals、字符串不可变、包装类缓存、值传递到泛型擦除，梳理 Java 基础题的高频追问和版本边界 基础题最容易答成背诵。下面每题都按「结论 → 原理 → 追问」组织，重点是把 JLS 里的规则和日常踩过的坑对应起来。",1,{"id":494,"title":462,"titles":495,"content":496,"level":459},"\u002Fjava-interview\u002Fjava-basics#_1-和-equals-有什么区别",[5],"== 比较值本身：基本类型比较数值，引用类型比较是否指向同一个对象。equals 是 Object 的方法，默认实现就是 ==，但 String、包装类、集合等重写了它，改成比较内容。 重写 equals 必须同时重写 hashCode，否则两个相等的对象可能算进不同的桶。契约是：equals 为真则 hashCode 必须相等；反过来不要求。违反契约会让 HashMap、HashSet 出现「放进去却查不到」的诡异现象。 追问常见于浮点数：0.1 + 0.2 == 0.3 为 false，因为二进制无法精确表示十进制小数。金额应使用 BigDecimal，并且用字符串构造，否则从 double 转入时误差已经带入。",{"id":498,"title":464,"titles":499,"content":500,"level":459},"\u002Fjava-interview\u002Fjava-basics#_2-为什么-string-设计成不可变三种字符串怎么选",[5],"String 类是 final 的，内部字符存储在私有字段中（JDK 9 起改为 byte[] 加编码标记，以压缩拉丁字符占用），所有看似修改的方法都返回新对象。 不可变换来四件事：可以安全共享、可以缓存哈希值（所以适合当 HashMap 的 key）、天然线程安全、传参后不会被调用方篡改。代价是频繁拼接会产生大量临时对象。 选型上：常量或少量拼接用 String；循环内拼接用 StringBuilder；确实需要多线程共享同一个可变缓冲区时才用 StringBuffer。绝大多数场景里，把 StringBuilder 限制在方法内部比用同步版本更有效。",{"id":502,"title":466,"titles":503,"content":504,"level":459},"\u002Fjava-interview\u002Fjava-basics#_3-new-stringabc-会创建几个对象",[5],"分情况：若常量池中还没有 \"abc\"，类加载解析字面量时先创建池中对象，执行 new 时再在堆上创建实例，共两个；若池中已有，则只创建堆上那一个。面试时把两种口径讲清楚，比背「两个」更稳妥。 intern() 的语义是：池中有相等字符串就返回池中引用，否则把当前字符串的引用登记进去并返回。JDK 7 起常量池移到堆中，所以 new String(\"a\").intern() == \"a\" 为 true。 编译期能折叠的只有字面量拼接，例如 \"a\" + \"b\" 会直接变成 \"ab\"。而 new String(\"a\") + new String(\"b\") 是运行期拼接，结果不会自动进入常量池。",{"id":506,"title":182,"titles":507,"content":508,"level":459},"\u002Fjava-interview\u002Fjava-basics#_4-包装类的缓存与自动拆箱有哪些坑",[5],"自动装箱调用 Integer.valueOf，默认缓存 -128 到 127，可用 -XX:AutoBoxCacheMax 调大上界。因此两个值为 127 的 Integer 用 == 比较为 true，128 则为 false；比较值一律用 equals。 最常见的坑是空指针：Integer x = null; x == 1 会拆箱，直接抛 NPE。Map.get() 返回 null 后赋给 int、三目表达式两边类型不一致导致整体提升，也都会引发同类问题。 Long 的缓存范围固定为 -128 到 127，不受上述参数影响。金额、订单号这类业务标识不要依赖缓存行为，统一用值比较或明确的基本类型。",{"id":510,"title":237,"titles":511,"content":512,"level":459},"\u002Fjava-interview\u002Fjava-basics#_5-java-是值传递还是引用传递",[5],"Java 只有值传递。基本类型传的是数值副本；引用类型传的是引用的副本，也就是对象地址的拷贝。 所以方法内可以通过这个副本修改对象内容，但把形参重新指向新对象，不会影响调用方的引用。典型例子是：给 StringBuilder 追加内容能生效，而在方法里 sb = new StringBuilder() 之后的操作与外部无关。 追问常考「为什么交换两个对象不生效」：交换的只是两个引用副本。交换数组元素则能生效，因为修改的是数组对象内部的数据。",{"id":514,"title":257,"titles":515,"content":516,"level":459},"\u002Fjava-interview\u002Fjava-basics#_6-泛型擦除带来了哪些限制pecs-怎么理解",[5],"Java 泛型是编译期特性，编译后类型参数被擦除到上界（无界时为 Object），运行时拿不到 List\u003CString> 的具体类型实参。 由此产生几条限制：不能 new T()、不能直接创建泛型数组、不能写 instanceof List\u003CString>、静态成员不能引用类上的类型参数、基本类型不能作为类型实参。为了维持多态，编译器会生成桥方法。 通配符遵循 PECS：生产者用 ? extends T，只能读不能写；消费者用 ? super T，可以写但读出来只能是 Object。List\u003C?> 不能 add 任何非 null 元素。 还要区分：List\u003CInteger> 不能赋给 List\u003CNumber>，这不是擦除造成的，而是泛型不协变。数组是协变的，所以 Object[] arr = new String[1] 能通过编译，但写入其他类型会在运行期抛 ArrayStoreException。",{"id":518,"title":324,"titles":519,"content":520,"level":459},"\u002Fjava-interview\u002Fjava-basics#_7-重载和重写有什么区别多态靠什么实现",[5],"重载在编译期按静态类型和参数列表选定，返回值不同不构成重载；重写在运行期按对象实际类型分派。重写要求签名相同、返回类型协变、访问权限不能收窄、不能抛出更宽的受检异常。 多态依赖对象头里的类型元数据指针和方法表：调用虚方法时按实际类型找到实现。private、static、final 方法不参与动态分派。 两个容易忽略的点：一是在构造器里调用可重写方法，子类字段尚未初始化，可能读到默认值；二是重载在编译期就固定，父类静态类型看不到子类新增的重载版本。",{"id":522,"title":346,"titles":523,"content":524,"level":459},"\u002Fjava-interview\u002Fjava-basics#_8-从-java-8-到-21哪些语言特性值得在面试里讲",[5],"JDK 8 带来 Lambda、Stream、Optional、接口默认方法和新的日期时间 API。JDK 10 的 var 只做局部变量类型推断，不改变类型系统。JDK 11 标准化了 HttpClient。JDK 17 是主流 LTS，包含 record、密封类、instanceof 模式匹配、文本块和 switch 表达式。 JDK 21 值得关注 switch 模式匹配（含 record 解构）、虚拟线程、分代 ZGC 和有序集合接口。 回答时最好落到场景：record 适合 DTO 和值对象，不适合需要可变状态或依赖无参构造的框架实体；虚拟线程适合大量阻塞在 IO 上的任务，不适合 CPU 密集任务，也不适合长时间持有 synchronized 的临界区——JDK 21 下会钉住载体线程，JDK 24 才改善这一限制。",{"id":526,"title":385,"titles":527,"content":528,"level":459},"\u002Fjava-interview\u002Fjava-basics#易错点与自测",[5],"不要只背「String 不可变所以线程安全」，要能说出共享、哈希缓存、常量池三个具体收益。 包装类比较、null 拆箱、BigDecimal 构造方式，是笔试里最高频的失分点。 泛型擦除不等于「运行时完全没有泛型信息」，声明处的信息仍可通过反射读到。 自测：① 解释 Integer a = 128; Integer b = 128; a == b 的结果；② 说明为什么泛型不能协变而数组可以；③ 说出 JDK 21 虚拟线程不适合哪两类任务。",{"id":530,"title":414,"titles":531,"content":532,"level":459},"\u002Fjava-interview\u002Fjava-basics#参考资料",[5],"JLS SE 21：类型、转换与泛型 — 泛型擦除、装箱转换的规范定义 java.lang.Object API — equals 与 hashCode 的契约原文 JEP 444：Virtual Threads — 虚拟线程的适用场景与钉住问题 JEP 441：Pattern Matching for switch — switch 模式匹配的正式语义",1793263559531]