八成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,所以我们有:


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代码块捕获,然后打印它的堆栈跟踪信息。然而,事与愿违。

这个代码具有欺骗性,它使得编译器和我们一样变得困惑。这段代码让编译器认为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”。


1

m1.name = m2.name = “m1";

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

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


1

[email protected] & [email protected]

所以正确的答案是“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%的答题者回答正确。

很少有开发者知道这个初始化常量集合的简便语法,虽然这个语法会带来一些副作用。但事实上,这个语法鲜为人知未免不是一件好事。在感叹之后,你看到,我们往list里添加了一个元素,然后打印这个list。正常情况下,你期望看到打印的结果是[John],但是利用两个花括号进行初始化是有另一套初始化过程的。这里,我们用了一个匿名类来初始化一个List,当要打印NAMES时,实际上打印出来的是null,这是因为初始化程序尚未完成,此时的list是空的。

关于使用两个花括号进行容器的初始化,可参考这里(right here)。

5、对于运行时Map容器的离奇事件

这是另一个社区贡献的问题,贡献者是来自以色列的Barak Yaish。只有27%的答题者能解答这个问题。

题目大意:这段代码的输出是什么

a.不能编译

b.类型转换异常

c.[] true

d.[“bar”, “ber”]

好吧,来看看代码。compute方法通过key在map中查找一个value。如果这个value是null,则插入(key, value),并返回value。因为开始时,这个list是空的,“foo”值并不存在,v是null。然后,我们向map中插入一个“foo”并且“foo”指向new ArrayList<Object>(),此时的ArrayList对象是空的,所以它打印出[]。

下一行,“foo”键值存在于map容器中,所以我们计算右边的表达式。ArrayList对象成功转换为List类型,然后“ber”字符串被插入到List中。add方法返回true,因此true就是第二行打印的内容。

所以正确的答案是”[]true”。再次感谢Barak于我们分享这道题。

鼓励一下:来看看最简单的题吧

题目大意:哪一种方法是初始化Java字符串最简单的方式

a.A

b.没有一个

c.C

d.B和C不能编译

现在,我们来看一下Peter Lawrey提供的问题。他工作于OpenHFT开源项目,同时也在Vanilla Java上撰写博客。Peter在StackOverflow上排名top 50,这一次他反过来向大家提问,76%的开发者能回答出这个问题。

C答案比A简单,B和D是不能编译的。

结论

我们有时喜欢做这样的小测验来加深我们对Java知识的理解。但是,你是否发现自己的代码库中也有这样或那样类似小测验的问题使自己困惑,常常需要花许多时间来维护,这样的话可能并不好。特别是在半夜时,你接到一个电话,让你去解决一个严重的产品错误。对于这种情况,我们开发了Takipi这个Java工具。Takipi是一个Java代理,它能在生产环境下追踪未捕获的异常、捕获异常以及记录服务器上的错误日志。使用这个工具,你可以在堆栈中看到引发异常的变量值,然后在你的代码中修改它们。

时间: 2024-11-05 11:55:39

八成Java开发者解答不了的问题的相关文章

八款Java开发者必备的工具

Java是计算机应用编程语言,被广泛的用于创建Web应用程序.服务器搭建.客户端API开发以及数据库.Java的用例和重要性是巨大的.Java的学习曲线需要程序员处于不断活跃的状态,而今天我们会列出8个最佳Java工具,可以帮助你开发Java应用. 1. SparkJava 这是针对Java开发者的一个新的轻量级以及强大的Web应用框架.Spark拥有简单和直接的方法,你可以使用Spark框架,通过做一些繁琐的XML配置来编码应用.Spark助你更轻松的开发Java Web应用. 2. jCla

如何成为一名Java开发者?

