最近在看一本 2006 年的 Java 书.
这书中文译名叫《Java 解惑》.不过我觉得, 中文版要不是翻译成解惑, 估计,他根本卖不出去.这玩意可能能解一点惑,但剩下满脑子都是:
-
原来**的还能这样? -
我对 Java ,知之甚少
1作者简介
书的作者有两位,分别是Joshua Bloch
和Neal Gafter
,可能很多人或多或少对这两个名字有印象.
没的话也不着急, 我来加深点印象.
/**
Author: Doug Lea, Josh Bloch, Arthur van Hoff, Neal Gafter
**/
public class HashMap<K,V> extends AbstractMap<K,V>
HashMap 注释上面 4 位作者中的两位.
2涉及内容
其实书的副标题很直白
-
Trap 圈套 -
Pitfall 陷阱 -
Corner case 极端情况
就是这些内容,非常黑暗 ,坑到人不要不要的.而且, 最关键是,10 多年了,96 道题里面, 还有很多没过时.要是真拿来当面试题的话,不少题能面死.
接下来简单分享几个我印象深刻的.主要是题目标题+我的笔记.要是想看题最好还是去看书.
3简单热身
先来几个简单的热热身
谜题 64: 绝对值 Math.abs
众所周知周知, 绝对值是数字到原点的距离. . 返回的是非负值. 然而由于数字的不对称性, 负数比正数多.Math.abs(Integer.MIN_VALUE)
是个负值.
然后作者批评了类库设计者, 如果这里在传入MIN_VALUE
时候,直接丢异常可能更好,而不是顶着一个 abs 的名字, 做一个有坑的设计.
Java 开发者估计是接受了批评, 在 Java15 版本的 Math 上, 添加了一个absExact
方法.这个方法就会在最小值抛出异常了.虽然迟到了十几年,坑过了很多人.
有篇文讲的特别好, 可以翻翻看.
RocketMQ 的源码,如果你看过源码你会发现到处都有这样的判断。
想着已经取绝对值了,然后再取余,肯定是正数啊,这
if(pos<0)
不就是多余的判断吗?是Yes呀,公众号:yes的练级攻略Math.abs 竟然返回了负数??
谜题 61: 日期和时间
这题其实是在批评 Java 的时间设计.
初代的时间设计是Date
类, 然后这个类巨坑无比.为了解决这个问题,又出了个Calendar
类. 但是Calendar
类也是满满的坑. 比如这个代码:
Calendar cal = Calendar.getInstance();
cal.set(2021,11,7);
Date date = cal.getTime();
date.getDay(); //输出
date.getDate(); //输出
会惊异的的发现, 方法奇葩:
-
getTime 返回的是 Date
. -
getDay 返回的是礼拜几. -
getDate 返回的是当月的第几天.
而且还有一个坑点是, 程序员从 0 开始计数, 11 指的是 12 月.
不过记不住这个坑也没关系, 好在, 这个问题也已经被解决了. Java8 时代最重要的更新之一,就是java.time
库.接口设计已经很像正常人类的表达, 终于, 可以优雅的写代码, 不被坑了.
LocalDate date = LocalDate.of(2021, 11, 7);
谜题 76: 乒乓
这个就不放题了, 简单说, 就是作者在批评Thread
类的设计.
相信很多朋友都会有在创建new Thread()
误调用了run()
而不是start()
的时候.
在使用工具时候, 一个人犯错,是用的人有问题.很多人犯错,其实那很可能是设计者也得多少有点问题.
Thread 类目前是实现了Runnable
接口的,但是其实这里完全可以不用继承.用组合来实现的话,按照组合>继承
的原则,如果这里 API 设计的好一点, 或许会更好. Thread has a
Runnable 明显优于 Thread is a
Runnable.
4较深的问题
除了前面那种盘点 Java 设计问题的小题目, 还有一些深一点的题目.
谜题 80 嵌套类
这个题目比较好玩. 我们都知道, java 写在一个类里面的类,叫嵌套类.
-
静态的叫 static nested class
,静态嵌套类 -
非静态的叫 inner class
内部类
静态嵌套类在编译以后,其实跟普通类没太大差别, 就是写的时候组织方式有点不一样. 内部类,就有点意思了.
我们先写成这样
public class Outer{
public class Inner{}
}
用javap '.Outer$Inner.class'
可以发现
Compiled from "Outer.java"
public class Outer$Inner {
final Outer this$0;
public Outer$Inner(Outer);
}
虽然每个没有构造器的类都会生成一个无参构造器
,但是对于内部类例外,他会生成一个以外部类为参数的构造器
.这东西其实有点复杂. 很多 JSON 库对内部类支持的不够好,都跟这玩意有关. 静态的还可以,如果不是完全掌握和确信有必要使用的话, 不要用内部类.
谜题 46,48 构造器和方法的选择
这个倒是一个常见的面试题了.主要考查是在有多个构造器或者方法符合的情况下, Java 是如何选择具体使用哪个方法的.
public class C {
public C(Object o) {}
public C(Integer o) {}
public C(int o) {}
public static void main(String[] args) {
new C(null);
}
}
这里面大致的规则是, 获取所有带选择方法,谁接收的参数范围小,用谁.这里使用的是Integer
版本.如果想使用Object
版本, 需要写成new C((Object) null)
.
谜题 36,39 try finally
36 题倒是也常被当面试题.
public static int foo(){
try{return 1;}
finally{return 2;}
}
public static int bar(){
try{return 1/0;}
finally{return(Integer)(Object)"";}
}
前面 return 啥都没用.前面的异常,也会被后面的吃掉.
39 题则是提供了一个不执行 finally 的方法.虽然很少用.
try{
System.out.println("Hello world");
System.exit(0);
}finally{
System.out.println("Goodbye world");
}
如果有需要在系统推出的时候做清理的话,不能用 finally,而是使用shutdownhook
.
Runtime.getRuntime().addShutdownHook(
new Thread(){
public void run(){
System.out.println("Goodbye world");
}
});
dubbo 就使用DubboShutdownHook
来做了这么个清理操作.
谜题 43 sneakyThrow
这题,其实很多lombok
玩家应该都见过了,这是lombok
的一个功能.
比如我们写这样一个方法,如果不 try 也不往外扔的话,肯定不能编译通过:
//@SneakyThrow
public void foo(){
throw new IOException();
}
如果加上lombok
的@SneakyThrow,那么就可以编译通过了. 他可以省掉try
或者throw
.这里用的就是这一题的技巧,如何在运行中扔出一个checkedException
. @SneakyThrows (projectlombok.org)[1] . 巧妙利用泛型丢出来的.
谜题 45 递归 try finally
这题非常灵异, 估计不会有人考. 但是拿来检查递归是否掌握了,很有趣.
static void foo(){
try{
foo();
}finally{
foo();
}
}
这个方法会爆栈.但是假如栈深度为 n 的话, 他会产生一个完全二叉的调用树.在 次调用后,产生 个异常.注意到棋盘放米的故事话, 这几乎等价于永远不会停了.
6数字和字符相关
除了上面那些, 还有一些跟数字和循环有关的.
谜题 6 多次 cast
System.out.println((int)(char)(byte)-1);
有一批主要是混合类型和类型拓宽之类带来的谜题,就不摘抄太多了了.
可以猜猜看,上面那句的答案是多少.猜不对的话,千万不要混合byte
,char
,int
等等类型进行计算,惹不起躲得起.
谜题 28,29,30,32,34 循环
这里是几个概念.
-
int 是有边界的, i+1 可能<i -
double 浮点数不是均匀的. 比较大的浮点数, 两个相邻数之间的间隔可能大于>1. 这时候 f+1 ==f. 用浮点数做循环很可能被坑死. -
NaN!=NaN 浮点数中有一个表示不是数的符号, 他跟自己不相等. -
+
用作字符串拼接 作者认为 java 不支持运算符重载却支持+
作为字符串加法是个败笔 -
i <= j && j <= i && i != j
可能为真. 这个涉及到自动拆装箱.
谜题 14,15,16 转义字符
这题涉及到 Java 的编译逻辑,代码里写的uxxxx
会先进行翻译, 即使是注释里写uxxxx
如果不是合法 Unicode 也能引起编译问题. 甚至能把 helloworld 写成这样.
u0070u0075u0062u006cu0069u0063u0020u0020u0020u0020
u0063u006cu0061u0073u0073u0020u0055u0067u006cu0079
u007bu0070u0075u0062u006cu0069u0063u0020u0020u0020
u0020u0020u0020u0020u0073u0074u0061u0074u0069u0063
u0076u006fu0069u0064u0020u006du0061u0069u006eu0028
u0053u0074u0072u0069u006eu0067u005bu005du0020u0020
u0020u0020u0020u0020u0061u0072u0067u0073u0029u007b
u0053u0079u0073u0074u0065u006du002eu006fu0075u0074
u002eu0070u0072u0069u006eu0074u006cu006eu0028u0020
u0022u0048u0065u006cu006cu006fu0020u0077u0022u002b
u0022u006fu0072u006cu0064u0022u0029u003bu007du007d
7完
今天是一篇打着面死题旗号的书评.
也就到此结束, 不能再摘了, 再摘整本书都摘上来了.里面还有很多我抄书都不知道怎么抄出来的题,有大佬感兴趣,可以去拿出来玩玩儿.没事看看有利于身心健康.
参考资料
@SneakyThrows (projectlombok.org): https://projectlombok.org/features/SneakyThrows
原文始发于微信公众号(K字的研究):Java面死题精选
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24822.html