这篇文章是对软件构造课程第四节Data Type and Type Checking内容的总结。
这一节内容是为后续ADT内容的铺垫,ADT的构成之一就是数据。
一、Java中数据类型
Java中数据类型分为:基本数据类型(primitive types)和对象数据类型(object types)
以下是这两种类型的对照表:
在Java中基本数据类型都有其对应的被包装的对象数据类型,它们都是Immutable
二、静态数据检验与动态数据检验
静态数据检查:在编译阶段进行类型检验,一般来说,涵盖:语法错误(Syntax errors)、类名/函数名错误(Wrong names)、参数数目错误(Wrong number of arguments)、参数类型错误(Wrong argument types)以及返回值类型错误(Wrong return types)等方面错误。
动态类型检查:在运行阶段进行类型检查,一般来说,涵盖:非法的参数值(Illegal argument values)、非法的返回值(Unrepresentable return values)、越界(Out-of-range indexes)以及空指针(Calling a method on a null object reference)等方面错误。
静态类型检查:可在编译阶段发现错误,避免了将错误带入到运行阶段,可提高程序正确性/健壮性。一般来说,静态检查>>动态检查>>无检查。
三、可变数据类型与不可变数据类型
修改变量与修改变量的值的区别:修改变量是指将该变量指向另一个值的存储空间。修改变量的值是指将该变量当前指向的值的存储空间中写入一个新的值。
可变(Mutability)与不可变(Immutability)分为两类——值的可变与不可变 and 引用的可变与不可变。
可变数据类型:拥有方法改变自己的内部值或引用,即mutator方法。
不可变数据类型:一旦被创建,无法修改自己的内部值或引用,即没有mutator方法。
Java中final关键字:
1.final类无法派生子类。
2.final变量无法改变值/引用
3.final方法无法被子类重写
举例:Java中的String与StringBuilder,用Snapshot Diagram表示
不可变是一种非常重要的设计决策,它的意义在于不会给客户端任何机会来改变已有的内部值,从而避免很多隐形的Bug。但同时不可变也使得程序的灵活性下降,所以在软件设计中,肯定会涉及到可变数据类型的设计,不变性原则是指在设计中要尽可能多的使用不可变数据类型。
不可变数据类型对其频繁修改会产生大量的临时拷贝(需要垃圾回收)。可变类型最少化拷贝以提高效率可获得更好的性能,也适合于在多个模块之间共享数据。但可变数据类型是危险的,如果编程疏忽,它会产生很多隐形且难发现的bug。
防御式拷贝思想:在返回或接收一个可变数据类型时,不要直接返回或者接收,而是new一个新的对象拷贝这个可变数据类型的值,然后再返回或接收。
四、Snapshot diagram
Snapshot diagram是用于描述程序运行时的内部状态的一种图,便于程序员之间的交流、刻画各类变量随时间变化以及解释设计思路等。
绘制规则:
引用是不可变的,但指向的值却可以是可变的;可变的引用,也可指向不可变的值。
一个Snapshot diagram的例子
五、Java中的复杂数据类型——数组(Arrays)与集合(Collections)
数组的知识比较简单,这里展示一段简单的代码,涵盖数组的定义,遍历以及赋值取值等。
public static void main(String[] args) {
int[] numbers = new int[5];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i;
}
System.out.println(""+numbers[0]+numbers[1]+numbers[2]+numbers[3]
+numbers[4]);
}
运行结果:01234
集合(Collections)包含三类:List<E>,Set<E>以及Map<Key,Value>
List<E>的基本知识:
List<Integer> list = new ArrayList<>();
indexing: list.get(2)
assignment: list.set(2, 0)
length: list.size()
常见的循环遍历方式:
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
for (int i = 0; i < numbers.size(); i++) {
System.out.print(numbers.get(i));
}
System.out.println("");
for(int x : numbers){
System.out.print(x);
}
}
运行结果:
123
123
Set<E>的基本知识:
A Set is an unordered collection of zero or more unique objects.
一个涵盖基本方法与遍历的例子:
public static void main(String[] args) {
Set<Integer> numbers = new HashSet<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
for(int x : numbers){
System.out.print(x);
}
System.out.println();
System.out.println(numbers.contains(4));
System.out.println(numbers.contains(2));
numbers.remove(3);
for(int x : numbers){
System.out.print(x);
}
}
运行结果:
Map<Key,Value>的基本知识:
一个涵盖基本方法与遍历的例子:
public static void main(String[] args) {
Map<Integer,String> people = new HashMap<>();
people.put(1,"jane");
people.put(2,"mike");
people.put(3,"andy");
for(int x : people.keySet()){
System.out.println(x+":"+people.get(x));
}
System.out.println(people.containsKey(3));
System.out.println(people.containsKey(5));
people.remove(2);
people.put(1,"chris");
for(int x : people.keySet()){
System.out.println(x+":"+people.get(x));
}
}
运行结果:
迭代器遍历集合
Iterator as a mutable type 迭代器是一个用于遍历集合的一种抽象数据类型,一般在遍历集合类时,是不能修改集合本身的,即不可以使用remove方法。如果我们需要在遍历集合类的途中删除某些特定元素,那么我们可以使用迭代器,当然迭代器的功能远不止这个,只要是循环遍历时能做的,迭代器都能做到。
迭代器Iterator包含三个方法
1.next():返回下一个元素,并且调用后直接指向下一元素,同时并未做防御式拷贝,当类型为可变时,可以直接在客户端改变其值。
2.hasNext():判断是否还有下一元素。
3.remove():可选方法,迭代器去删除遍历到的该元素,同时会自动调整下标。
迭代器的具体实现原理,设计到委派的知识,将在后续章节中总结。上述对各方法的描述也并不绝对严谨,真正的说明参考迭代器自身的Spec。
以下给出一个使用迭代器的例子,其中我们先构造一个可变数据类型Person。
public class Person {
private String name;
public Person(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
具体例子
public static void main(String[] args) {
List<Person> numbers = new ArrayList<>();
numbers.add(new Person("mike"));
numbers.add(new Person("chris"));
numbers.add(new Person("roy"));
numbers.add(new Person("chris"));
numbers.add(new Person("andy"));
Iterator<Person> iter = numbers.iterator();
while (iter.hasNext()){
Person num = iter.next();
if(num.getName().equals("andy")){
num.setName("Andy");
}
if(num.getName().equals("chris")){
iter.remove();
}
}
System.out.println("List:"+numbers);
Set<Person> people = new HashSet<>();
people.add(new Person("mike"));
people.add(new Person("chris"));
people.add(new Person("roy"));
people.add(new Person("chris"));
people.add(new Person("andy"));
Iterator<Person> iter2 = people.iterator();
while (iter2.hasNext()){
Person p = iter2.next();
if(p.getName().equals("andy")){
p.setName("Andy");
}
if(p.getName().equals("chris")){
iter2.remove();
}
}
System.out.println("Set:"+people);
}
运行结果:
六、不可变封装
不可变封装是指将可变变为不可变,这种“不可变”是运行阶段获得的,编译阶段无法据此进行静态类型检查——“只能看,不能改”。
例子