MIT6.031Software construction学习笔记(二)

Java基础(Basic Java)

开始学习Java(Getting started with Java)

语法基础(Language basics)

这部分要求先掌握Java的基本语法知识,可以用Eclipse中的Java Tutor做练习,可以通过Java API文档来学习,也可以看看这个东西语法基础。然后有一个小练习。

int a = 5;     // (1)
if (a > 10) {  // (2)
    int b = 2; // (3)
} else {       // (4)
    int b = 4; // (5)
}              // (6)
b *= 3;        // (7)

假设我们在Java中编写了上述代码,那么哪一行会导致编译错误呢?答案是第7行。该代码声明了两个变量名字都是b,其中过一个在if分支中,另一个在else分支中,当我们编译到第7行时,对b的引用会模糊不清,从而引发编译错误。

要对上述代码修改正确,修改最少的一种方式,是这样的:

int a = 5;     // (1)
int b;		   // new
if (a > 10) {  // (2)
    b = 2; 	   // (3) int b = 2;
} else {       // (4)
    b = 4; 	   // (5) int b = 4;
}              // (6)
b *= 3;        // (7)

继续看修改之后的代码,如果这时把else分支中的b = 4;注释掉,会发生什么呢?在程序运行之前,Java编译器就会报错了。Java编译器规定,在我们访问一个变量值之前,必须保证这个变量是被声明过的,不仅如此,还需要保证这个变量一定已经赋过值了,显然,在上述更改之后的代码中,如果把else中代码注释掉,那么在a<=10的情况下,运行到第8行(原第7行)对b的引用时,b仍没有一个确定的值,Java编译器是会报错的。

数字和字符串(Numbers and strings)

这部分可以先阅读一下数字和字符串,然后课程要求我们做一下Java Tutor的练习。之后又有几个小练习。

fahrenheit = 212.0
celsius = (fahrenheit - 32) * 5/9

上述代码是一段Python代码,用于将华氏度转化为摄氏度。请问这段代码能否输出正确的转化结果?答案是。在Python中,首先fahrenheit变量被赋以浮点值212.0,在celsius的赋值语句中的右侧算术式是从左往右算,从fahrenheit开始,之后的运算都是浮点型,最后也就能得出正确答案。

先对上述Python代码改成Java形式,就要对变量有类型声明,在不改变代码语句的前提下,正确的类型声明应该是fahrenheitcelsius都声明为double类型。Java中的浮点类型都是double而不是float,每个基础类型都有一个对应的对象类型。有的时候必须用对象类型,比如说double对应Double,但通常我们不会用对象类型来声明普通的变量。至于上述代码的第二行,练习题给的答案是这样的:celsius = (fahrenheit - 32) * (5. / 9);这也不难理解,需要注意的是5是浮点数,这样5./9才是浮点运算,而不是整数运算得0.不过,这样的写法也说得过去celsius = (fahrenheit - 32) * 5 / 9;毕竟运算是从左向右的,不过前一种写法看起来更舒服也更明确。

要想把最终结果输出出来,一种正确的方式是System.out.println(fahrenheit + " -> " + celsius);,打印变量的值,用+作连接,字符串常量要用""。还有一种比较冗长的写法是System.out.println(Double.toString(fahrenheit) + " -> " + Double.toString(celsius));这种写法是把浮点数转化成了字符串输出,虽然结果也对,但这种转换是多余的。

使用对象(Using objects)

关于使用对象的知识就读这个吧对象

Hello, world!

现在可以写一个HelloWorldApp.java小程序了,按照教程Hello World!编译运行可以在控制台输出Hello World!了。

快照图(Snapshot diagrams)

快照图是运行时的关系图,来理解一些微妙的问题。快照图表示运行时刻程序的内部状态,包括栈中的方法和局部变量,以及堆中的当前对象等。在该课中,快照图是一种辅助教学工具,便于理解。

基本变量(Primitive values)

来向的箭头表示变量值或对象域。
在这里插入图片描述

对象变量(Object values)

用一个标有对象类型的圈来表示一个对象变量值。也可以在圈里写上对象域的名字,用去向的箭头指出它们的值。x:intint x的写法都是常见的。
在这里插入图片描述

赋值(Mutating values vs. reassigning variables,就姑且翻译成赋值吧。。)

这里有两个名词,我也不会翻译,不过解释是这样的。可变值变量(mutable values)主要指数组或列表,这种变量的内容是可以改变的,就是改变数组或列表中元素的值。还有不可变值变量(immutable values)主要指基本类型变量,比如int或者String也算这一类,它们没有什么内容可以改变,如果要改变它们的值,就是指向另一个值。

不可变值变量和赋值(Reassignment and immutable values)

比如说要吧一个String类型变量s由原来的"a"赋值为"ab"

String s = "a";
s = s + "b";

快照图是这样的在这里插入图片描述

可变值变量(Mutable values)

String类似的一种表示字符串的对象StringBuilder是可变值变量,把这个类型的变量sb"a"改为"ab"是这样的:

StringBuilder sb = new StringBuilder("a");
sb.append("b");

在这里插入图片描述

不可变引用(Immutable references)

