1. 字符串常量池经典考题解析这道题几乎出现在90%的Java初级面试中我当年第一次遇到时也栽了跟头。先看题目public class StringDemo { private static final String MESSAGEcsdn; public static void main(String[] args) { String a csdn; String bcs; String cdn; System.out.println(aMESSAGE); // true System.out.println((bc)MESSAGE); // false } }正确答案是Dtrue false但为什么会出现这样的结果这涉及到Java字符串处理的三个关键机制编译期优化当编译器看到csdn时会直接合并成csdn相当于直接写String a csdn。这种优化只发生在编译期能确定的字符串拼接所以a和MESSAGE指向常量池同一个对象。运行时拼接bc的拼接是在运行时通过StringBuilder实现的本质上等价于new String(csdn)。每次new都会创建新对象所以地址肯定不同。常量池验证技巧我教大家一个验证方法 - 用System.identityHashCode()打印对象内存地址System.out.println(System.identityHashCode(a)); // 两个输出相同 System.out.println(System.identityHashCode(MESSAGE));实际项目中永远不要用比较字符串内容必须用equals()。我曾经在线上环境因为这个问题导致用户登录异常排查了整整一天。2. 自动导入包的特殊情况这题考察的是Java的默认导入机制// 哪个包不需要手动导入 A.java.lang B.java.util C.java.applet D.java.awt正确答案是A但背后的设计思想更值得深究java.lang的设计哲学包含Object、String、System等语言级基础类就像空气一样是编程必需品。如果每次都要导入代码会变得冗长。常见误区以为import java.lang.*能提高性能实际上编译器会自动优化在模块化项目中忘记requires java.basejava.lang的父模块扩展知识在JDK 9模块化系统后所有模块都需要显式声明依赖但java.base模块包含java.lang始终被隐式依赖。可以通过以下命令验证jdeps --list-deps your.jar我在重构老旧系统时就遇到过因为不了解这个机制导致的NoClassDefFoundError。当时花了三小时才定位到是缺失module-info.java中对java.sql的声明。3. 继承中的构造方法调用这道构造方法题非常具有迷惑性class Parent { int data; Parent(int d) { this.data d; } } class Child extends Parent { Child(String name, int d) { // 空白处填什么 } }正确答案是Dsuper(d)但这里有三个关键知识点构造方法链子类构造方法必须直接或间接调用父类构造方法。如果不写super编译器会自动插入super()调用无参构造。常见坑点父类没有无参构造时子类必须显式调用其他构造super()必须作为构造方法的第一条语句实战建议在团队协作中我强制要求所有实体类都显式定义无参构造方法避免继承时的意外错误。曾经有同事因为忘记这个规则导致Spring依赖注入失败。4. 静态代码块执行顺序这个题目考察类初始化顺序public class B { public static B t1 new B(); static { System.out.println(静态块); } { System.out.println(构造块); } public static void main(String[] args) { B t new B(); } }输出结果是C构造块 构造块 静态块 构造块具体执行流程首次加载类时为静态变量分配空间 → 执行静态初始化初始化t1时触发实例初始化构造块注意此时静态块还未执行关键原理静态变量初始化与静态代码块按代码顺序执行每个new都会触发实例初始化块我在设计配置加载系统时就利用这个特性实现了静态块加载配置 → 构造块检查配置的机制。建议大家在IDE里调试执行一遍观察栈帧变化。5. 多态与编译检查这个多态题目非常经典Animal b new Dog(); b.bark(); // 会发生什么正确答案是D编译错误原因在于编译时类型编译器只看引用类型Animal而Animal类没有bark()方法运行时类型虽然实际对象是Dog但编译阶段就要报错解决方案向下转型((Dog)b).bark()更好的设计将bark()声明为Animal的抽象方法我在代码评审中经常看到这种问题特别是DAO层返回的接口类型在实际使用时需要强制转型。推荐使用泛型来避免这种危险操作public interface AnimalT extends Animal { T getOffspring(); }6. 内部类访问限制这道内部类题目考察静态/非静态的访问规则public class OuterClass { private float f1.0f; // 插入哪个内部类会编译错误 }错误选项是ABCD每个都有问题A静态方法不能访问实例变量fB抽象方法不能有方法体C同A的静态方法问题D同A的静态方法问题实用建议非静态内部类会隐式持有外部类引用可能引起内存泄漏在Android开发中尤其要注意Handler如果作为非静态内部类会持有Activity引用我遇到过最棘手的Bug就是内部类导致的内存泄漏最后用静态内部类WeakReference解决的。建议大家在代码中写清楚注释// 必须用static避免内存泄漏 private static class SafeHandler extends Handler { private final WeakReferenceActivity mActivity; SafeHandler(Activity activity) { mActivity new WeakReference(activity); } }7. 线程同步机制剖析这道线程题考察Java的同步原理// Java用什么机制实现线程同步 A.监视器 B.虚拟机 C.多个CPU D.异步调用正确答案是A但背后的实现非常精彩监视器本质每个Java对象都关联一个monitor通过javap -v查看字节码会发现monitorenter/monitorexit指令JDK 6后的锁优化偏向锁、轻量级锁都是基于monitor实现实战经验synchronized在方法上和使用代码块字节码表现不同高并发场景下建议用ReentrantLock替代synchronized我在处理一个支付系统的高并发问题时用JProfiler发现synchronized成了瓶颈。最终方案是用ReentrantLock tryLock()实现超时控制细化锁粒度到账户级别而非整个服务8. 进制转换的巧妙应用这道进制题看似数学题实则是考察对数字系统的理解// 哪个等式在某个进制下成立 A.13*14204正确答案是A解题步骤设进制为n将等式转换为十进制(1n 3) * (1n 4) 2n² 0n 4解得n8八进制时成立开发中的应用处理文件权限时Linux的chmod命令网络子网掩码计算颜色RGB值表示我在开发智能硬件固件时就经常需要处理不同进制的数据转换。分享一个实用的进制转换工具方法public class NumberUtils { public static boolean isValidInBase(String num, int radix) { try { Integer.parseInt(num, radix); return true; } catch (NumberFormatException e) { return false; } } }9. 线程安全集合类辨析这道题考察对集合类线程安全的理解// 哪些类是线程安全的 A.Vector D.StringBuffer E.Properties正确答案是ADE但实际开发中有更好选择新版替代方案用Collections.synchronizedList包装ArrayList用ConcurrentHashMap替代Hashtable用StringBuilder替代StringBuffer局部变量时性能对比Vector的同步粒度是整个实例CopyOnWriteArrayList适合读多写少场景我在电商系统秒杀功能中就通过将Vector替换为ConcurrentHashMap使QPS从200提升到1500。关键点是分析读写比例测量不同实现的实际吞吐量使用JMH进行基准测试10. Spring事务隔离级别这道Spring事务题需要结合数据库知识// ISOLATION_REPEATABLE_READ下可能出现 B.幻读正确答案是B各隔离级别对比隔离级别脏读不可重复读幻读READ_UNCOMMITTED✓✓✓READ_COMMITTED×✓✓REPEATABLE_READ××✓SERIALIZABLE×××实战经验MySQL的InnoDB在RR级别通过MVCC间隙锁解决了幻读使用Transactional时要明确指定isolation和timeout分布式系统要用Seata等方案处理跨服务事务我在开发财务系统时就遇到过RR级别下的幻读导致对账不平的问题。最终解决方案是对关键操作使用SELECT FOR UPDATE添加版本号乐观锁业务层面添加校验机制11. 字符串创建优化这道字符串题目看似简单实则暗藏玄机String s acd; // 创建几个对象正确答案是A1个但要注意编译器优化常量折叠Constant Folding会将连接优化为acd使用javap -c查看字节码验证特殊情况如果拼接包含变量如s1s2则会创建StringBuilder在循环中拼接要用StringBuilder显式优化我在处理一个日志解析服务时就遇到过字符串拼接导致的性能问题。优化前后的对比// 错误写法每次循环创建StringBuilder String result ; for (LogEntry entry : logs) { result entry.getMessage(); } // 正确写法 StringBuilder sb new StringBuilder(1024); for (LogEntry entry : logs) { sb.append(entry.getMessage()); }12. 参数传递的本质这道题考察Java的参数传递机制public void change(String str, char[] ch) { str test ok; ch[0] c; } // 调用后输出结果是hello and cb因为String的不可变性方法内修改的是引用的副本原引用仍指向旧对象数组的可变性数组引用也是传值但可以通过引用修改内容这与C的引用传参不同设计启示使用不可变对象可以避免意外的副作用防御性拷贝是保持对象隔离的好方法我在设计API时对于敏感数据都会采用不可变对象防御性拷贝public final class SecurityToken { private final byte[] secret; public SecurityToken(byte[] secret) { this.secret Arrays.copyOf(secret, secret.length); } public byte[] getSecret() { return Arrays.copyOf(secret, secret.length); } }13. Servlet生命周期误区这道Servlet题考察对容器管理的理解// 关于Servlet生命周期错误的说法是 A.在init()中创建Servlet实例正确答案是A实际生命周期加载和实例化容器创建Servlet实例早于init初始化调用init()进行一次性设置服务每个请求在独立线程中调用service()销毁容器调用destroy()释放资源常见误区认为init()是创建实例的时机在service()方法中定义实例变量线程不安全我在处理一个线上内存泄漏时发现是因为在Servlet中缓存了大量数据。正确做法应该是用应用作用域的缓存如Spring的Cacheable实现ServletContextListener进行资源管理14. 异常处理最佳实践这道异常题考察对错误处理的理解// 关于异常处理正确的是 C.try检测异常 D.finally总执行正确答案是CD但实际开发中要注意抗干扰设计不要捕获Throwable特定异常要先于通用异常捕获资源管理用try-with-resources替代finally手动关闭在Spring中要区分checked/unchecked异常我在金融系统中采用的异常处理策略业务异常用自定义异常体系技术异常统一转换为用户友好提示关键操作添加事务补偿机制public class PaymentService { public void processPayment() throws PaymentException { try { // 支付逻辑 } catch (NetworkException e) { throw new PaymentException(支付网络超时, e); } } }15. 线程安全与可见性这道并发题考察对同步机制的理解// 关于线程安全说法正确的是 D.ThreadLocal是线程私有变量正确答案是BD深入理解ThreadLocal原理每个Thread维护一个ThreadLocalMap使用弱引用避免内存泄漏使用场景保存用户会话信息传递事务上下文避免方法参数传递我在分布式追踪系统中使用ThreadLocal的实践用InheritableThreadLocal支持线程池场景配合拦截器实现全链路追踪ID传递必须配套使用try-finally清理变量public class TraceContext { private static final ThreadLocalString traceId new ThreadLocal(); public static void startTrace(String id) { traceId.set(id); } public static void endTrace() { traceId.remove(); // 必须清理 } }16. 对象创建与内存分配最后这道题考察JVM内存管理String str new String(hello); // 创建了几个对象答案是1或2个如果常量池没有hello则先在常量池创建再在堆创建如果已有则只在堆创建性能优化建议避免不必要的new String()大量字符串处理时考虑直接使用char[]注意substring的内存共享问题我在开发报文解析器时通过重用char[]缓冲区将性能提升了40%public class MessageParser { private char[] buffer new char[1024]; public String parse(Reader input) { int len input.read(buffer); return new String(buffer, 0, len); } }