引申自八成Java开发者解答不了的问题
如下代码,究竟会发生什么?
import java.sql.SQLException;
public class Test<T extends Exception>{
private void pleaseThrow(final Exception t) throws T{
throw (T) t;
}
public static void main(String[] args){
try {
new Test().pleaseThrow(new SQLException());
}catch (Exception e){
System.out.println("Catch");
e.printStackTrace();
System.out.println("Print");
}
}
}
一开始认为产生“抛出ClassCastException,因为SQLException并不是RuntimeException的一个实例”,但不然,这里涉及到了泛型和类型擦除。
参考官网文档中Type Erasure
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.
编译时,这里因为T extends Exception,泛型T绑定了Exception,所以T会替换为Exception,泛型信息丢失造成类型擦除,所以不会产生一个编译错误(SQLException转换成RuntimeException,但不会发生)。
看回字节码Test.class中try块(new Test().pleaseThrow(new SQLException())这句),我也看不出像引文一样的结果。
wang@Bottom:~/桌面$ javap -c Test
0: new #2 // class Test 3: dup 4: invokespecial #3 // Method "
":()V 7: new #4 // class java/sql/SQLException 10: dup 11: invokespecial #5 // Method java/sql/SQLException." ":()V 14: invokespecial #6 // Method pleaseThrow:(Ljava/lang/Exception;)V 17: goto 41 20: astore_1
但是发现执行的结果和引文中的结果“编译失败,因为编译器认为SQLException不会从try代码块中抛出”不同。
这样执行,try块会抛出了SQLException,catch块能捕捉到
wang@Bottom:~/桌面$ java Test
Catch
java.sql.SQLException
at Test.main(Test.java:11)
而修改catch代码块,把它修改为接收一个RuntimeException后,并没有被catch块能捕捉到,则是会被虚拟机捕获并打印出异常栈的信息
Exception in thread “main” java.sql.SQLException
at Test.main(Test.java:11)
public class Test{
private String name;
public static void main(String[] args){
Test m1 = new Test();
Test m2 = new Test();
m1.name = m2.name = "m1";
callMe(m1, m2);
System.out.println(m1 + "&" + m2);
}
public static void callMe(Test... m){
m[0] = m[1];
m[1].name = "New name";
}
}
这题不难,System.out.println(m1 + "&" + m2);
相当于
System.out.println(m1.toString() + "&" + m2.toString());
而上述代码中没有覆写toString(),所以直接调用Object的toString()
“`Java
//Object的toString()的源码
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
““
结果为Test@1f96302&Test@14eac69
import com.google.common.collection.Sets;
public class Test{
public static Set getBestFriendsClique(Person person){
Set result = Sets.newHashSet(person);
while ( (person.bestFriend != null) && (result.add(person.bestFriend)) )
{person = person.bestFriend;}
return result;
}
}
这题吧,和com.google.common.collection.Sets(Google Guava类库中的Sets)关系不大,直接上题解,
一直向bestfriend集合添加person对象,直到有一个person,它没有bestfriend,或者它的bestfriend已经在我们的result集合里了。
我们不能向这个Set集合添加重复的元素,即person对象,所以这个方法并不会导致无限循环。
真正的问题在于,这段代码很有可能造成内存用尽的异常(out of memory exception)。这个循环实际上是没有边界的,所以我们可以不停地往set中添加person对象,直到内存用尽。
原题上修改了点,方便输出结果
import java.util.*;
public class Test{
private static final List NAMES = new ArrayList(){
{ add("John"); System.out.println(NAMES); }
};
public static void main(String[] args){
new Test();
}
}
这道题我以为会编译失败,因为在代码块内NAMES是未知的。但是运行一遍后,显示结果为null。
其实双重花括号是个这个初始化常量集合的简便语法(好像1.2时问世的),第一层括弧(外面那层)实际是定义了一个内部匿名类 (Anonymous Inner Class),第二层括弧(里面那层)实际上是一个实例初始化块 (instance initializer block),这个块在内部匿名类构造时被执行(在构造器之前运行)。这里,我们用了一个匿名类来初始化一个List,当要打印NAMES时,实际上打印出来的是null,这是因为初始化程序尚未完成,此时的list是空的。
import java.util.*;
public class Test{
public static void main(String[] args){
Map collection = new TreeMap<>();
System.out.println( collection.compute("foo", (k, v) -> ( null == v )? new ArrayList;
System.out.println( collection.compute("foo", (k, v) -> ( null == v )? new ArrayList;
}
}
这题是真不会。这题的关键在于弄懂compute()这个方法。
显示结果
wang@Bottom:~/桌面$ java Test
[]
true
看回源码,compute实际上是“通过key在map中查找一个value。如果这个value是null,则插入(key, value),并返回value”。
因为开始时,这个list是空的,“foo”值并不存在,v是null。然后,我们向map中插入一个“foo”并且“foo”指向new ArrayList(),此时的ArrayList对象是空的,所以它打印出[]。
下一行,“foo”键值存在于map容器中,所以我们计算右边的表达式。ArrayList对象成功转换为List类型,然后“ber”字符串被插入到List中。add方法返回true,因此true就是第二行打印的内容。
//java.util.Map中的compute
default V compute(K key,
BiFunction super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
有兴趣的话可以尝试Java死亡竞赛,一次五题,每题90秒内作答。