这里主要指关键字final,它可以让一个引用不可变,也就是不能再指向其它值了,也就是不可赋值了(只能赋值一次,即初始化)。

如果Java编译器认为一个final变量在运行时没有只赋值一次,那么就会报编译错误。

在快照图中,final变量的表示方法是双线箭头指向它的值。下面id就是不可再变的,age就是可变的
在这里插入图片描述

Java集合(Java Collections)

先前我们提到了arrays,也就是一些对象或基本类型的定长数组。Java对于对象集合的组织有一套功能强大灵活的工具:Java集合工作框架(Java Collections Framework)

列表,集合,和映射(Lists, Sets, and Maps)

Java的List概念和Python的list差不多。List是一个包含了若干个对象的集合,这里面同一个对象可能出现多次。可以向List中添加或删除表项(item),同时List自己会变长或变短来适应它的内容变化。

以下是几个List的操作:

Java描述Python
int count = lst.size();获取列表元素个数count = len(lst)
lst.add(e);向列表末尾添加一个元素lst.append(e)
if(lst.isEmpty()) ...判断列表是否为空if not lst: ...

在快照图里,用一个对象来表示List,里面用索引数来替代对象域,比如cities是一个列表,而它的实际含义可能表示从波士顿到波哥大再到巴塞罗那的旅程。
在这里插入图片描述

Map和Python的dictionary(字典)概念相似,意思是映射。Python中映射的关键字必须有哈希性(可散列性)。Java也有类似的要求,下面会介绍。

Map的几个例子:

Java描述Python
map.put(key, val)添加映射keyvalmap[key] = val
map.get(key)获取关键字的映射值map[key]
map.containsKey(key)判断映射集合中是否有关键字keykey in map
map.remove(key)删除一个映射del map[key]

在快照图中,用包含关键字和映射值的序对的对象来表示Map
在这里插入图片描述

Set是若干个唯一的对象的无序集合。像数学上的集合定义,和Python中的set也类似,和Java中List不同,在Set中,一个对象绝不能出现多次。和Python一样,Java对此中的对象也有哈希性要求。

Set的几个操作例子:

Java描述Python
s1.contains(e)判断集合中是否包含元素e in s1
s1.containsAll(s2)判断集合s2是否属于集合s1s1.issuperset(s2)
s1 >= s2
s1.removeAll(s2)把集合s1中的s2集合除去s1.difference_update(s2)
s1 -= s2

在快照图中,表示Set是用对象表示的,这种对象的对象域没有名字。
在这里插入图片描述

写法(Literals)

Python的lists语法是这样的:

lst = [ "a", "b", "c" ]

Python的maps语法是这样的:

map = { "apple": 5, "banana": 7 }

Java的数组语法是这样的:

String[] arr = { "a", "b", "c" };

但这是在构造一个数组,而不是List,可以用Arrays.asList函数来把一个数组改造成一个List

Arrays.asList(new String[] { "a", "b", "c" })

或者直接这样:

Arrays.asList("a", "b", "c")

Arrays.asList函数构造的List有一个缺点,就是它的长度也是固定的。

泛型:列表,集合和映射变量的声明(Generics: declaring List, Set, and Map variables)

和Python的集合类型不一样,Java集合可以确保加入集合中的对象一定属于这个集合的数据类型,这点是由Java编译器的静态检查保证的。这样,当我们从集合中拿出一个元素时,也能确保这个元素的类型是我们所预期的。

下面是声明集合类型变量的语法(查了百度和谷歌我也没明白Java里Turtle是什么鬼):

List<String> cities;        // 字符串列表
Set<Integer> numbers;       // 整数集合
Map<String,Turtle> turtles; // 字符串到Turtle类型的映射

由于泛型的原因,我们不能创建一个基本类型的集合,比如说Set<int>就不行。不过,int类型有Integer作为封装,我们可以用这个封装对象来创建集合Set<Integer> numbers

虽然对于基本类型的集合我们是用它们的封装对象来创建的,不过在实际使用过程中,Java会做一些自动转换。比如说如果我们已经声明了List<Integer> squence,那么下面的代码是完全成立的:

sequence.add(5);              // 把5加入到集合中
int second = sequence.get(1); // 获取集合中第二个元素

用ArrayLists和LinkedLists创建列表(ArrayLists and LinkedLists: creating Lists)

ListSetMap都是接口(interfaces)。它们定义了各自的类型,却没有提供实例化代码。这么做有几个好处,其中之一就是我们可以根据实际情况来选择不同的实例化类型。

比如说,我们要创建两个List类型变量:

List<String> firstNames = new ArrayList<String>();
List<String> lastNames = new LinkedList<String>();

既然泛型参数在语句左右两端都是一样的,Java允许我们简化一点地写:

List<String> firstNames = new ArrayList<>();
List<String> lastNames = new LinkedList<>();

ArrayListLinkedList都是List的实例化,两者都具有List的所有操作,而且所有的操作必须按照List的操作规定来执行。在上述示例中,firstNameslastNames的行为就是一样的,我们要是把这两个给换一下,lastNamesArrayListfirstNamesLinkedList,代码也没问题。

