统计数据来自Java“死亡”竞赛——一个针对开发者的迷你测验
几个月前,我们在一个小型网站上发布了一个称为Java“死亡竞赛”的新项目。测验发布后,超过20000位开发者参加了测验。网站以20道关于Java的多选题为主。我们得到了众多开发者的测验统计数据,今天,我们非常乐意将其中的一些数据和答案与你们分享。
我们从20个题目中得到了61872个答案,大约每个题目有3094个答案。每个Java“死亡”测验都会随机地从20个题目中抽取5个题目,然后每个题目有90秒的时间作答。每个问题有四个可能的选项。经常有人向我们抱怨说这些题目太难了。所以,我们的测验被称为Java“死亡”竞赛并不是没有理由的哦!从测验结果的统计数据中,我们能知道哪些问题是最难的,哪些是最简单的。在这篇博客中,我想与你们分享5个从我们的测验中挑选出的最难的问题,然后一起解决它们。
平均来看,开发者给出的答案中大约41%是正确的,这个结果可一点不差。每个问题的索引和它的作答统计结果可以从这里得到。这篇博客所用的统计数据是在7月26日得到的。从这里可以尝试我们的Java“死亡”竞赛测验。
1、Java“死亡竞赛”中最难的问题
让我们从最难啃的骨头开始吧。这个问题由来自罗马尼亚首都布加勒斯特的 Alexandru-Constantin Bledea提供。这个问题确实是一个脑筋急转弯,只有约20%的参与者答对这道题,这意味着瞎选都能提高你回答正确的概率。这道题是关于Java泛型的。
题目大意:
这段代码错在哪儿?
a.编译错误,因为没有SQLException被抛出
b.抛出ClassCastException,因为SQLException并不是RuntimeException的一个实例
c.没有错误,程序打印出抛出的SQLException堆栈跟踪信息
d.编译错误,因为我们不能将SQLException类型转换成RuntimeException
好,我们能从题目中得到什么信息?题目中的泛型涉及到了类型擦除,以及一些异常。这里需要回忆一些知识:
RuntimeException和SQLException都继承自Exception,但是在这个代码中RuntimeException是未检查的异常,而SQLException是受检异常。
2.Java的泛型并不是具体化的。这意味着在编译时,泛型的类型信息会“丢失”,并且泛型参数像是被它的限定类型替换了一样,或者当限定类型不存在时,泛型参数被替换成了Object。这就是大家所说的类型“擦除”。
我们天真地希望第七行能产生一个编译错误,因为我们不能将SQLException转换成RuntimeException,但是这并不会发生。发生的是将T替换成了Exception,所以我们有:
1
throw (Exception) t; // t is also an Exception
pleaseThrow方法期望一个Exception,并且T被替换成了Exception,因此类型转换被擦除了,就像没写这个代码一样。这一点我们可从下面的字节码中得到佐证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
private pleaseThrow(Ljava/lang/Exception;)V throws java/lang/Exception L0 LINENUMBER 8 L0 ALOAD 1 ATHROW L1 LOCALVARIABLE this LTemp; L0 L1 0 // signature LTemp<TT;>; // declaration: Temp<T> LOCALVARIABLE t Ljava/lang/Exception; L0 L1 1 MAXSTACK = 1 MAXLOCALS = 2
我们再看一下,如果代码中没有涉及泛型,那么编译产生的字节码是什么样的,我们看到,在ATHROW前会有如下的代码:
1
CHECKCAST java/lang/RuntimeException
现在,我们可以确信,代码中并没有涉及到类型转换,因此我们可以排除下面这两个选项:
“编译错误,因为我们不能将SQLException类型转换为RuntimeException”
“抛出ClassCastException,因为SQLException不是RuntimeException的一个实例”
因此毕竟我们抛出了SQLException,然后你希望它能被catch代码块捕获,然后打印它的堆栈跟踪信息。然而,事与愿违。