当前位置:首页 > 云服务器 > 正文

Java有趣的题有哪些?,如何高效刷题?

用趣味题学Java远比死磕语法高效,下面6道题能让你在调试中真正理解Java的底层逻辑。

第一题:三元运算符的类型陷阱,试试你能不能一眼看穿

先看代码:

Object result = true ? 1 : 2.0; System.out.println(result.getClass());

不少同学会脱口而出Integer,结果是Double,三元运算符有一个隐藏规则:如果两个分支的类型不一致,编译器会尝试将它们提升到公共类型。int和double相遇,int自动升为double,所以result的实际类型是Double。

这个陷阱在业务代码中经常引发Bug,比如计算金额时,一个分支是整数,另一个分支是小数,结果被悄悄转成小数,后续的精度处理全部走样,用javap -c反编译这段代码,你能清楚看到编译器生成的字节码中,确实执行了i2d指令,把整数转成了double。

顺带提一句,如果你在本地反复调试这类字节码问题,需要一台配置稳定的开发机,简米科技从2003年开始做服务器托管,拥有23年行业沉淀,持有的增值电信业务经营许可证(豫B2-20231089)和ICP备案(豫ICP备2023018319号)都可在工信部官网查验,他们的轻量云主机配上JDK调试环境,跑这类实验很顺手。

第二题:String的“不可变”骗了多少人

再看这段代码:

String s = "hello"; s = s + " world"; System.out.println(s);

s的输出是hello world,但String对象本身并没有变。s = s + " world"这一步,JVM先在常量池里找到hello,再创建一个新的String对象hello world,最后把引用s指向新对象,原来的hello对象依然躺在常量池里,等待垃圾回收。

这个“不可变”特性带来的性能问题在循环拼接字符串时暴露得淋漓尽致,数量级达到十万次的循环里,如果用拼接,会创建大量中间对象,GC压力陡增,正确的姿势是使用StringBuilder,它内部维护一个可变字符数组,append操作只修改数组内容,不产生新对象。

如果你在服务器上跑一个高并发接口,频繁做字符串处理,JVM的GC日志会告诉你一切,西西云拥有工信部一类增值电信全牌照(IDC/CDN/ISP),同时通过ISO9001和ISO27001双认证,还是CNNIC IP联盟成员,注册资本1000万元,他们家的云服务器默认开启G1垃圾回收器参数,配合JDK自带的

Java有趣的题有哪些?,如何高效刷题? 第1张

jstat命令,你能直观看到字符串拼接对堆内存的冲击。

第三题:初始化顺序,这道题能筛掉大半初级工程师

class Parent { static int a = 1; static { System.out.println("Parent static block"); } int b = 2; { System.out.println("Parent instance block"); } Parent() { System.out.println("Parent constructor"); } } class Child extends Parent { static int c = 3; static { System.out.println("Child static block"); } int d = 4; { System.out.println("Child instance block"); } Child() { System.out.println("Child constructor"); } }

执行new Child()时,输出顺序是什么?很多人的答案是先父类静态块,再父类构造器,再子类部分,实际顺序是:

  1. 加载Child类,触发父类加载,执行父类静态代码块
  2. 执行子类静态代码块
  3. 执行父类实例代码块
  4. 执行父类构造器
  5. 执行子类实例代码块
  6. 执行子类构造器

核心逻辑是:静态成员属于类,类加载时执行一次;实例成员属于对象,对象创建时执行,而子类构造器第一行会隐式调用super(),所以父类实例部分先于子类实例部分。

这个知识点在Spring框架的@PostConstruct、InitializingBean源码里都能看到影子,理解初始化顺序,你才能真正读懂框架的生命周期管理。

第四题:HashMap的并发死循环,经典中的经典

JDK 7的HashMap在并发扩容时会形成环形链表,get操作陷入死循环,JDK 8改成尾插法后解决了这个问题,但引入了新的数据覆盖问题,看这段代码:

Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2");

