设为首页 加入收藏

TOP

八成Java开发者解答不了的问题(一)
2015-08-31 21:22:58 来源: 作者: 【 】 浏览:82
Tags:八成 Java 开发者 解答 不了 问题

统计数据来自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,所以我们有:


pleaseThrow方法期望一个Exception,并且T被替换成了Exception,因此类型转换被擦除了,就像没写这个代码一样。这一点我们可从下面的字节码中得到佐证:


我们再看一下,如果代码中没有涉及泛型,那么编译产生的字节码是什么样的,我们看到,在ATHROW前会有如下的代码:


现在,我们可以确信,代码中并没有涉及到类型转换,因此我们可以排除下面这两个选项:


“编译错误,因为我们不能将SQLException类型转换为RuntimeException”


“抛出ClassCastException,因为SQLException不是RuntimeException的一个实例”


因此毕竟我们抛出了SQLException,然后你希望它能被catch代码块捕获,然后打印它的堆栈跟踪信息。然而,事与愿违。


这个代码具有欺骗性,它使得编译器和我们一样变得困惑。这段代码让编译器认为catch代码块是不能到达的。对于不知情的旁观者来说,代码中并没有SQLException。所以,正确答案是:编译失败,因为编译器认为SQLException不会从try代码块中抛出-但是实际上它确实能抛出!


再次感谢Alexandru与我们分享这个问题!我们可以用另一个很酷的方式来查看代码中的错误以及SQLException实际上是怎样抛出的,这个方法是:修改catch代码块,把它修改为接收一个RuntimeException。这样你就可以看到SQLException的堆栈信息了。实际上SQLException也并没有被catch代码段捕获,而是被虚拟机捕获并打印出异常栈的信息。


2、问题的关键在于,是否使用了toString()



这道题只有24%的正确率,它的困难程度是这20道题中的亚军。


题目大意:这个程序的打印结果是?


a.m1 & new name


b.以上都是错误的


c.m1&m1


d.new name & new name


这道题实际上简单得多,我们只要看到第十二行,它直接打印了m1和m2,而不是m1.name和m2.name。这段代码狡猾的地方在于,当我们要打印一个对象时,Java使用的是toString方法。“name”属性是我们自己加入的,如果你忘记这点,其他地方都判断正确的话,你可能会错误地选择m1&new name这个答案。


这行代码将两个对象的name属性都赋值为”m1”。


然后callMe方法将m2对象的name属性设置成”new name”,然后代码就结束了。


但是,这个代码片段实际上将会打印出如下信息,包括类名称以及它们的哈希码:


所以正确的答案是“None of the above”


3、Google Guava类库中的Sets



题目大意:


这道题目不妥的地方在哪?


a.不能编译


b.没有问题


c.可能造成内存溢出


d.可能造成无限循环


这个问题实际上并不特别需要关于Guava sets类库的专业知识,但却使绝大多数的开发者产生困惑。只有25%的参与者给出了正确的答案,和瞎选的正确率是一样的。


那么我们能从这段代码中看出什么呢?我们有一个方法,它返回一个集合,这个集合包含了某个人的好友圈。方法中有一个循环,它检查一个person对象的bestfriend属性是否为null。如果不为null,则将bestfriend添加到results集合里。如果一个person对象确实有一个bestfriend,那么对这个person的bestfriend,重复执行上述过程,所以我们就可以一直向bestfriend集合添加person对象,直到有一个person,它没有bestfriend,或者它的bestfriend已经在我们的result集合里了。最后这部分有一点微妙,我们不能向这个Set集合添加重复的元素,即person对象,所以这个方法并不会导致无限循环。


真正的问题在于,这段代码很有可能造成内存用尽的异常(out of memory exception)。这个循环实际上是没有边界的,所以我们可以不停地往set中添加person对象,直到内存用尽。


顺便提一下,如果你想详细了解Google Guava,可以看看我们写的这篇博客: the lesser known yet useful features about it


4、利用两个花括号进行初始化



题目大意:这段代码错误的地方在哪?


a.没有错误


b.可能获得null值


c.代码不能编译


d.打印出不正确的结果


这个问题是代码最少的问题之一,但是足以迷惑绝大部分的开发者。这道题只有26%的答题者回答正确。


很少有开发者知道

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Python Socket网络编程 下一篇10个实用的但偏执的Java编程技术

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: