java笔记--八成Java开发者解答不了的问题

引申自八成Java开发者解答不了的问题

1

如下代码,究竟会发生什么?

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<RuntimeException>().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 "<init>":()V
     7: new           #4                  // class java/sql/SQLException
    10: dup
    11: invokespecial #5                  // Method java/sql/SQLException."<init>":()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)
Print

而修改catch代码块,把它修改为接收一个RuntimeException后,并没有被catch块能捕捉到,则是会被虚拟机捕获并打印出异常栈的信息

Exception in thread “main” java.sql.SQLException
at Test.main(Test.java:11)


2

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


3.

import com.google.common.collection.Sets;

public class Test{

    public static Set<Person> getBestFriendsClique(Person person){
        Set<Person> result = Sets.newHashSet(person);

        while ( (person.bestFriend != null) && (result.add(person.bestFriend)) )
            {person = person.bestFriend;}

        return result;
    }
}

这题吧,和com.google.common.collection.SetsGoogle Guava类库中的Sets)关系不大,直接上题解,

一直向bestfriend集合添加person对象,直到有一个person,它没有bestfriend,或者它的bestfriend已经在我们的result集合里了。
我们不能向这个Set集合添加重复的元素,即person对象,所以这个方法并不会导致无限循环。
真正的问题在于,这段代码很有可能造成内存用尽的异常(out of memory exception)。这个循环实际上是没有边界的,所以我们可以不停地往set中添加person对象,直到内存用尽。


4

原题上修改了点,方便输出结果

import java.util.*;

public class Test{

    private static final List<String> NAMES = new ArrayList<String>(){
        { 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是空的。

5

import java.util.*;

public class Test{

    public static void main(String[] args){
        Map<String, Object> collection = new TreeMap<>();

        System.out.println( collection.compute("foo", (k, v) -> ( null == v )? new ArrayList<Object>(): ((List)v).add("bar") ));
        System.out.println( collection.compute("foo", (k, v) -> ( null == v )? new ArrayList<Object>(): ((List)v).add("ber") )); 
    }
}

这题是真不会。这题的关键在于弄懂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秒内作答。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值