4 Data Type and Type Checking
1.编程语言的数据类型
在Java中,数据类型分为基本数据类型(int、boolean、char等)和对象数据类型(String、Integer等)。
🔺基本数据类型(int, long, byte, short, char, float, double, boolean)都是Immutable的;栈(局部变量;成员变量在堆中)中分配内存,且仅在使用时存在;没有统一的表达;代价比较低。
🔺对象数据类型(类、接口、数组、枚举、注释)有的是Immutable的,有的是Mutable的;分配的内存都在堆中(局部变量对象引用存在栈中,对象存在堆中;成员变量在堆中),自动垃圾回收;可用泛型统一表达;代价相对昂贵。
因此在能使用基本类型的情况下尽量使用基本数据类型,降低代价。
对象数据类型是OOP的核心,由于对象数据类型存在继承(extends)机制,因此在OOP中可以更好的复用代码。
基本类型被包装为对象类型,通常只有在定义集合的时候使用,其他情况下尽量避免使用。基本类型和对象类型之间一般可以自动转换。
2.静态类型检查——重在类型
静态类型语言,如Java,可在编译阶段进行类型检查,发现语法错误、函数名/类名错误、返回值类型错误等错误。
例如:
int n = 1;
if (n) {
n = n + 1;
}
错误提示: Type mismatch: cannot convert from int to boolean;
3.动态类型检查——重在数值
动态类型语言,如Python,只有动态类型检查,在运行阶段才进行类型检查,发现如非法参数值、非法返回值、越界、除0、空指针等错误。
静态类型检查 >> 动态 >> 无检查
4.Mutability and Immutability
改变一个变量: 将该变量指向另一个值的存储空间———改变引用
改变一个变量的值: 将该变量当前指向的值的存储空间中写入一个新的值———改变值
Note :final类无法无法被继承;final变量无法改变值/引用(注意区别);final方法无法被子类重写;
final修饰的变量表示赋值之后不能再进行更改;
final修饰的成员变量需要在定义时就初始化或在构造方法中初始化
对于基本类型,final使数值恒定不变;
而对于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。
不变对象 Immutable types: 一旦被创建,始终指向同一个值/引用,更安全。若要改变内部值,需要构造新的对象。
缺点: 使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)。
可变对象: 拥有方法可以修改自己的值/引用,更灵活。如StringBuilder
但是在质量指标中,性能的优先级较低。
区别:
例子: mutable潜在的风险,这种情况非常难以跟踪发现,也难以被其他开发者理解
/* @return the sum of the numbers in the list */
public static int sum (List<Integer> list) {
int sum =0;
for (int x: list) sum += x;
return sum;
}
/* @return the sum of the absolute values of the numbers in the list */
public static int sumAbsolute (List<Integer> list) {
// let's reuse sumo), because DRY, so first we take absolute values
for (int i =0; i<list.size(); i++)
list.set(i, Math.abs(list.get(i)));//改变了list内容
return sum(list);
}
//client
public static void main(String[] args) {
List<Integer> myData=new ArrayList<Integer>();
myData.add(-5);
myData.add(-3);
myData.add(-2);
System.out.println(sumAbsolute(myData)); //期望值10,实际值10
System.out.println(sum(myData)); //期望值-10,实际值10
解决的办法: 通过防御式拷贝——给客户端传送一个全新的Date对象(除地址外一样),客户端即使对数据做了更改,也不会影响到自己。我们解决了外部对内部的无意改动,但为此付出的代价就是空间的浪费。
而如果使用immutale类型的数据,就不存在这种风险。
同时,我们编程的时候也要注意避免出现一个对象的多个引用,也就是说尽量不要让一个对象出现别名。
5.代码快照(Snapshot diagram)
Primitive values 基本类型的值:
基本类型的值由常数表示。 传入箭头是对变量或对象字段中的值的引用
Object values 对象类型的值:
对象类型的值是一个按其类型标记的圆。当我们想要显示更多的细节时,我们在里面写字段名,用箭头指向它们的值。对于更详细的信息,字段可以包括其声明的类型。
注意:
不可变对象: 用双线椭圆
不可变的引用(final声明): 用双线箭头
引用是不可变的,但指向的对象却可以是可变的——final StringBuilder sb
可变的引用,也可指向不可变的对象——String s
集合类型:
List:
Set:
Map:
注意: 遍历集合类中的元素并对集合做修改时,要使用Iterator来做迭代器,避免bug。
上述的这些集合类型被传到其他地方时有可能会被更改,于是可用
Collections.unmodifiabbleList()
、Collections.unmodifiabbleSet()
、Collections.unmodifiabbleMap()
做一次封装,这样给外界的时候就是一个不可更改的类型了;
而且如果封装前的类型的对象做了增删改,那么封装后的也会表现出增删改后的状态,因为实际上这两个指向的是同一块内存区域。如下,list有改动,listCopy也会被改动
List<String> list = new ArrayList<>();
List<String> listCopy = Collections.unmodifiableList(list);
list.add("a"); //list中有一个元素a,listCopy中也有了一个元素a