这是国庆假期系列的第一篇。
6.031 软件构造
安装
这门课需要 JDK14,Eclipse 和 Git。
Eclipse 安装
首先到 https://www.oracle.com/java/technologies/javase-jdk14-downloads.html 下载 JDK14。
然后到 https://www.eclipse.org/downloads/ 下载 eclipse 安装程序。
- 运行安装程序,选 ADVANCED MODE,使用 URL
https://raw.githubusercontent.com/6031/eclipse031/master/setup/eclipse-for-6031.setup
- 应该自动选择 Java 14,如果没有的话请手动选择
- Next,不选择任何项目
- Next,选择安装位置,Finish
Eclipse 配置
打开 Preferences
- 打开
Java -> Installed JREs
,确保使用的是 Java SE 14 或者 14.0.2- 打开
Edit..
,在 Default VM arguments 输入-ea
,Finish
- 打开
- 打开
Java -> Compiler
,把 compiliance level 设置成 14 - Apply and Close
Git 安装
Git 在 https://www.git-scm.com/ 下载安装。
# git --version
git version 1.8.3.1
# git config --global user.name "username"
# git config --global user.email username@mail.somewhere.edu.cn
# git config --global alias.lol "log --graph --oneline --decorate --color --all"
# git config --list
Github
Github 是一个远程存放 Git repo 的网站。
登录 Github 后,你可以在 Settings -> SSH and GPG keys
页面
Git 操作
clone
# git clone URI-of-remote-repo
# git clone URI-of-remote-repo repo-name
commit
# git add file-name
# git commit
如果你 add
了但是没有 commit
,可以
# git status
diff
# git diff
# git diff --staged
工作流
# git pull
# do something
# git add something
# git push
查看记录
# git log
# git show
先修
在 MIT,这门课的先修是 6.009 Foundamentals of Programming,也就是基本的 Python 编程。
如果之前的编程经验较少,需要在 6.031 中加倍的努力。
如果学习 Java 有困难,可以去看 OCW 6.092 或者 Codecademy Java。
内容
- 静态检查
- Java 基础
- 代码审查
- 测试
- 版本控制
- Specifications
- 可变性与不变性
- 避免调试
- 抽象数据类型
- 抽象函数和表示不变性
- 使用接口、泛型、枚举和函数定义抽象数据类型
- 调试
- 递归
- 等价性
- 递归数据类型
- 正则表达式
- 解析器
- 使用抽象数据类型写程序
资料
- Java API,https://docs.oracle.com/en/java/javase/14/docs/api/
- Codecademy Java,https://www.codecademy.com/learn/learn-java/modules/learn-java-hello-world/cheatsheet
静态检查
Hailstone 序列
我们以 Hailstone 序列为例子,这个序列从 n
开始,到 1
结束
- 如果
a[n]
是偶数,那么a[n+1]=a[n]/2
- 如果
a[n]
是奇数,那么a[n+1]=3*a[n]+1
我们可以这样计算这个序列
int n = 7;
while (n!=1) {
System.out.printf("%d, ", n);
if (n%2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
System.out.printf("1");
类型
Java 和 Python 最大的不同是,当我们声明一个变量的时候我们要声明类型
int n = 7;
一个类型是值的集合,同时还定义了一些作用在这些值上的操作。
Java 的 primitive types,例如
int
,4 字节long
,8 字节boolean
double
char
Java 的 object types,例如
String
BigInteger
静态类型
Java 是一门静态类型的语言。所有变量的类型在编译时就已经知道了。而动态类型语言,比如 Python,要到运行时才知道变量的类型。
静态类型是静态检查的一种,静态检查指在编译时检查程序有没有 Bug。这门课的主要内容就是如何避免软件存在 Bug,而静态检查是我们做的第一步。
动态类型语言的静态类型支持
比如 PEP 484,Type Hints,https://www.python.org/dev/peps/pep-0484/。
从 Python 3.5 开始支持声明 type hints,例如
def hello(name:str)->str:
return 'Hi, ' + name
像 mypy(http://mypy-lang.org/) 之类的 checker 会在程序运行之间检查有没有静态错误。
再比如 Typescript
function hello(name:string):string {
return 'Hi, ' + name;
}
这种支持显示出软件工程师的一种普遍共识:使用静态类型是构建和维护大型软件系统的基础。接下来我们会解释这种共识的理由。
与 Java 这种静态类型的语言不同,给动态类型语言提供静态类型支持,这种方法又被称为 Gradual typing。也就是说程序里有的部分有静态类型声明,有的部分没有。Gradual typing 提供了一种,从小的实验原型构造大型稳定可维护系统的,更平缓的路径。
静态检查,动态检查
静态检查是指在程序运行之前找 Bug。
- 语法错误
- 拼错的名字
- 参数数量/类型错误
- 返回类型错误
动态检查是指在程序运行的过程中找 Bug。
- 不合法的值
- 不合法的转换
- 越界
- 调用
null
对象引用的方法
大体来说,静态检查是和类型有关的。
另外,在 Java 和很多编程语言中,数字类型和我们熟悉的整数/实数概念不同。这可能导致有一些我们希望动态检查的错误查不出来,比如
- 整数除法,
5/2
会得到2
- 整数溢出
- 浮点型的特殊值,比如
NaN
,POSITIVE_INFINITE
,NEGATIVE_INFINITY
数组和集合
数组是一种固定长度的序列
int[] a = new int[100];
// a[2]
// a[2] = 0
// a.length
List 是一种可变长度的序列
List list = new ArrayList();// list.get(2)// list.set(2)// list.size()
注意 List
是一个接口,ArrayList
是一个类,实现了 List
这个接口。而 ArrayList
则是因为 Java 要求在参数化一个类型的时候必须使用对象类型而不是基本类型。
List list = new ArrayList();int n = 3;while (n != 1) {
list.add(n);if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
list.add(n);
迭代
List 可以这样迭代
int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
方法
Java 里,语句都在一个方法内,方法都在一个类内。
所以 hailstone 程序可以写成
public class Hailstone {
/**
* Compute a hailstone sequence.
* @param n starting number for sequence; assumes n > 0.
* @return hailstone sequence starting with n and ending with 1.
*/
public static List hailstoneSequence(int n) {
List list = new ArrayList();while (n != 1) {
list.add(n);if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
list.add(n);return list;
}
}
其中 public
表示任何代码,你的程序中的任何地方,都可以使用这个类或者方法。而 private
就有更好的安全性,保证不可变类型的不可变性。
其中 static
表示这个方法并不把 self
作为参数传入,或者在 Java 里叫 this
。
改变/再分配
改变是必要的恶,好的程序员应该极可能避免改变。
如果一个参考是不会被改变的,可以用 final
final int n = 5;
final
可以在静态检查中检查你有没有做 reassignment。
这门课的目标
- safe from bugs
- easy to understand
- ready to change
Java 基础
快照图
快照图很好用
int n = 1;
double x = 3.5;
BigInteger val = new BigInteger("123456789");
String s = "hello";
Point pt = new Point(5, -3);
String s = "a";
s = s + "b";
StringBuilder sb = new StringBuilder("a"); sb.append("b");
final int n = 5;
final StringBuilder sb = new StringBuilder("a");
sb.append("b");
String s = "a";
s = "ab";
==/.equals
对于基本类型,应该用 ==
,对于对象,应该用 .equals()
。
x == y
, truex == z
, falsex.equals(y)
, truex.equals(z)
, true
Java 集合
List
Java 的 List 和 Python list 很像。
int count = lst.size()
lst.add(e)
if (lst.isEmpty()) ...
lst.contains(e)
Map
Map 很像 Python 的 dictionary。
map.put(key, val)
map.get(key)
map.contains(key)
map.remove(key)
Set
Set 是一个无序的集合,并且里面的元素互异,有点像 Python set。
s1.contains(e)
s1.containsAll(s2)
s1.remove(s2)
Literals
在 Java 里,这样只能创建 array,不能创建 list
String[] arr = {"a", "b", "c"};
要创建 list 必须这样
List.of("a", "b", "c");
但是要注意,通过 List.of()
创建的对象是不可变的。所以 add, remove, replace
都是不可以的。
另外我们还有
Set.of("a", "b", "c");
Map.of("apple", 5, "banana", 7);
泛型
在 Java 里,我们可以限制集合中元素的类型,这样就可以做静态检查
List cities;
虽然必须用对象类型来写泛型,但是下面这种情况会自动转换
sequence.add(5);
int second = sequence.get(5);
ArrayList 和 LinkedList
List
,Set
和 Map
都是接口,它们定义了对应的类型如何工作,但是并没有给出实现代码。
比如 List
的实现
List firstNames = new ArrayList();
List secondNames = new LinkedList();
还可以简写
List firstNames = new ArrayList<>();
List secondNames = new LinkedList<>();
刚才我们说 List.of()
会创建不可变的对象,如果需要可变的可以
List firstNames = new ArrayList<>(List.of("a", "b", "c"));
HashSet 和 HashMap
如果需要一个 Set
Set numbers = new HashSet<>();
如果需要有序集合
SortedSet numbers = new TreeSet<>();
如果需要一个 Map
Map turtles = new HashMap<>();
迭代
对 List, Set
可以做迭代
// List cities = new ArrayList<>();
for (String city : cities) {
System.out.println(city);
}
// Set numbers = new HashSet<>();
for (int num : numbers) {
System.out.println(num);
}
对 Map
,我们可以对 key
做迭代
// Map turtles = new HashMap<>();
for (String key : turtles.keySet()) {
System.out.println(key + ": " + turtles.get(key));
}
注意,如果在迭代过程中发生了改变,迭代会中断。
枚举
Java 支持用 enum
来构造枚举
public enum Month {
JANUARY, FEBRUARY, MARCH, APRIL,
MAY, JUNE, JULY, AUGUST,
SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER;
}
public enum PenColor {
BLACK, GRAY, RED, PINK, ORANGE,
YELLOW, GREEN, CYAN, BLUE, MAGENTA;
}
然后就可以
public static void main(String[] args){
PenColor drawingColor;
drawingColor = PenColor.BLACK;
System.out.println(drawingColor);
}
注意初始化和赋值要分开,这样才能做静态检查。