原网页链接:
MIT Software Construction 课程网站 Reading 1: Static Checking
数据类型
类型(type)就是一组数据,以及对这些数据的操作
Java中的数据类型:
-
基本类型(primitive types)
如 int,long,boolean,double,char -
对象类型(object types)
如
String (一组的char)
BigInteger (可以表示任意大小的整数,不像int一样有范围的限制)
根据Java的惯例,基本数据类型都是小写字母组成的单词,而对象类型以一个大写字母开头
对数据的操作:
操作其实就是接受输入并产生输出的函数(有时还会改变输入数据本身的值)
Java中的操作有3中不同语法:
-
中缀、前缀或后缀操作符
如2个整数的“相加”操作 a + b,
就是调用了+ : int × int → int这样的操作(在这种表示法中:+是操作的名称,箭头前的int×int描述了两个输入的类型,箭头后的int描述了输出类型) -
对象的方法(method)
方法就是对某个对象的操作,
例如,bigint1.add(bigint2),就是对前面bigint1这个对象进行操作,把它的值加上了后面的bigint2。
调用了操作add: BigInteger×BigInteger→BigInteger -
函数
例如,Math.sin(theta)
调用操作sin: double→double。和对象的方法不同,Math不是一个对象,它是包含sin函数的类
操作的重载:相同的操作名作用在不同的类型上的情况就叫做重载。
例如对于Java中的基本数字类型,算术运算符+、-、*、/就被严重重载(它们可以被作用在int,long,double等类型上,数据类型是不同的,但是操作的名称是一样的,因此是重载)
方法也可以重载,大多数编程语言都有一定程度的重载
静态类型语言(statically-typed language)
Java是一种静态类型的语言。也就是说,所有变量的类型在编译时(程序运行之前)是已知的,因此编译器也可以推断出所有表达式的类型。如果a和b被声明为int型,那么编译器就会得出a+b也是int型的结论。实际上,Eclipse环境在你编写代码时就会这样做,所以当我们在写代码的时候就会发现许多错误(也就是warning和error),而不用等到运行才发生错误
静态类型是一种特殊的静态检查,这意味着在编译时检查bug。本课程中的许多想法都是为了消除代码中的bug,而静态检查是第一个方法。静态类型可以防止大量的bug感染您的程序:确切地说,是由于将操作应用到错误类型的参数而导致的bug。如果你写了如下的代码:
"5" * "6"
也就是说你尝试将两个字符串相乘,那么静态类型检查将在你敲出这行代码后就捕获此错误,而不是等到你执行程序后程序运行到这行代码的时候才报错
静态检查,动态检查,以及,没有检查
- 静态检查(Static checking):在程序运行之前就自动发现错误
- 动态检查(Dynamic checking):在代码执行过程中自动发现错误
- 没有检查:语言本身无法找到错误。你必须自己去检查(调试),否则就会得到错误的答案。
静态检查和动态检查的区别:
静态检查往往与类型错误有关,这些错误与变量的特定值无关。静态检查能保证一个变量会是某种类型而不会是其他类型(比如 int a,就保证了a在接下来的程序中会是int型)。
但是我们直到运行时才知道这些变量到底有哪些值。因此,如果错误只是由某些值引起的,比如“a/b”在运行时遇到b == 0的情况,或(数组等的)索引超出(数组的)范围,那么编译器就不会产生关于它的静态错误。
相比之下,动态检查往往与特定值引起的错误有关。
原始数据类型和实际数学上的数是有区别的
比如
- 整数的除法
int a = 5;
int b = 2;
int c = a/b;
//c == 2 rather than 2.5
这个例子中,5/2并不会返回一个分数,而是一个向下取整了的整数
-
整数的溢出
int,long都是有具体范围的整数,int的范围是 [-231,231-1],long的范围是 [-263,263-1],如果一个int或long,经过运算后的结果过大或过小,超过了应有的范围,就会发生溢出,过大的数会变成一个负数,过小的数会变成一个正数 -
浮点数的特殊值
像double一样的浮点数,就有NaN(不是一个数),POSITIVE_INFINITY(正无穷), NEGATIVE_INFINITY(负无穷)这三种特殊值
数组和集合
数组是一组特定长度的数据类型,我们可以这样定义一个数组
int[] a = new int[100];
因为有特定的长度,因此当数组的索引超过数组的范围时,就会有许多错误发生,这就是缓冲区溢出
因此不推荐使用固定长度的数组,而是使用列表(List)类型,这是一组包含许多数据类型T的可变长度的序列
List<Integer> list = new ArrayList<Integer>();
List 的一些操作:
-
取出索引处存放的值: list.get(2)
-
为索引处的元素赋值: list.set(2, 0)
-
得到list的长度: list.size()
注意,上面代码中List是一个接口,它是一种不能直接用new构造的类型,但是它指定了List必须提供的操作。我们将在以后的抽象数据类型的课程中讨论这个概念。ArrayList是一个类,一个提供这些操作实现的具体类型。ArrayList并不是列表类型的唯一实现,LinkedList是另一种。
你可以在Java API文档中查看它们,搜索“Java 10 API”即可。好好利用API,它们是你的好朋友(“API”指的是“应用程序编程接口”,这里指的是Java提供的帮助您构建Java应用程序的方法和类。)
需要注意的是,我们写的是List<Integer>
,而不是List<int>
。列表只知道如何处理对象类型,而不知道如何处理基础数据类型。在Java中,每个基础数据类型(通常用小写字母和缩写形式编写,如int)都有一个等价的对象类型(大写且全拼,如Integer)。Java要求我们在用尖括号参数化类型时使用这些等价的对象类型。
但是在其他情况下,Java会自动在int
和Integer
之间进行转换,因此我们可以编写int i = 5
而不会出现任何错误。
方法(method)
在Java中,程序语句通常必须在一个方法中,而且每个方法都必须在一个类(class)中,所以编写我们的hailstone程序(MIT的示例程序)的最简单方法是这样的:
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<Integer> hailstoneSequence(int n) {
List<Integer> list = new ArrayList<Integer>();
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中,self是一个名为this的隐式参数,永远不会成为方法参数)。静态方法不会在对象上调用。这与List add()方法或String length()方法不同,这些方法它们要求首先有一个对象。相反,调用静态方法的正确方法是使用类名而不是对象引用:
//add()方法是非静态的,作用在“list”这个对象上
//因此调用add()的时候要先写出对象的名字
List<Integer> list = new ArrayList<Integer>();
list.add(2);
//hailstoneSequence()是静态方法
//调用的时候直接写出类的名字,然后再跟着方法名
Hailstone.hailstoneSequence(83)
就我个人理解,非静态的方法其实是作用在一个具体的对象身上的,因此,需要先有这个“对象”,才能对其进行这个“方法”的操作。而静态方法就不一样了,它不是对某个对象的操作,因此不需要先声明一个对象。