定义:从正整数n开始,如果n是偶数,则下一个数是n/2,否则下一个数是3n+1,直到n等于1。
int n = 3;
while (n != 1) {
System.out.println(n);
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
System.out.println(n);
n = 3
while n != 1:
print(n)
if n % 2 == 0:
n = n / 2
else:
n = 3 * n + 1
print(n)
1.Java和Python的基本语法很相似,例如while 和 if
2.Java在每一个句子结束时要求以分号作为结尾。这看起来有些麻烦,更方便安排代码——例如你可以将一行代码写成两行然后以分号结束。
3.Java在使用 if 和 while需要添加圆括号。
4.Java使用花括号将一个语句块分割开来——python是以缩进作为区分。编程是一种交流,不仅要和编译器交流,还要和别的程序员交流,所以缩进也是必要的。
Python和Java最大的不同就是Java需要指定变量n的类型:int( 即需要指定n的数据类型)
一个数据类型是一系列值的集合,以及这些值对应的操作。
eg 5种常用的原始类型 :
int :例如5和-200这样的整数,但是必须在2^31之间,或者说是大概在±20亿
long:比int更大范围的整数,大概至2^63
boolean:对或错这两个值 true false
double :浮点数,其表示的是实数的子集
char:单个字符例如 ‘A’ 和 ‘$’
String 表示一串连续的字符。
BigInteger 表示任意大小的整数。
从Java来说,原始类型用小写字母,对象类型的大写字母开头。
操作符是一些能接受输入并输出结果的功能。句法虽然有所区别但是仍然认为他们是函数。下面是java和Python中三种不同的书写方式:
1.前缀、中缀、后缀操作符。 例如, a + b 调用这样一种操作(映射) + : int × int → int
( + 是这个操作符的名字, 箭头之前的int × int 描述了这两个输入, 最后的 int 描述了输出)
2.一个对象的方法. 例如, bigint1.add(bigint2) 调用这样一种操作(映射) add: BigInteger × BigInteger → BigInteger
3.一个函数. 例如: Math.sin(theta) 调用这样一种操作(映射) sin: double → double. 注意, Math 不是一个对象,它是一个包含sin函数的类。
比较Java 中**str.length()和 Python的len(str)**虽然都是输入一个字符串然后返回他的长度,但是却运用了不同的句法。
Java是一种静态类型的语言。在编译阶段(程序运行之前)所有的变量的类型都是已知的,因此编译器也可以推断所有表达式的类型。
eg:
如果a和b是int类型的,那么编译器就可以知道a+b是int类型的。idea和Eclipse编译环境在写代码时如果出错就会发现。
在动态类型的编程语言(Python)中,这种类型的检查是被推迟直到运行时才做的。
静态类型是特殊类型的静态检查,这意味着在编译时检查bug。静态检查是为了避免bug的发生。静态类型阻止了许多类型相关的bug:准确的说,是由于将操作符用到了不对应的类型对象上上。如果你写了这样一行不完整的代码:
"5" * "6"
对两个字符串进行乘法操作,然后静态检查就会在你还在编程的时候提示你这个错误,而不是等到这一行被执行的时候才说。
三种自动检查方法:
静态检查:bug在程序还没有被执行的时候被自动地检查出来。----》运行前
动态检查:bug在程序正在被执行的时候被发现 --------》运行中
无检查:编程语言根本不帮助你找到bug。你必须自己认真检查,不然就会最终得到错误的程序。
三个自动检查比较:静态捕获bug比动态捕获它要好,而动态捕获比根本不捕获它要好。
下面是一些各种类型的检查能检查出来的错误:
静态检查:
动态检查
静态检查倾向于类型错误,与变量的特定值无关。一个类型是一组值的集合,静态类型可以保证变量将从该集合中得到一些值,但是我们直到运行时才知道它到底有哪些值。因此,如果错误只会由某些值引起,比如除以零或索引超出范围,那么编译器就不会产生关于它的静态错误。
相比之下,动态检查往往是由特定值引起的错误。
原语类型不是真数
Java(许多其他的编程语言也一样)中的一个陷阱是它的原始数值类型的对象并不像我们熟悉的整数或者实数那样得到应有的输出。导致一些本应该被动态检查出来的问题没有被发现,下面是一些例子:
让我们更改冰雹序列的计算方法,以便将序列存储在一种数据类型中,而不是只将它打印出来。Java有两种我们能够使用的列表类型的数据类型:数组和列表
数组是另一种类型的固定长度的序列,例如,下面是如何声明一个数组变量。
int[] a = new int[100];
int[ ]包括所有长度的数组,但是一个特定数组,一旦被创建就确定了长度。数组类型上的操作包括:
下面是利用数组写的冰雹代码,它存在一些bug。
int[] a = new int[100]; // <==== DANGER WILL ROBINSON
int i = 0;
int n = 3;
while (n != 1) {
a[i] = n;
i++; // very common shorthand for i=i+1
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
a[i] = n;
i++;
在这个例子中,我们可以发现有些东西不太对劲,为啥数组的长度是100(100称为幻数)?如果我们尝试的n的冰雹序列长到大于100的话就无法使用这个数组。万一我们犯了错误,Java是否能够静态地、动态地检查出这个错误或者根本不检查?偶然地,像这样一个固定长度的数组的溢出在像C或者C++这样不太安全的语言中是非常常见的,而且被称为缓冲区溢出。这种溢出是大量网络安全漏洞和网络爬虫的罪魁祸首。
试试List类型而不是固定长度的数组,List类型是不定长度的,下面看我们如何声明一个List类型的变量:
List<Integer> list = new ArrayList<Integer>();
下面是List类型的一些操作:
下面是用列表编写的冰雹代码:
List<Integer> list = new ArrayList<Integer>();
int n = 3;
while (n != 1) {
list.add(n);
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
list.add(n);
for循环遍历数组或列表的元素,就像Python中的那样,尽管语法看起来有点不同。例如:
// find the maximum point of a hailstone sequence stored in list
int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
你可以遍历数组和列表。如果将列表替换为数组,则相同的代码将工作。
Math.max()是JavaAPI中的一个函数。Math类中有很多有用的函数。
在Java中,语句在方法中,每个方法在类中,所以编写冰雹算法最简单的方式是:
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的定义
public意味着程序中任何地方的任何代码都可以引用这个类或这个类中的方法。
其他访问修饰符(如private)用于在程序中获得更多的安全性。并确保不可变类型的不可变性。
static意味着该方法是一个不接受Self参数的函数(在Java中它是一个名为this的隐式参数,你永远不会将它看作一个方法参数)。静态的方法不能通过对象来调用,例如List add()方法或者 **String length()**方法,它们要求先有一个对象。静态方法的正确调用应该使用类来索引,例如:
Hailstone.hailstoneSequence(83)
另外,记得在定义的方法前面写上注释。这些注释应该描述了这个方法的功能,输入输出/返回,以及注意事项。记住注释不要写的啰嗦,而是应该直切要点,简洁明了。例如在上面的代码中,n是一个整型的变量,这个在声明的时候int已经体现出来了,就不需要进行注释。但是如果我们设想的本意是n不能为负数,而这个编译器(声明)是不能检查和体现出来的,我们就应该注释出来,方便阅读理解和修改。
“快照图”(snapshot diagrams)用来辨别修改一个变量和修改一个值的区别。给一个变量赋值:改变这个变量指向的对象(值也不一样)。
对一个可变的值进行赋值操作:例如数组或者列表,实际上是在改变对象本身的内容。
变化是“邪恶”的,好的程序员会避免可改变的东西,因为这些改变可能是意料之外的。
不变类型是指那些这种类型的对象一旦创建其内容就不能被更改的类型。
Java也给我们提供了不变的索引:声明的时候加上final,变量被初始化后就不能再次被赋值了 :
final int n = 5;
如果编译器发现你的final变量不只是在初始化的时候被“赋值”,那么它就会报错。换句话说,final会提供不变索引的静态检查。
正确的使用final是一个好习惯,就好像类型声明一样,这不仅会让编译器帮助你做静态检查,同时别人读起来也会更顺利一些。
在hailstoneSequence方法中有两个变量n和list,我们可以将它们声明为final吗?n不行,list可以。因为我们需要改变n指向的对象,而List对象本身的内容是可以更改的,我们也不需要改变list对应的对象
public static List<Integer> hailstoneSequence(final int n) {
final List<Integer> list = new ArrayList<Integer>();
黑客派的编程风格可以理解为“放飞自我并且乐观的”:
缺点: 在已经编写大量代码以后才测试它们
缺点: 将所有的细节都放在脑子里, 以为自己可以永远记住所有的代码, 而不是将它们编写在代码中
缺点: 认为 BUG 都不存在或者它们都非常容易发现和被修复.
而工程派对应的做法是:
优点: 一次只写一点点, 一边写一边测试. 在将来的课程中, 我们将会探讨"测试优先编程" (test-first programming)
优点: 记录代码的设想、意图 (document the assumptions that your code depends on)
优点: 静态代码检查将会保护你的代码不沦为“愚蠢的代码”
我们今天的主题是静态检查,下面是这一主题与这门课的关联: