gb-0526

 

静态检查与动态检查

由一道选择题引发的思考:


 

1.对于A、C选项很容易甄别,final声明的sb是不可变引用,不能new一个新的引用;unmodifiableList是集合的一个不可变形式,不能改变其中的内容了。

但是我以为C会和A一样,连静态检查都过不了,抱着好奇心去试了一下,发现C中的代码在IDE中编译时只是报了一个warning(不可变类型被修改),并没有error,在运行时才会触发一个  UnsupportedOperationException异常,表示对当前类型的操作错误。见下图:

 

 

 

但是对于A选项,我也试了一下,正如老师上课提到的那样,会连静态检查都过不了:

明明都是对不可变类型进行修改,为什么前者能通过静态检查呢?去看了下Collection的源码,找到了UnmodifiableList类对应的部分,发现他是继承了Collections.UnmodifiableCollection并且实现了List了的,并持有一个引用叫做list,这说明了两个问题:unmodifiableList是继承了List的add方法的,所以在静态检查时不会报错,IDE也只是给出一个warning;既然如此,为了达到不可修改的目的,只能使用异常警告程序员,而这样恰好只能在运行时的动态检查实现。这个UnsupportedOperationException异常是在这个类中重写的add方法中报的,同样的,set和remove方法也会抛出相同的异常。源码部分如下:

不修改List内容的get方法就没有抛出异常。

 

 

 由此又引出一个问题,unmodifiableList是真正意义上的不可变吗?其实不然,对于由list实例构建的unmodifiableList实例,对list实例进行修改时,unmodifiableList实例并非一成不变,而是随着list实例进行同步的改变。由此我们可以使用反射突破一下,去找到父类中list的字段进行修改。由于是静态内部类,需要去Object类中找。这里给出一个简单的实现:通过调用上面那个getDeclaredField方法访问到unmodifiableList的父类后,就可以对父类修改,进而影响到unmodifiableList了。若是想要生成真正意义上的不可变集合,可以使用Guava提供的ImmutableList,这里就不再深入阐述其原理了。


 

2.对于D选项,其实我不太有把握,但是我的理解是将int型、boolean型、double型变量和String型变量使用“+”运算时,会自动装箱,变成应的包装类,然后重载的运算符“+”将它们当做字符串连接在一起。用IDE检验了一下D选项,果然是对的,运行结果如下:,这里其实和python里面很类似。

 

 


 

3. 对于B选项,第一印象是能够运行,但是运行结果是错误的。之前在MIT的讲义中看到在使用迭代器循环时如果使用Collection.remove删除元素,可能产生意想不到的结果,见下图:然后让我在潜意识中认为它能通过静态检查和动态检查,但是结果是错误。直到我今天亲自动手尝试的时候才发现,其实它不能通过动态检查,在IDE中运行上面B选项的代码,静态检查会报出一个warning:,说明原写法不安全,可以用λ表达式进行替代。运行它,发现程序抛出异常ConcurrentModificationException

 

 

这里就需要解释一下ConcurrentModificationException异常了,ConcurrentModificationException与变量modCount有关,后者是一个表示list集合结构上被修改的次数,初始值为0。因为modCount在具体的实现类中的Iterator中才会使用,而add()和remove()方法会是modCount进行+1操作,modCount被修改后会产生ConcurrentModificationException异常,  这是jdk的快速失败原则。所以在运行时使用迭代器循环并且使用Collection.remove删除元素会抛出该异常。而使用λ表达式调用的是迭代器的remove函数,这样就不会产生异常了,运行结果也是对的。


 

综上,其实很多Java里面很多的细节需要深入了解底层实现(源码)才能发现它和自己的固有印象不相符,也需要多动手尝试才能发现这些问题,总结一下就是“纸上得来终觉浅,绝知此事要躬行”。

2022-06-12 00:59:36

posted on 2022-06-12 01:00  GloamingBlue  阅读(373)  评论(0编辑  收藏  举报

导航