可是选哪个实例化也是一个问题:在Python里面我们不关心lists,为什么到Java里就要区分ArrayListsLinkedList了呢?事实上这两者的区别只有表现形式,深入理解可以知道,这两者的方法功能相同,但实现方法不太相同。在该课程中对这二者不作区分,不知道用哪个的话,就用ArrayList吧。

用HashSets和HashMaps来创建集合和映射(HashSets and HashMaps: creating Sets and Maps)

HashSetSet的默认实例化选择:

Set<Integer> numbers = new HashSet<>();

Java还提供了有序集合,用TreeSet实例化来实现。

至于Map默认的实例化选择是HashMap

Map<String,Turtle> turtles = new HashMap<>();

迭代(Iteration)

假设我们现在有:

List<String> cities        = new ArrayList<>();
Set<Integer> numbers       = new HashSet<>();
Map<String,Turtle> turtles = new HashMap<>();

那要怎么对这些集合变量遍历呢?

在Python中:

for city in cities:
    print city

for num in numbers:
    print num

for key in turtles:
    print "%s: %s" % (key, turtles[key])

Java对于ListSet有相似的语法来遍历:

for (String city : cities) {
    System.out.println(city);
}

for (int num : numbers) {
    System.out.println(num);
}

但我们不能在Java里对Map用这种便利方式,我们倒是可以用这种方式来遍历集合中的关键字或值:

for (String key : turtles.keySet()) {
    System.out.println(key + ": " + turtles.get(key));
}

这种for循环模式涉及到了迭代器(Iterator 的概念,课程后续会讲到。

用索引迭代(Iterating with indices)

Java里还可以用for循环来根据索引进行遍历:

for (int ii = 0; ii < cities.size(); ii++) {
    System.out.println(cities.get(ii));
}

上面这个代码尽量避免,这个代码冗长且有很多潜在bug,除非我们真的需要索引ii来帮我们遍历,否则就尽量避免这种模式。

下面两个小练习:

List重写下面这些数组变量的声明,只声明变量而不初始化值:
String[] names; // Answer is: List\<String\> names;
int[] numbers; // Answer is: List<Integer> numbers;
char[][] grid; //List<List<Character>> grid;

Java的Map类似Python的字典。在我们运行下面的代码之后:

Map<String, Double> treasures = new HashMap<>();
String x = "palm";
treasures.put("beach", 25.);
treasures.put("palm", 50.);
treasures.put("cove", 75.);
treasures.put("x", 100.);
treasures.put("palm", treasures.get("palm") + treasures.size());
treasures.remove("beach");
double found = 0;
for (double treasure : treasures.values()) {
    found += treasure;
}

下面表达式的结果是:
treasures.get(x) \\ Answer is 54.
treasures.get("x") \\ Answer is 100.
found \\ Answer is 229.

x指的是"palm",且"palm"的映射值被改为了原映射值+当时集合的大小,也就是50.+4=54.。

第二个,直接找"x"的映射值,就是100.了。

第三个,found是对最后映射集中所有映射值的累加和,最后的映射集改变了"palm"值,并移除了"beach",所以结果是54.+75.+100.=229.。

Java API 文档(Java API documentation)

这段是教大家怎么看Java API的,Java API 文档真的是学习Java语法的官方教材。不多墨迹了,简单翻译一下原课程内容。

  • java.lang.StringString的全名。创建String对象类型只需要用双引号就好了""。
  • java.lang.Integer还有其它基本类型的封装类,在多数情况下,Java可以在基本类型和封装类之间自由转换。
  • java.util.List类似与Python的list。
  • java.util.Map类似Python的字典(dictionary)。
  • java.io.File表示磁盘上的文件。用File提供的方法可以判断文件是否可读,删除文件,获知文件最近一次被修改的时间等。
  • java.io.FileReader是用来读文本文件的。
  • java.io.BufferedReader可以高效地读文本,而且提供了一个非常有用的特性就是一次读一整行。

接下来是对Java API文档中BufferedReader的一次学习引导,教大家如何看文档。

文档最上面是BufferedReader的类级展示,还有一些类的接口。一个BufferedReader对象有除自己以外所有类型的所有方法(这句应该这么翻译吗?因为原文是A BufferedReader object has all of the methods of all those types (plus its own methods) available to use.)。

接下来是直接子类,还展示了一个接口用来实例化类。

在下面是类的说明。要弄懂一个类,应该看这里吧。

如果想知道如何构建一个BufferedReader的话就要看构造总结了。Java里不是非得靠构造器来实例化一个对象,不过这是最常见的方法了。

然后是能调用的所有方法总结清单

总结下面是每个方法和构造器的详细描述。点击构造器和方法可以看详细描述。要理解一个方法的实现和功能,也应该看这里!

每个详细描述都包括:

  • 方法特征:返回值类型,方法名称还有参数。还可以看异常(exceptions),目前异常就是指方法运行时可能遇到的错误。
  • 全部描述
  • 参数:方法参数的描述。
  • 还有方法返回值的描述。

后面还有两个练习,是为了训练阅读API能力的,就不放出来了。

关于Java API,method summary和method detail比较重要!!!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值