数据类型基础知识
数据类型: 一组值以及可以对其执行的操作
变量: 用特定数据类型定义,可存储满足类型约束的的值
java中包括基本数据类型和对象数据类型,二者对比情况:
基本数据类型 | 对象数据类型 |
---|---|
int, long, byte, short, char, float, double, boolean | Classes, Interfaces, arrays, enums, annotations |
只有值,没有ID(与其他值无法区分) | 既有ID,也有值 |
不可变的(immutable) | 一些可变(mutable),一些不可变 |
在栈中分配内存 | 在堆中分配内存 |
无法实现表达的一致 | 可以实现表达和泛型的一致 |
代价低 | 代价高 |
对象类型的层次结构:
继承: 如果类A继承了类B,A称为B的子类,B称为A的超类,子类会获得超累所有可见的实例域和方法,并且可以通过重写来改变方法的行为,Java中所有的类都继承自Object类,如果一个类没有显式地继承一个类,则会继承Object。
包装基础数据类型: 通过将基础数据类型包装为对象数据类型(Boolean,Integer,Short,Long,Character,Float,Double)以实现表达的一致性,通常实在定义容器类型时使用,但会降低性能,故在一般情况下尽量避免使用。
//e.g.
List<Integer> list = new Array<Integer>();
list.add(1);
//1不是Integer对象类型,但编译能自动完成转换,但会降低效率,等价于
list.add(Integer.valueof(1));
静态/动态类型检查
静态类型语言(如java): 所有变量的类型在编译时已知,因此编译器可以推导表达式类型,在编译阶段进行类型检查。
例:如果变量 a 和变量 b 被定义为 int 型,编译器会推导出 a+b 也是 int 型。
动态类型语言(如Python): 在运行阶段进行类型检查
静态检查内容
为关于类型的检查,在编译阶段进行检查,避免将错误带入到运行阶段。
- 语法错误(动态类型语言也会进行此类检查)
- 类名/函数名错误
- 参数数目错误
- 参数类型错误
- 返回值类型错误
动态检查内容
为关于值的检查,如除零错误。
- 非法的参数值
- 非法的返回值
- 越界
- 空指针
可变性和不可变性
两种变化
改变一个变量: 将该变量指向另一个存储空间
改变一个变量的值: 在该变量指向的存储空间中写入一个新的值
变化是麻烦的源头,但程序不能没有变化,好的程序员应该尽可能避免变化以避免副作用。
不变性
不变性: 一种重要的设计原则
不变数据类型: 一旦被创建, 其值不能改变
不变引用: 一旦确定其指向的对象,不能再改变指向其他对象,如Java中的 final 关键字
关于final的补充:fianl类无法派生子类,final变量无法改变值或引用,final方法无法被子类重写
可变对象与不可变对象
不可变对象: 一旦被创建,始终指向同一个值或引用,如String类
对于不可变对象String执行如下代码:
String s = "a";
s = s.concat("b");//这一方法并不会改变原先的值,而是创建一个新的String对象
不可变对象: 拥有方法可以修改自己的值或引用,如StirngBuilder类
对于可变对象StringBuilder执行类似代码
StirngBuilder sb = new StringBuilder("a");
sb.append("b");
二者对应的snapshot diagram如下:
在上述例子中,只有一个引用指向该对象,二者并没有区别,但有多个引用时,差异就出现了
在上述代码的基础上继续执行:
Sting t = "c";
t = t + "c";
StringBuilder tb = sb;
tb.appedn("c");
二者对应的snapshot diagram如下:
可变对象的优点: 在使用不可变类型时,对其频繁修改会产生大量的临时拷贝,而可变类型往往会进行最小化拷贝以提高效率,并且可变类型也适合在多个模块之间共享数据。
但是可变性可能使得难以理解程序在做什么,更难满足方法规约,此时不可变类型更加安全,在其他质量指标上表现更好。
可变对象的风险:
- 传递可变值: 将一个可变对象作为方法的参数传入 ,若该方法在无意中改变了这个对象的值,则方法外部的对象也会随之改变,会产生难以察觉的错误,同时,这样的程序会变得难以理解。
/**@return the sum of the number in the list */
public static int sum(List<Integer> list){
int sum = 0;
for(int x : list)
sum += x;
return sum;
}
/**@return the sum if the absolute values of the numbers in the list */
public static int sumAbsolute(List<Integer> list){
// let's reuse sum(), first wi take absolute values
for(int i = 0; i < list.size(); ++i){
list.set(i, Math.abs(list.get(i)));
return sum(list);
}
// meanwhile, somewhere else in the code...
public static void main(Stirng[] args){
List<Integer> myData = Arrays.asList(-5, -3, -2);
System.out.println(sumAbslute(myData)); //the list myData will change to [5, 3, 2]
System.out.println(sum(myData)); //the result will be 10
}
- 返回可变值: 将一个可变对象的值作为返回值,在接收该变量并改变其值后,可能会产生错误。
/** @return the first day of spring this year */
public static Date startOfSpring(){
if(groundhogAnswer == null)
groundhogAnswer = askGroundhog();
return groundhogAnswer;
static Date groundhogAnswer = null;
}
public static void main(String[] args){
Date partyDate = startOfSpring();
partyDate.setMonth(partyDate.getMonth() + 1);
Date homeDate = startOfSpring(); //what the homeDate wiil be?
}
安全使用可变对象:
- 在仅有一个引用时使用,如作为局部变量使用
- 防御式拷贝,返回一个全新的对象,但是可能造成内存浪费,而不可变对象不需要防御式拷贝
return new Date(groundhogAnswer.getTime());
复杂数据类型
Array: 数组是另一种数据类型T的定长序列
List: List是另一种数据类型T的边长序列
Set: Set是0个或多个唯一对象(互不重复)的无序集合
Map: Map是一种类似字典的(key-value)集合
Iterator: 一个遍历一组元素并逐个返回元素的对象
不可变包装
List,Set,Map的通用实现是可变的,Collections 类可以获得这些可变集合的不可变观察,这种 “不可变” 实在运行阶段获得的,编译阶段无法据此进行静态检查
List<String> list = new ArrayList<>();
list.add("ab");
List<String> listCopy = Collections.unmodifiableList(list);
listCopy.add("c"); //执行时会抛出异常
list.add("c");
System.out.println(listCopy.size()); //如果注释掉抛出异常的代码,输出值为2
两种主要用法:
- 当集合构建完成后将其转变为不可变类型
- 对特定用户对数据结构访问设置只读权限