Created
August 30, 2016 08:10
-
-
Save pygman/a31419b1dfc2bc38d00098bcff9a8982 to your computer and use it in GitHub Desktop.
无聊的难题
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Mocker<T extends Exception>{ | |
private void pleaseThrow(final Exception t)throws T{ | |
throw (T)t; | |
} | |
public static void main(final String[] args){ | |
try{ | |
new Mocker<RuntimeException>().pleaseThrow(new SQLException()); | |
}catch(final SQLException ex){ | |
ex.printStackTrace(); | |
} | |
} | |
} | |
/* | |
RuntimeException和SQLException都继承自Exception,但是在这个代码中RuntimeException是未检查的异常,而SQLException是受检异常。 | |
Java的泛型并不是具体化的。这意味着在编译时,泛型的类型信息会“丢失”,并且泛型参数像是被它的限定类型替换了一样,或者当限定类型不存在时,泛型参数被替换成了Object。这就是大家所说的类型“擦除”。 | |
我们天真地希望第七行能产生一个编译错误,因为我们不能将SQLException转换成RuntimeException,但是这并不会发生。发生的是将T替换成了Exception,所以我们有: | |
throw (Exception) t; // t is also an Exception | |
pleaseThrow方法期望一个Exception,并且T被替换成了Exception,因此类型转换被擦除了,就像没写这个代码一样。这一点我们可从下面的字节码中得到佐证: | |
我们再看一下,如果代码中没有涉及泛型,那么编译产生的字节码是什么样的,我们看到,在ATHROW前会有如下的代码: | |
CHECKCAST java/lang/RuntimeException | |
现在,我们可以确信,代码中并没有涉及到类型转换,因此我们可以排除下面这两个选项: | |
“编译错误,因为我们不能将SQLException类型转换为RuntimeException” | |
“抛出ClassCastException,因为SQLException不是RuntimeException的一个实例” | |
因此毕竟我们抛出了SQLException,然后你希望它能被catch代码块捕获,然后打印它的堆栈跟踪信息。然而,事与愿违。 | |
这个代码具有欺骗性,它使得编译器和我们一样变得困惑。这段代码让编译器认为catch代码块是不能到达的。对于不知情的旁观者来说,代码中并没有SQLException。所以,正确答案是:编译失败,因为编译器认为SQLException不会从try代码块中抛出-但是实际上它确实能抛出! | |
再次感谢Alexandru与我们分享这个问题!我们可以用另一个很酷的方式来查看代码中的错误以及SQLException实际上是怎样抛出的,这个方法是:修改catch代码块,把它修改为接收一个RuntimeException。这样你就可以看到SQLException的堆栈信息了。(实际上SQLException也并没有被catch代码段捕获,而是被虚拟机捕获并打印出异常栈的信息。) | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyClass0830{ | |
private String name; | |
public static void main(String args[]){ | |
MyClass0830 m1 = new MyClass0830(); | |
MyClass0830 m2 = new MyClass0830(); | |
m1.name = m2.name = "m1"; | |
callMe(m1, m2); | |
System.out.println(m1 + " & " + m2); | |
} | |
private static void callMe(MyClass... m){ | |
m[0] = m[1]; | |
m[1].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 | |
MyClass@3d0bc85 & MyClass@7d08c1b7 | |
所以正确的答案是“None of the above” | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static final List<String> NAMES = new ArrayList<String>(){{ | |
add("John"); | |
System.out.println(NAMES); | |
}}; | |
/* | |
很少有开发者知道这个初始化常量集合的简便语法,虽然这个语法会带来一些副作用。但事实上,这个语法鲜为人知未免不是一件好事。在感叹之后,你看到,我们往list里添加了一个元素,然后打印这个list。正常情况下,你期望看到打印的结果是[John],但是利用两个花括号进行初始化是有另一套初始化过程的。这里,我们用了一个匿名类来初始化一个List,当要打印NAMES时,实际上打印出来的是null,这是因为初始化程序尚未完成,此时的list是空的。 | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Map<String, Object> collection = new TreeMap<>(); | |
System.out.println(collection.compute("foo",(k,v)->(v == null)? new ArrayList<Object>() : ((List)v).add("bar"))); | |
System.out.println(collection.compute("foo",(k,v)->(v == null)? new ArrayList<Object>() : ((List)v).add("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”。 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment