数据类型与类型检验
- 静态/动态类型检验
- 可变/不可变的数据类型
- 可变数据的危险性
- 不可变数据的优越性
- 用Snapshot图理解数据类型
- 用集合类表达复杂数据类型
- Null的危害
Java 中的数据类型
注意几个小问题:
- object在初始化之后都是有ID的,而基本数据类型没有;
- class不都是mutable的!常见的immutable的class的有很多,比如将基本类型包装的对象类型:String BigInteger BigDecimal Boolean Integer Short Long Character Float Double等;
静态/动态类型检查
静态类型语言与动态类型语言的区别主要在于类型检查的时间:
静态类型检查在编译阶段进行检查,动态类型检查在运行阶段进行检查。
静态类型检查的部分bug可以自动在程序运行前被找到,而动态类型检查的bug只能在代码执行时被找到。
静态类型检查>>动态类型检查>>不检查
静态类型检查可以检查的错误:
- 语法错误
- 类名/函数名错误
- 参数数目错误
- 参数类型错误
- 返回值类型错误
动态类型检查可以检查的错误:- 非法的参数值(比如除零异常(稍微注意除零报的是异常),区别参数类型错误)
- 非法的返回值(注意区别返回值类型错误)
- 越界
- 空指针错误
静态类型检查是关于类型的,检测出的问题是不依赖于值(除非final关键字的赋值问题)的;而动态类型检查考虑值的检查
引入一下强类型语言与弱类型语言:
强类型:偏向于不容忍隐式类型转换。
弱类型:偏向于容忍隐式类型转换。
静态类型:编译的时候就知道每一个变量的类型,因为类型错误而不能做的事情是语法错误。
动态类型:编译的时候不知道每一个变量的类型,因为类型错误而不能做的事情是运行时错误。譬如说你不能对一个数字a写a[10]当数组用。
借用网络上的一张图描述强弱类型语言以及静态/动态类型检查的划分:
可变与不可变
主要内容PPT上描述很详细,只记录自己认为重要的点,嘿嘿
final方法也会被子类继承,但是无法被子类重写;
在性能上,不可变类型表现的较差,对其频繁的修改会产生大量的临时拷贝
可变类型可最少化拷贝以提高效率,也可以获得更好的性能,同时适用于在各个模块之间共享数据
防御式拷贝是针对可变类型的,不可变类型节省频繁复制的代价,与上面相反
程序快照 (作为代码层面运行时视图)
程序快照提供了一个将改变变量和改变变量的值可视化的方法
不可变对象用双线椭圆
不可变的引用用双线箭头
对于对象来说,final修饰的对象的引用是不可变的,但是指向的值是可变的;对于可变的引用,也可以指向不可变得值
迭代器暗中破坏
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
for (String s : list)
if (s.equals("2"))
list.remove(s);
for (String s : list)
System.out.println(s);
尝试此段代码会报如下错误:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:939)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:893)
at Plan.main(Plan.java:29)
查看原因:
迭代器内置的modcount与ExpectedModCount不匹配导致报错,modCount在ArrayList中定义为记录改变了ArrayList的size(即增减操作)的次数;
当ArrayList调用iterator()方法,初始化iterator时,将list的modCount值赋给expectedModCount,此时此刻,如果后续expectedModCount和modCount的值不变,或者同步改变保持相等,iterator.next()是不会抛并发修改异常的。既然抛了,它们肯定没有同步修改,直接跟进list.remove()到ArrayList.remove()方法中查看,源码如下:
跟进fastRemove()源码如下:
System.arraycopy()效果如下图:
回到刚才说的list.remove(),方法中modCount自增了,而expectedModCount是没有修改的,因此,在下一次iterator调用next()时,将抛出ConcurrentModificationException。
问题似乎已经弄明白了,我们再看看iterator.remove()方法为什么是OK的。如下是其源码:
首先调用外部类ArrayList的remove()方法,即前面图片中所示,其中修改了modCount。而此处,同时将expectedModCount置为modCount的值,它俩的值再次同步了,所以,iterator的remove()是OK的。
关于可能其它的地方也修改了modCount或expectedModCount的值,在上面代码中,已经确认,除了本文所述的地方存在修改以及list.add()修改了modCount,从迭代开始,是没有其它修改的。
结论:
在使用ArrayList过程中,当使用iterator迭代获取元素并需要移除元素时,需使用iterator的remove()方法移除元素。如果使用list.remove(),将在iterator下一次调用next()时抛出ConcurrentModificationException。如果中途调用list.add(),同样会修改modCount,导致抛出异常。