数据类型与类型检验
一.编程语言中的数据类型
1.Java中的数据类型
数据类型:一组值以及可以对其执行的操作。
变量:用特定数据类型定义,可存储满足类型约束的值。
Java中的数据类型分为基本数据类型和对象数据类型(引用数据类型),其中基本数据类型包括以下八种:
boolean,byte,short,int,long,float,double,char。
按照Java中的规定,基本数据类型都是小写字母形式,对象数据类型都以大写字母开头。
Java也提供了将基本数据类型包装为对象数据类型的方法,即Boolean, Integer, Short, Long, Character, Float, Double。
通常是在定义容器类型的时候使用它们(容器类型操作的元素要求是对象类型,所以需要对基本数据类型进行包装,转换为对象类型)。在一般情况下,尽量避免使用,因为这样会降低性能。
同时Java会自动实现二者之间的转换,如下图所示:
Java中基本数据类型与对象数据类型的区别如下图:
2.对象类型形成层次结构
所有对象数据类型的根都是Object类。所有类除了Object类都有一个父类。每个类都是其父类的一个实例。
3.操作器
二.静态和动态数据类型检验
1.静态检查
Java是一种静态类型语言。所有变量的类型在编译时已知,因此编译器可以推导表达式类型。而静态检查就是在编译阶段进行类型检查。
静态检查避免了程序在运行前可能出现的大量bug。比如说在编译时出现了类似"5" * "6"的代码,该语句试图将两个String类型的数据相乘,而在违反了String类型的规则,所以在编译的时候Java就会通过静态检查报错,这样就避免了直到程序运行的时候才能发现错误。
一些常见的静态检查如下:
综上可得静态检查的最大好处就是:可在编译阶段发现错误,避免了将错误带入到运行阶段,可提高程序正确性和健壮性。
2.动态检查
动态检查与静态检查相比最大的区别就是动态检验是在运行阶段进行类型检查。有些在编译时不能发现,而只能在运行时才能发现的错误就需要动态检查。
一些常见的动态检查如下:
总结:静态检查是关于“类型”的检查,不考虑值。而动态检查时关于“值”的检查。
静态检查 >> 动态检查 >> 无检查
三.可变性与不变性
1.改变变量与改变值
改变一个变量:将该变量指向另一个存储空间。
改变一个变量的值:将该变量当前指向的存储空间中写入一个新的值。
2.不变性
不变性是程序设计时的一个重要设计原则。
不变数据类型:一旦被创建,其值不能再改变。如果是引用类型,也可以是不变的:一旦确定其指向的对象,不能再被改变指向其它对象。
为了使某个引用不可变,可以使用Java中提供的关键字:final。在编译器进行静态类型检查时,如判断final变量首次赋值后发生了改变,会提示错误。
final相关内容如下:
3.可变类型与不可变类型
不变对象:一旦被创建,始终指向同一个值/引用。
可变对象:拥有方法可以修改自己的值/引用。
以String和StringBuilder对象为例:
通过上述例子可以发现:当只有一个引用指向该对象时,二者没有区别。但是有多个引用时,差异就出现了。具体差异如下:
由此可以发现,可变类型的好处如下:
- 使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收),而使用可变类型不会产生临时拷贝,可变类型最少化拷贝从而可以提高效率。
- 使用可变数据类型,可获得更好的性能。
- 可变数据类型也适合于在多个模块之间共享数据。
然而,可变数据类型也具有如下坏处:
4. 可变性使得难以理解程序正在做什么,更难满足方法的规约。而不可变类型更“安全”,在其他质量指标上表现更好。
5. 传递可变对象是一个潜在的错误源泉,一旦被无意中改变,则这种错误非常难于跟踪和发现。对其他程序员来说也难以理解。
4.防御式拷贝
当多个引用都指向同一个可变数据类型时,通过其中一个引用对数据进行修改后,其它引用得到的数据也会跟着变化,这就会产生错误。而使用防御式拷贝,给客户端返回一个全新的对象(即创造一个副本),这样客户端对其进行操作时不会对本来的数据进行修改。
但是大部分时候该拷贝不会被客户端修改,所以可能造成大量的内存浪费。
如果使用不可变数据类型,则节省了频繁复制的代价,因为不可变数据类型不需要防御式拷贝。
什么时候可以安全地使用可变类型对象呢?
- 局部变量,不会涉及共享
- 只有一个引用
若有多个引用(别名),使用可变类型就非常不安全。
5.快照图(Snapshot)
Snapshot 是一个code-level, run-time, and moment view。用于描述程序运行时的内部状态 ,好处是用于描述程序运行时的内部状态 ,便于程序员之间的交流,便于刻画各类变量随时间变化 ,便于解释设计思路。
- 基本类型的值
基本类型的值由bare constants表示。箭头代表对该值的引用。
- 对象类型的值
对象类型的值是由其类型标记的圆
- 不可变对象用双线椭圆表示,可变对象用单线椭圆表示。
//不可变对象String
String s = 'a';
s = s + 'b';
// 可变对象StringBuilder
StringBuilder sb = new StringBuilder("a");
sb.append("b");
- 不可变的引用
顾名思义,不可变引用即该引用只能指向一个位置,不能再次改变。Java实现不可变引用的方式是通过final关键字。在快照图中,不可变引用需要用双线箭头。
引用是不可变的,但指向的值却可以是可变的。可变的引用,也可指向不可变的值。
例:针对可变值的不可变引用
其中StringBuilder是可变对象类型。而变量sb前用final修饰,所以sb为不可变引用。而代码第三行尝试new一个StringBuilder对象并将其赋值给sb,而这就违反了sb的不可变性,所以由静态检查会发现该错误。
6.复杂数据类型:Arrays and Collections
(1)Array
数组是另一个类型T的固定长度序列。
int[] a = new int[100];
int []数组类型包含所有可能的数组值,但是一旦创建了特定的数组值,永远不会改变其长度。
数组类型的操作包括:
- indexing: a[2]
- assignment: a[2]=0
- length: a.length
(2)List
List是另一个类型T的可变长度序列。
List<Integer> list = new ArrayList<Integer>();
List的操作包括:
- indexing: list.get(2)
- assignment: list.set(2, 0)
- length: list.size()
注:List是一个接口;List中的成员必须是对象类型。
(3)Iterating
遍历一个数组
int max = 0;
for (int i=0; i<array.length; i++) {
max = Math.max(array[i], max);
}
遍历一个List
int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
(4)Set
Set是零个或多个对象的无序集合,其中每个对象在Set中最多出现一次。Set是一个抽象接口。
Set的操作包括:
s1.contains(e) //test if the set contains an element
s1.containsAll(s2) //test whether s1 ⊇ s2
s1.removeAll(s2) //remove s2 from s1
(5)Map
Map与字典非常相似,同时也是一个抽象接口。
Map的操作包括:
map.put(key, val) //add the mapping key → val
map.get(key) //get the value for a key
map.containsKey(key) //test whether the map has a key
map.remove(key) //delete a mapping
(6)List,Set,Map的具体实现
List,Set,Map都是接口。它们定义了这些类型有什么功能,但是不提供具体的实现代码。具体的实现代码在其实现类中完成。
List,Set,Map的实现类
List<String> firstNames = new ArrayList<String>();
List<String> lastNames = new LinkedList<String>();
List<String> firstNames = new ArrayList<>();
List<String> lastNames = new LinkedList<>();
Set<Integer> numbers = new HashSet<>();
Map<String,Turtle> turtles = new HashMap<>();
List,Set,Map的遍历
List<String> cities = new ArrayList<>();
Set<Integer> numbers = new HashSet<>();
Map<String,Turtle> turtles = new HashMap<>();
for (String city : cities) {
System.out.println(city);
}
for (int num : numbers) {
System.out.println(num);
}
for (int ii = 0; ii < cities.size(); ii++) {
System.out.println(cities.get(ii));
}
for (String key : turtles.keySet()) {
System.out.println(key + ": " + turtles.get(key));
}
(7)Iterator
迭代器是一个对象,它遍历一组元素并逐个返回元素。for(…:…)形式的遍历,调用的是被遍历对象所实现的迭代器。
迭代器中有两个常用方法:
next() 返回集合中的下一个元素—这是一个mutator方法!
hasNext() 测试迭代器是否已达到集合的末尾。
例:
List<String> lst = new ArrayList<>();
Iterator iter = lst.iterator();
while (iter.hasNext()) {
String str = iter.next();
System.out.println(str);
}
7.有用的不可变类型
- 基本类型及其封装对象类型都是不可变的
由上图可知,Collections实用程序类具有获取这些可变集合的不可修改视图的方法: - Collections.unmodifiableList
- Collections.unmodifiableSet
- Collections.unmodifiableMap
这种包装器得到的结果是不可变的:只能看,但是这种“不可变”是在运行阶段获得的,编译阶段无法据此进行静态检查。