MIT_6.031之静态检查

静态检查与动态检查

在编程语言中,静态检查动态检查是两种不同的自动错误检测机制:

  1. 静态检查:编译器在程序运行之前检查代码中的错误。这些错误通常是语法错误、类型错误或违反语言规则的情况。例如,Java 是静态类型语言,类型不匹配的错误会在编译时被捕获。

  2. 动态检查:错误只有在程序运行时才会被检测到。这些错误通常是与程序逻辑或数据输入相关的。例如,数组越界、除以零等错误。


Java 的整数除法与数值陷阱

Java 中的数值类型有其独特的特性,可能导致一些新手常犯的错误。以下是一些常见的陷阱:

1. 整数除法截断

在 Java 中,整数除法并不会返回小数,而是直接舍去小数部分,返回一个 截断的整数

1
int result = 5 / 2; // result 的值是 2,而不是 2.5

原因:整数类型 (int) 不支持分数,因此在进行除法操作时,任何小数部分都会被舍去。

2. 整数溢出

Java 中的 intlong 类型有固定的取值范围。int 的取值范围为 -2^312^31 - 1long-2^632^63 - 1。当结果超过这些范围时,就会发生整数溢出,并且不会抛出异常,而是返回错误的值。

1
2
int big = 200000;
big = big * big; // 结果应该是 40 亿,但实际结果是 -1454759936(溢出)

Java 并不会静态或动态地捕获溢出,导致可能产生难以察觉的错误。

3. 浮点类型中的特殊值

Java 的浮点类型(如 double)支持一些特殊的值:

  • NaN (Not a Number):表示无法定义的数字结果,例如 0.0 / 0.0
  • POSITIVE_INFINITY:表示正无穷大,例如 1.0 / 0.0
  • NEGATIVE_INFINITY:表示负无穷大,例如 -1.0 / 0.0
1
double result = 1.0 / 0.0;  // result 为 POSITIVE_INFINITY

某些操作(例如除以零或负数的平方根)会返回这些特殊值。


错误分析与练习

练习 1: 静态类型错误

1
2
3
4
int n = 5;
if (n) {
n = n + 1;
}

解释if 语句的条件必须是布尔值 (boolean),但 nint 类型,这是一个 静态类型错误。编译器会报错,提示类型不匹配。


练习 2: 整数溢出

1
2
int big = 200000;
big = big * big; // big 应该是 40 亿,但结果是溢出的负数

解释:这是整数溢出的例子。由于 int 的取值范围有限,结果超出了它的范围,溢出后的结果并没有被静态或动态捕获,导致错误答案。Java 的 int 类型最大值是 2147483647,所以 200000 * 200000 导致溢出。


练习 3: 截断整数除法

1
double probability = 1 / 5;  // 结果为 0 而不是 0.2

解释:由于 15 都是整数,Java 会执行 整数除法,并将小数部分截断。因此,结果是 0,而不是期望的 0.2。正确的做法是至少将一个操作数转换为浮点数:

1
double probability = 1.0 / 5;  // 结果为 0.2

练习 4: 动态类型错误

1
2
3
int sum = 0;
int n = 0;
int average = sum / n; // 动态类型错误:除以零

解释整数除以零在 Java 中会抛出 ArithmeticException。虽然没有编译错误,但在运行时会出现异常,因为整数除法不允许除以零。


练习 5: 浮点数除以零

1
2
3
double sum = 7;
double n = 0;
double average = sum / n; // 结果为 POSITIVE_INFINITY

解释:虽然 double 除以零不会抛出异常,但结果是特殊值 POSITIVE_INFINITY,因为除以零并不产生实数。Java 使用特殊值来处理这类情况。


数组和集合

1. 定长序列(数组)

1
2
3
int[] a = new int[100];  // 创建一个长度为 100 的数组
a[2] = 0; // 通过索引访问元素
int length = a.length; // 获取数组的长度

数组在创建时是定长的,这意味着一旦创建,长度不能改变。数组越界(即访问不存在的索引)会在运行时抛出异常,例如 ArrayIndexOutOfBoundsException


2. 变长序列(集合)

1
2
3
4
List<Integer> list = new ArrayList<>();
list.add(1); // 添加元素
int value = list.get(0); // 通过索引访问元素
int size = list.size(); // 获取集合的大小

集合是动态的,可以随着元素的添加或移除而改变大小。与数组不同,Java 的集合类不会有静态的长度限制,但索引越界仍然会抛出运行时异常。


遍历与不变量

遍历元素时,可以使用 for-each 循环:

1
2
3
4
int max = 0;
for (int x : list) {
max = Math.max(x, max); // 查找集合中的最大值
}

使用 final 声明不变量

1
2
3
4
public static List<Integer> hailstoneSequence(final int n) { 
final List<Integer> list = new ArrayList<>();
// 其他操作
}

在 Java 中,final 关键字可以用于声明常量或不变量,表明该变量一旦被初始化后就不能被修改。final 的使用有助于代码的可读性,并允许编译器进行静态检查,确保变量没有被不当修改。