复习目录
Data type in programming languages
数据类型Type 是一组值的集合,以及可以在这些值上所进行的操作。例如
- Boolean:真值 (true or false).
- int:整数如(0, 1, -47)
变量Variables用特定数据类型定义,可存储满足类型约束的值
如 String foo; 则foo是一个字符串类型的变量
Java 基本数据类型
- int (范围 ± \pm ± 2^31)
- Boolean (true or false)
- double (浮点数,实数的一部分)
- char (单个字符如 ‘A’ ‘a’ 等)
Java 对象数据类型
例如
- String 代表一个字符的序列
- BigInteger 任意大小的整数
基本数据类型都是小写字母,而对象数据类型开头字母都是大写的
两种类型的比较
基本数据类型 | 对象数据类型 |
---|---|
int, long, byte, short, char, float, double, boolean | Classes, interfaces, arrays, enums,annotations |
只有值,没有ID | 既有ID,也有值 |
Immutable 不可变的 | 部分可变(mutable),部分不可变 |
在栈中分配内存 | 在堆中分配内存 |
无法实现表达的统一 | 通过泛型实现表达的统一 |
对象类型可以形成层次结构
Object 是所有对象类型的根(父类)
类与类之间存在继承关系,子类可以从父类继承属性和方法也可以重写父类的方法来改变行为。
例如
class Dog extends Animal{…}
则Dog继承Animal,Dog是Animal的子类,同时Dog和Animal都是Objects的子类
Static vs. dynamic data type checking
在程序运行中可能会发生类型转换
int a=(int)18.7; // a=18
int a=18.7; //error!
更隐蔽的错误如
double a=2/3; //a = 0.0
Java 是静态类型语言a statically-typed language在编译阶段进行类型检查,当然也有动态类型语言如Python等,这类语言在运行阶段进行类型检查
- Static checking静态类型检查: 在运行之前检查bug
- Dynamic checking动态类型检查: 当程序运行时检查bug
- No checking无检查: 这类语言不会检查错误,需要自己观察,否则返回错误的结果
检查效果:静态类型检查 >> 动态 >> 无检查
静态类型检查:可在编译阶段发现错误,避免了将错误带入到运行阶段,可提高程序正确性/健壮性,静态类型检查可以检查:
- Syntax errors 语法错误
- Wrong names 类名/函数名错误,如Math.sine(2) 正确的应该为Math.sin(2)
- Wrong number of arguments 参数数目错误如Math.sin(30,20)
- Wrong argument types 参数类型错误,如Math.sin(“30”)
- Wrong return types 返回值类型错误,如声明返回String类型的返回值,却返回了int类型的值
动态类型检查可以检查
- Illegal argument values 非法的参数值
- Unrepresentable return values 非法的返回值
- Out-of-range indexes 越界
- Calling a method on a null object reference. 空指针引用
对比而言,静态倾向于检查一个变量的类型是否错误,动态检查倾向于检查可能出现的特定值导致的错误
Mutability & Immutability
改变一个变量Changing a variable:将该变量指向另一个值的存储空间
改变一个变量的值Changing a variable‘s value:将该变量当前指向的值的存储空间中写入一个新的值。
尽管程序不能没有变化,但是应该尽可能避免变化,以避免副作用
重要设计原则Immutability 不变性
Immutable types 不变数据类型:一旦被创建,其值不能改变。
如果是引用类型,也可以是不变的,即一旦确定其指向的对象,不能再被改变。需要通过final关键字来实现如
final int n =5;
则n的值只能是5。如果编译器无法确定final变量不会改变,就提示错误,这也是静态类型检查的一部分。
基于上述原因尽量使用 final变量作为方法的输入参数或者作为局部变量。
final特性:
- final类无法派生子类
- final变量无法改变值/引用
- final方法 无法被子类重写
相对于Imumutability,也有Mutability
Immutable object不变对象:一旦被创建,始终指向同一个值/引用
Mutable object 可变对象:拥有方法可以修改自己的值/引用
例子:
String 类是Immutable 类,一旦创建,则该String的值就不会改变
如果修改,则是新建一个String。
String s = "a";
s = s.concat("b");
Snapshot Diagram如下图所示
双边椭圆代表该对象为Immutable的
StringBuilder是mutable类,创建之后,可以通过方法修改,而不是新建一个StringBuilder
StringBuilder sb = new StringBuilder("a");
sb.append("b");
如下图所示:
这两者的区别在于如果存在多个引用指向一个对象,mutable类的值修改可能导致程序错误
例如
String t = s;
t = t + "c";
StringBuilder tb = sb;
tb.append("c");
Snapshot diagram如下图所示:
此时Immutable类s指向ab,t指向abc,程序正常,
而StringBuilder 则sb,tb都指向同一个对象,对tb执行的append,也会对sb起作用,可能会导致出现不希望出现的情况
mutable 类的优势在于:当使用不可变类型时,对其频繁修改会产生大量的临时拷贝(需要垃圾回收),而使用可变类型可以最少化拷贝以提高效率
例如StringBuilder类,如果需要频繁对一个字符串进行操作,使用StringBuilder类可以减少很多拷贝
//String
String s = "";
for(int i = 0; i < n; ++i){
s = s + n;
}
//StringBuilder
StringBuilder sb = new StringBuilder();
for(int i = 0; i < n; ++i){
sb.append(String.valueOf(i));
}
String s= sb.toString();
上方共创建n+1个String,下方仅一个StringBuilder和一个String。
由此可见使用可变数据类型,可获得更好的性能,也适合于在多个模块之间共享数据,类似于全局变量。
如何防御mutable带来的隐患?
通过防御式拷贝,给客户端返回一个全新的Date对象。但是大部分时候该拷贝不会被客户端修改,可能造成大量的内存浪费。此时如果使用不可变类型, 则节省了频繁复制的代价。
安全的使用可变类型:局部变量,不会涉及共享;只有一个引用
如果有多个引用(别名),使用可变类型就非常不安全。
使用Immutable类更“安全”, 在其他质量指标上表现更好
使用mutable还是immutable,需要在性能和安全性折中
下面是一个防御式编程的例子
public final class Period{
private final Date start, end;//Invariant: start <=end
//denfensively copies parameters
public Period(Date start, Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(this.start.after(this.end)){
throw new IllegalArgumentExcepiton(start + " > " + end);
}
}
//defensively copy fields
public Date start(){
return new Date(start.getTime());
}
public Date end(){
return new Date(end.getTime());
}
}
Snapshot diagram
code-level, run-time, and moment view
- 刻画运行中某一时刻的程序状态用于描述程序运行时的内部状态
- 便于程序员之间的交流
- 便于刻画各类变量随时间变化
- 便于解释设计思路
Snapshot diagram的元素
- 基本类型的值用单纯的值表示即可。箭头指向该值代表某一个变量或者对象的属性引用了该值
- 对象类型的值用椭圆表示,圈内部为对象的属性 这些属性可以带上他们的类型是什么
引用可以改派。(可以从这个例子看出 Immutable类用双线椭圆表示)
String s = "a";
s = s.concat("b");
另一个修改值的例子,表明值直接修改而不是修改引用
StringBuilder sb = new StringBuilder("a");
sb.append("b");
final表示不可变的引用 ,在Snapshot diagram中不可变的引用是用双线箭头表示的
但注意
//引用是不可改变的,但指向的值是可以改变的,
final StringBuilder sb;
//同样,可变的引用,也可指向不可变的值
String s;
例子:
String s1 = new String("abc");
List<String> list =new Arraylist<String>();
list.add(1);
s1 = s1.concat("d");
System.out.println(list.get(0));
String s2 = s1.concat("e");
list.set(0,s2);
System.out.println(list.get(0));
注意
第一次会输出 abc
第二次会输出 abcde
Snapshot Diagram如下图所示
Complex data types: Arrays and Collections
Arrays 数组
Arrays are fixed-length sequences of another type T
数组是存放固定数量T类型变量的数据结构
如
int[] A = new int [100];
这是个java中长度为100的整数数组,这个数组可以装下可能的int类型的数,但是长度无法改变
Collections 集合
List
Lists are variable-length sequences of another type T
List可以起到与数组同样的作用,但是长度可以变!
List<Integer> list = new ArrayList<Integer>();
一些List提供的操作
list.get(i);
list.set(i,n);
list.size();
需要注意的是
- List是一个接口Interface(不能被实例化)
- List的成员必须是对象类型(不能是int等)
Set
A Set is an unordered collection of zero or more unique objects
Set<Object> s1 = new HashSet<Object>();
s1.contains(e) //test if the set contains an element
s1.containsAll(s2)//test whether s1 ⊇ s2
s1.removeAll(s2)// remove s2 from s1
- 一个对象只能在set中出现一次,
- set是一个抽象接口
Map
A Map is similar to a dictionary (key-value)
Map<K, V> map = new HashMap<K, V>();
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
map像是一个映射表 从K映射到V
map也是一个抽象接口
Iterator
** a mutable type 迭代器**
迭代器的方法
- next() 返回集合中的下个元素 mutator method可以修改数据!
- hasNext() 判断集合中是否有下一个元素
- remove() 在集合中移除当前项
使用迭代器是因为如果使用for循环修改集合中的对象,程序会出现意想不到的错误
public static void dropCourse(Arraylist<String> subjects){
for(String subject:subjects){
if(subject.startsWith("6.")){
subjects.remove(subject);//尝试移除集合中所有以6.开头的字符串
}
}
}
程序在运行时会报错
可以通过迭代器来实现这样的操作
public static void dropCourse(Arraylist<String> subjects){
Iterator iter = subjects.iterator();
while(iter.hasNext()){
String subject = iter.next();
if(subject.startsWith("6.")){
iter.remove();//尝试移除集合中所有以6.开头的字符串
}
}
}
Useful immutable types
基本类型及其封装对象类型都是不可变的
Collections 提供了包装list,set,map的方法
Collections.unmodifiableList();
Collections.unmodifiableSet();
Collections.unmodifiableMap();
这种包装器得到的结果是不可变的:只能看
但是这种“不可变”是在运行阶段获得的,编译阶段无法据此进行静态检查
也就是说,如果代码中有对这样产生的集合进行修改,程序是不会报错的。只有运行时才会报错。