Java基础知识强化14:Java死亡竞赛题目解析
一个小型网站上发布了一个称为Java“死亡竞赛”的新项目。测验发布后,超过20000位开发者参加了测验。网站以20道关于Java的多选题为主。我们得到了众多开发者的测验统计数据,今天,我们非常乐意将其中的一些数据和答案与你们分享。
我们从20个题目中得到了61872个答案,大约每个题目有3094个答案。每个Java“死亡”测验都会随机地从20个题目中抽取5个题目,然后每个题目有90秒的时间作答。每个问题有四个可能的选项。经常有人向我们抱怨说这些题目太难了。所以,我们的测验被称为Java“死亡”竞赛并不是没有理由的哦!从测验结果的统计数据中,我们能知道哪些问题是最难的,哪些是最简单的。在这篇博客中,我想与你们分享5个从我们的测验中挑选出的最难的问题,然后一起解决它们。
1.Java“死亡竞赛”中最难的问题:
让我们从最难啃的骨头开始吧。这个问题由来自罗马尼亚首都布加勒斯特的 Alexandru-Constantin Bledea提供。这个问题确实是一个脑筋急转弯,只有约20%的参与者答对这道题,这意味着瞎选都能提高你回答正确的概率。这道题是关于Java泛型的。
题目大意:
这段代码错在哪儿?
a.编译错误,因为没有SQLException被抛出
b.抛出ClassCastException,因为SQLException并不是RuntimeException的一个实例
c.没有错误,程序打印出抛出的SQLException堆栈跟踪信息
d.编译错误,因为我们不能将SQLException类型转换成RuntimeException
解析:
好,我们能从题目中得到什么信息?题目中的泛型涉及到了类型擦除,以及一些异常。这里需要回忆一些知识:
RuntimeException和SQLException都继承自Exception,但是在这个代码中RuntimeException是未检查的异常,而SQLException是受检异常。
Java的泛型并不是具体化的。这意味着在编译时,泛型的类型信息会“丢失”,并且泛型参数像是被它的限定类型替换了一样,或者当限定类型不存在时,泛型参数被替换成了Object。这就是大家所说的类型“擦除”。
我们天真地希望第七行能产生一个编译错误,因为我们不能将SQLException转换成RuntimeException,但是这并不会发生。发生的是将T替换成了Exception,所以我们有:
throw (Exception) t; // t is also an Exception
pleaseThrow方法期望一个Exception,并且T被替换成了Exception,因此类型转换被擦除了,就像没写这个代码一样。这一点我们可从下面的字节码中得到佐证:
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前会有如下的代码:
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”。
m1.name = m2.name = “m1";
后callMe方法将m2对象的name属性设置成”new name”,然后代码就结束了。
但是,这个代码片段实际上将会打印出如下信息,包括类名称以及它们的哈希码:
MyClass@3d0bc85 & MyClass@7d08c1b7
所以正确的答案是“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于我们分享这道题。
6.最简单的题目:
题目大意:哪一种方法是初始化Java字符串最简单的方式
a.A
b.没有一个
c.C
d.B和C不能编译
解析:
Java中String是一个特殊的包装类数据有两种创建形式:
- String s = "abc";
- String s = new String("abc");
第一种先在栈中创建一个对String类的对象引用变量s,然后去查找"abc"是否被保存在字符串常量池中,如果没有则在栈中创建三个char型的值'a'、'b'、'c',然后在堆中创建一个String对象object,它的值是刚才在栈中创建的三个char型值组成的数组{'a'、'b'、'c'},接着这个String对象object被存放进字符串常量池,最后将s指向这个对象的地址,如果"abc"已经被保存在字符串常量池中,则在字符串常量池中找到值为"abc"的对象object,然后将s指向这个对象的地址。
第一种特点:JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。
第二种可以分解成两步1、String object = "abc"; 2、String s = new String(object); 第一步参考第一种创建方式,而第二步由于"abc"已经被创建并保存到字符串常量池中,因此jvm只会在堆中新创建一个String对象,它的值共享栈中已有的三个char型值。
第二种特点:一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象。
在讲字符串比较前,必须要了解==和equals的区别:
因为java所有类都继承于Object基类,而Object中equals用==来实现,所以equals和==是一样的,都是比较对象地址,java api里的类大部分都重写了equals方法,包括基本数据类型的封装类、String类等。对于String类==用于比较两个String对象的地址,equals则用于比较两个String对象的内容(值)。
现在,我们来看一下Peter Lawrey提供的问题。他工作于OpenHFT开源项目,同时也在Vanilla Java上撰写博客。Peter在StackOverflow上排名top 50,这一次他反过来向大家提问,76%的开发者能回答出这个问题。
C答案比A简单,B和D是不能编译的。
Java基础知识强化14:Java死亡竞赛题目解析的更多相关文章
- Java基础知识强化之集合框架笔记76:ConcurrentHashMap之 ConcurrentHashMap简介
1. ConcurrentHashMap简介: ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和Hashtable功能相同但是线程安全的方法.Conc ...
- java基础知识回顾之---java String final类普通方法
辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /* * 按照面向对象的思想对字符串进行功能分类. * ...
- Java基础知识强化之多线程笔记01:多线程基础知识(详见Android(java)笔记61~76)
1. 基础知识: Android(java)学习笔记61:多线程程序的引入 ~ Android(java)学习笔记76:多线程-定时器概述和使用
- Java基础知识强化之网络编程笔记06:TCP之TCP协议发送数据 和 接收数据
1. TCP协议发送数据 和 接收数据 TCP协议接收数据:• 创建接收端的Socket对象• 监听客户端连接.返回一个对应的Socket对象• 获取输入流,读取数据显示在控制台• 释放资源 TCP协 ...
- Java基础知识强化之多线程笔记06:Lock接口 (区别于Synchronized块)
1. 简介 我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式 ...
- Java基础知识强化之IO流笔记72:NIO之 NIO核心组件(NIO使用代码示例)
1.Java NIO 由以下几个核心部分组成: Channels(通道) Buffers(缓冲区) Selectors(选择器) 虽然Java NIO 中除此之外还有很多类和组件,Channel,Bu ...
- Java基础知识强化100:JVM 内存模型
一. JVM内存模型总体架构图: 方法区和堆由所有线程共享,其他区域都是线程私有的 二. JVM内存模型的结构分析: 1. 类装载器(classLoader) 类装载器,它是在java虚拟机中用途是 ...
- Java基础知识强化之多线程笔记05:Java程序运行原理 和 JVM的启动是多线程的吗
1. Java程序运行原理: Java 命令会启动Java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程.该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 m ...
- Java基础知识强化之IO流笔记60:打印流 之 改进复制文本文件的案例
1. 使用打印流改进复制文本文件的案例 2. 代码示例: package cn.itcast_03; import java.io.BufferedReader; import java.io.Buf ...
随机推荐
- Delphi FireMonkey使用UniDAC 连接MySQL
首次用Delphi XE6 开发安卓程序,并没有在网上找到连接远程MySQL服务器的文档,自己摸索一番,发现UniDAC控件新版本也已支持了FireMonkey下的开发.遂记下连接方法和大家分享. 1 ...
- BZOJ 2301: [HAOI2011]Problem b 莫比乌斯反演
2301: [HAOI2011]Problem b Time Limit: 50 Sec Memory Limit: 256 MBSubmit: 1007 Solved: 415[Submit][ ...
- JPA2.1 中三个提升应用性能的新功能
经常在网上看到开发者们抱怨 JPA 性能低下的帖子或文章,但如果仔细查看这些性能问题,常会发现导致问题的根本原因大致包括以下几个: 使用过多的 SQL 查询从数据库中获取所需的实体信息,即我们常说的n ...
- SQLserver 数据库
1.数据库: 结构化查询语言(Structured Query Language)简称SQL: 数据库管理系统(Database Management System)简称DBMS: 数据库管理员(Da ...
- Volley框架支持HTTPS请求。
第一次写帖子,嘿嘿. 最近了解到google2013IO大会出了个网络框架,正好项目也需要用到,就看了下. 最后发现接口都是HTTPS的,但是Volley默认是不支持HTTPS,网上找了好久,都没有对 ...
- 【HDOJ】1908 Double Queue
双端队列+二分. #include <cstdio> #define MAXN 1000005 typedef struct { int id; int p; } node_st; nod ...
- java学习面向对象之异常之一
一.异常的概述: 什么是异常?在我们编写java程序的时候,会出现一些问题,比如内存溢出啊或者数组索引超出最大索引啊,这些编程当中出现的这些个问题就是异常.但是异常也分为可以处理的和不可以处理的.比如 ...
- JDK 1.6.0和 6.0 有啥区别,JavaTM SE 6 的平台名字和版本号的说明(转)
一直这么理解,今天才看到官方的解释,真是有点汗颜. 核心就是 6.0用于平台和产品的名字,而1.6.0用于开发者. 他们指的是同一个东西. 原文地址:http://java.sun.com/javas ...
- 通过eclipse的DDMS连接bluestacks找不到设备的解决方法
在找不到设备的时候cmd输入命令 adb connect 127.0.0.1 ,DDMS中就会显示已连接的bluestacks了.
- HTML---网页编程(1)
前 言 HTML需要和CSS还有JS一起用,才能提现强大. 所以,学了HTML.最好去学学CSS还有JS(JavaScript) ☆静态页面和动态页面 网站页面分为静态页面和动态页面两种 • 静态页面 ...