Java是当今世界三大编程语言之一.它可被用来开发Web应用和桌面应用,而且它是跨平台的 - 一次编译,多处运行(write once, run everywhere).而且,Java上手十分简单.如果你想要成为一名合格的Java开发者,你需要看看自己是否知道一下内容. 下面的列表是由一个高级Java开发者Vivek Vermani总结的. 对于一个核心Java开发者来说,他最好应了解以下内容. (1) 面向对象(OOP)的概念 (2) 抽象类(abstract class)和接口(interf

Java开发者必备十大学习网站

作为开发者来说,必备的除了对编码的热情还要有自己的一套技巧,另外不可缺少的就是平时学习的网站.以下本人收集的Java开发者必备的网站,这些网站可以提供信息,以及一些很棒的讲座, 还能解答一般问题.面试问题等,或许你会认为有些网站适合任何水平的开发者,但是我认为:对于Java开发大牛来说,网站的好坏取决于如何使用它们. Stack overflow Stack overflow.com 可能是编程界中最流行的网站了, 是一个与程序相关的IT技术问答网站,用户可以在网站免费提交问题,浏览问题,索引相

JAVA开发者学习必备的十大网站

作为开发者来说,必备的除了对编码的热情还要有自己的一套技巧,另外不可缺少的就是平时学习的网站.以下本人收集的 Java 开发者必备的网站,这些网站可以提供信息.以及一些很棒的讲座 , 还能解答一般问题.面试问题等,或许你会认为有些网站适合任何水平的开发者,但是我认为::对于 Java 开发大牛来说,网站的好坏取决于如何使用它们. 1.Stack overflow Stack overflow.com 可能是编程界中最流行的网站了 , 是一个与程序相关的 IT 技术问答网站,用户可以在网站免费提交

Scala学习笔记及与Java不同之处总结-从Java开发者角度

Scala与Java具有很多相似之处,但又有很多不同.这里主要从一个Java开发者的角度,总结在使用Scala的过程中所面临的一些思维转变. 这里仅仅是总结了部分两种语言在开发过程中的不同,以后会陆续更新一些切换后在开发过程中值得注意的地方.以下列举了部分,但令人印象深刻的Scala语言的不同之处,具体的代码演示样例及具体阐述见下文. ? Scala中可直接调用Java代码,与Java无缝连接. 语句能够不用";"结束.且推荐不适用";". 变量声明时以var或va

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

给Java开发者的Scala教程

author:Michel Schinz,Philipp Haller 1. 简介 本文将该要的介绍Scala语言和其编译.这里假设读者已经有一定的java开发经验,需要概要的了解他们可以用Scala 做些什么. 2. 第一个例子 我们用全世界最著名的代码来作为开始.虽然没什么用,但是可以很好地直观的了解Scala: object HelloWorld { def main(args: Array[String]): Unit = { println("Hello, world!")

从Java开发者的视角解释JavaScript

我们无法在一篇博文里解释JavaScript的所有细节.如果你正或多或少地涉及了web应用程序开发,那么,我们的Java工具和技术范围报告揭示了,大多数(71%)Java开发者被归到了这一类,只是你对JavaScript遇到了阻碍. 毫无疑问,你已经知道了Java和JavaScript,不管它们有着多么类似的命名,彼此没有共享太多共通之处.Java的静态类型.符合直接规律的简单语法和冗长,与JavaScript的动态.缺乏一致性原则和怪异,有着巨大的不同. 然而,JavaScript是web的编

评论 “App死亡潮:400万应用僵尸超八成,周期仅10月”

原文: App死亡潮:400万应用僵尸超八成,周期仅10月 时间 2015-04-05 22:48:19  和讯科技相似文章 (16)原文  http://tech.hexun.com/2015-04-05/174713108.html App死亡潮:400万应用僵尸超八成,周期仅10月 艾媒咨询分析师表示,“App的生命周期平均只有十个月,85%的用户会在1个月内将其下载的应用程序从手机中删除,而到了5个月后,这些应用程序的留存率仅有5%.” 理财周报特约记者 李曼宁/广州报道 PC互联网时代