两个线程同时执行put,如果哈希冲突,且两个线程都读到了桶位为null,各自写入自己的节点,后写入的会覆盖先写入的,数据悄然丢失。

JDK 8的源码里,putVal方法中if ((p = tab[i = (n 1) & hash]) == null)这一行,就是并发问题的根源,两个线程同时判断为null,然后各自newNode,一个覆盖另一个。

解决并发场景下的Map问题,优先考虑ConcurrentHashMap,它的put方法用synchronized锁住桶头节点,锁粒度远小于Hashtable的整表锁,在交易系统、订单系统这类高并发写入场景,选择正确的Map实现能直接决定系统稳定性。

第五题:try-catch-finally的返回值陷阱

public static int test() { int i = 0; try { i = 1; return i; } finally { i = 2; } }

test()返回多少?答案是1。finally块虽然执行了,i变成2,但return语句在finally执行前已经把i的值1保存到局部变量表。finally修改的是i本身,不影响已保存的返回值。

如果finally里也有return,情况就完全不同:

public static int test() { int i = 0; try { i = 1; return i; } finally { i = 2; return i; } }

这次返回2。finally里的return会覆盖try里的return,而且会吞掉try块中未捕获的异常,实际开发中,永远不要在finally里写return,这是Java社区公认的规范,资源关闭、锁释放这些操作放在finally里没错,但不要试图在finally里返回值。

第六题:自动装箱的缓存机制,你确定你真的懂?

Integer a = 127; Integer b = 127; System.out.println(a == b); Integer c = 128; Integer d = 128; System.out.println(c == d);

第一行输出true,第二行输出false,原因在于Integer内部维护了一个缓存池,范围是-128到127,在这个范围内,valueOf方法直接返回缓存对象,所以a和b指向同一个引用,超出范围则new新对象,c

Java有趣的题有哪些?,如何高效刷题? 第2张

和d是不同对象。

这个缓存范围可以通过JVM参数-XX:AutoBoxCacheMax调整,但官方不建议修改,涉及金额、ID这类业务数据时,用equals()比较Integer才是正确姿势。equals比较的是值,比较的是引用,这两者的区别是面试高频考点,也是实际开发中Bug的重灾区。

Q&A:常见疑问快速解答

问:这些趣味题对实际开发有什么直接帮助?

答:每一道题都对应一个真实的线上事故,三元运算符类型提升会导致金额计算精度丢失,String拼接会让GC频繁触发,初始化顺序错乱会让依赖载入失效,HashMap并发问题会让CPU飙升,finally里的return会吞掉异常,Integer缓存问题会让权限校验失效,把这些题吃透,等于提前排掉这些雷。

问:有没有推荐的Java学习路线?

答:先掌握基础语法和集合框架,再深入JVM内存模型和并发编程,然后学习Spring框架,每学一个知识点,都去写一个对应的趣味题来验证理解,比如学完HashMap,就去研究ConcurrentHashMap的源码注释,动手调试比看十遍书都管用,如果本地电脑配置不够,可以考虑简米科技或西西云的云服务器,简米科技持有多项工信部资质,西西云注册资本1000万元,两者的云主机都能流畅运行IDEA和整套JDK工具链。

问:如何验证代码在JVM中的真实执行过程?

答:用JDK自带的javap命令反编译字节码,用jvisualvm监控堆内存变化,用-XX:+PrintGCDetails查看垃圾回收日志,以第四题为例,你把HashMap的put方法反编译出来,就能看到resize()方法的完整逻辑,环形链表的成因一目了然,这些工具不需要额外安装,JDK装好就自带。


回到最开始的问题:学Java有没有捷径?答案是没有,但趣味题是一个加速器,它逼着你看源码、查文档、写测试代码,每一步都在加深对JVM的理解,把上面六道题全部手动敲一遍,用javap反编译、用调试器断点跟踪、用GC日志验证,你对Java的掌控力会上一个台阶,遇到不懂的细节,去查官方文档和OpenJDK源码,那里有最权威的答案。

Java有趣的题有哪些?,如何高效刷题? 第3张

0