麻省理工18年春软件构造课程阅读02“Java基础”


本文内容来自MIT_6.031_sp18: Software Construction课程的Readings部分,采用CC BY-SA 4.0协议。
html

因为咱们学校(哈工大)大二软件构造课程的大部分素材取自此,也是推荐的阅读材料之一,因而打算作一些翻译工做,本身学习的同时也能帮到一些懒得看英文的朋友。另外,该课程的阅读资料中有许多练习题,可是没有标准答案,所给出的答案均为译者所写,有错误的地方还请指出。java




译者:李秋豪 江家伟python

审校:李秋豪web

V1.0 Fri Mar 2 16:48:58 CST 2018编程


译者注:本章主要讲解了Java的一些基础知识,以及Python和Java的一些区别与联系,熟悉的读者能够略过本章。api


Reading 2: Java 基础

本节目标

  • 学习Java基本的语法和语义
  • 从Python过渡到Java(译者注:MIT是先学的Python)

本课程目标

远离bug 易读性 可改动性
确保如今和未来都是正确的 使得将来的阅读者(包括你本身)可以读懂代码意图 架构设计应该允许将来的改动


开始写Java

阅读 From Python to Java的前六节 (译者注:这个网站(英文)上详细介绍了Python和Java的异同和注意点,学过Python没学过Java的同窗能够认真看一看):数组

  • 程序的结构与执行
  • 数据类型及其表达
  • 简单声明
  • 终端输入输出
  • 控制流
  • 对象与接口


阅读小练习

语言基础安全

下面这块代码取自某一个函数中:架构

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

哪一行会致使编译时报错?oracle

7

修改bug

选择出(多选)最简单能改掉这个bug的操做:

  • [x] 在第一行后声明 int b;

  • [ ] 在第二行以前赋值 b = 0;
  • [x] 第三行改成 b = 2;
  • [x] 第五行改成 b = 4;
  • [ ] 第七行改成声明并赋值 int b *= 3;

咱们按照上面的修改策略进行了修改,若是咱们将else块注释掉,会发生什么呢?

  • [ ] b 为 0
  • [ ] b 为 3
  • [ ] b 为 6
  • [x] 编译器会报错,在咱们运行程序以前
  • [ ] 在咱们运行程序的时候报错,在咱们到达最后一行以前
  • [ ] 在咱们运行程序的时候报错,在咱们到达最后一行的时候

数字与字符串

下面这个程序语句将华氏温度转化为摄氏温度,在Python中能获得正确结果吗?

fahrenheit = 212.0
celsius = (fahrenheit - 32) * 5/9
  • [x] 是

  • [ ] 否:整数除法运算会致使摄氏温度变为0

  • [ ] 否:整数除法运算会致使摄氏温度被向下取整

若是改用Java写,第一行应该改成(译者注:这里网站上给的是单选,可是译者以为存在多个答案):

  • [ ] int fahrenheit = 212.0;

  • [ ] Integer fahrenheit = 212.0;

  • [ ] float fahrenheit = 212.0;

  • [ ] Float fahrenheit = 212.0;

  • [x] double fahrenheit = 212.0;

  • [x] Double fahrenheit = 212.0;

第二行应该改成(???是你上面选择的类型):

  • [x] ??? celsius = (fahrenheit - 32) * 5/9;

  • [ ] ??? celsius = (fahrenheit - 32) * (5 / 9);

  • [x] ??? celsius = (fahrenheit - 32) * (5. / 9);

应该如何输出?

  • [ ] System.out.println(fahrenheit, " -> ", celsius);

  • [x] System.out.println(fahrenheit + " -> " + celsius);

  • [ ] System.out.println("%s -> %s" % (fahrenheit, celsius));

  • [x] System.out.println(Double.toString(fahrenheit) + " -> " + Double.toString(celsius));


用快照图理解值与对象

为了弄清楚一些隐秘的问题,咱们会画一些图来进行解释。快照图(Snapshot diagrams)能表明程序运行时的各类状态——它的栈(即方法和局部变量)和它的堆(即如今存在的对象)。

具体来说,使用快照图有如下优势:

  • 在课堂上和会议上与同窗交流
  • 解释一些概念例如原始类型 vs. 对象类型不可更改的值 vs. 不可更改的引用, 指针别名, stack栈 vs. 堆heap, 抽象表达 vs. 具体表达.
  • 可以帮助你解释你的工程的设计思想
  • 为之后的课程作铺垫(例如MIT 6.170中的对象模型)

虽然这些图像都只是解释Java中的一些概念,可是不少均可以延伸到别的现代语言中,例如Python, JavaScript, C++, Ruby.

原始值

原始值都是以常量来表达的。上面箭头的来源能够是一个变量或者一个对象的内部区域(field)。

对象值

一个对象用一个圆圈表示。对象内部会有不少区域(field),这些区域又指向它们对应的值。同时这些区域也是有它们的类型的,例如int x

可更改的值 vs. 可被从新赋值的改变

经过快照图咱们能够视图化可更改的值和可被从新赋值的改变之间的区别:

  • 当你给一个变量或者一个区域(filed)赋值的时候,你其实是改变了它指向的方向,即指向了另外一个值。
  • 当你修改一个可被更改的(mutable)值的时候——例如数组或者列表——你真正修改了这个值自己(译者注:变量或者区域的指向并无变)

从新赋值和不可改变的(immutable)值

例如,若是咱们有一个 String 变量 s, 咱们能够将它从 "a" 赋值为 "ab".

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

String 就是一种不可改变的(immutable)值,这种类型的值在第一次肯定后就不能改变。不可改变性是咱们这门课程的一个重要设计原则,之后的课程中会详细介绍的。

不可更改的对象(设计者但愿它们一直是这个值)在快照图中以双圆圈的边框表示,例如上面的字符串对象。

可更改的(mutable)值

与此相对应的, StringBuilder (Java的一个内置类) 是一个可更改的字符串对象,它内置了许多改变其内容的方法:

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

可更改性和不可更改性(mutability and immutability)将会对咱们的“安全健壮性”目标起到重要做用。

不可更改的引用

Java也提供了不可更改的引用:final声明,变量一旦被赋值就不能再次改变它的引用(指向的值或者对象)。

final int n = 5;

若是Java编译器发现final声明的变量在运行中被赋值屡次,它就会报错。因此final就是为不可更改的引用提供了静态检查。

在快照图中,不可更改的引用(final)用双箭头表示,例如上图中的idPersonid引用不可改变,可是age倒是可改变的。

这里要特别注意一点,final只是限定了引用不可变,咱们能够将其引用到一个可更改的值 (例如final StringBuilder sb ),虽然引用不变,但引用的对象自己的内容能够改变。

一样的,咱们也能够将一个可更改的引用做用到一个不可更改的值(例如String s ),这个时候变量值的改变就是将引用改变。


Java 聚合类型

阅读 From Python to Java上的Collections 章节(译者注:英文)。

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

Java中的列表和Python中很类似 。列表能够包含零个或多个对象,并且对象能够出现屡次。咱们能够在列表中删除或添加元素。

一些 List 常见的操做:

Java 描述 Python
int count = lst.size(); 计算列表中元素的个数 count = len(lst)
lst.add(e); 在列表最后添加元素 lst.append(e)
if (lst.isEmpty()) ... 测试列表是否为空 if not lst: ...

在快照图中,咱们用数字索引表示列表中的各个区域(filed),例如一个全是String对象的列表:

Java中的映射和Python中的字典相似 。在Python中,字典的keys必须是 可哈希的hashable ,Java也是相似。咱们会在后面讲解对象对等的时候说这个。

经常使用的 Map操做 :

Java 描述 Python
map.put(key, val) 添加映射 key → val map[key] = val
map.get(key) 获取key映射的值 map[key]
map.containsKey(key) 测试key是否存在 key in map
map.remove(key) 删除key所在的映射 del map[key]

在快照图中,咱们将Map表示为包含key/value对的对象。例如一个Map<String, Turtle> :

集合是一种含有零个或多个不重复对象的聚合类型 。和映射中的key相同,Python中的集合的元素也要求是可哈希的hashable ,Java也是相似。

经常使用的 Set 操做:

Java 描述 Python
s1.contains(e) 测试集合中是否含有e e in s1
s1.containsAll(s2) 测试是否 s1 ⊇ s2 s1.issuperset(s2) s1 >= s2
s1.removeAll(s2) s1 中去除s2的元素 s1.difference_update(s2) s1 -= s2

在快照图中,咱们不用数字索引表示集合的元素(即元素没有顺序的概念),例如一个含有整数的集合:


List, Set, and Map的广泛声明方法

Python 提供了建立列表和字典的方便方法:

lst = [ "a", "b", "c" ]
dict = { "apple": 5, "banana": 7 }

Java 不是这样 它只为数组提供了相似的建立方法:

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

咱们能够用the utility function Arrays.asList 从数组建立列表:

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

… 或者直接提供元素:

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

要注意的是,若是一个 List 是用 Arrays.asList 建立的,它的长度就固定了。

在Python中,聚合类中的元素的类型能够不一样,可是在Java中,咱们可以要求编译器对操做进行静态检查,确保聚合类中的元素类型相同。例如:

List<String> cities;        // a List of Strings
Set<Integer> numbers;       // a Set of Integers
Map<String,Turtle> turtles; // a Map with String keys and Turtle values

因为Java要求元素的广泛性,咱们不能直接使用原始类型做为元素的类型,例如Set<int> ,可是,正如前面所提到的, int有一个对应的 Integer ”包装“对象类型,咱们能够用 Set<Integer> numbers.

为了使用方便,Java会自动在原始类型和包装过的对象类型中作一些转换,因此若是咱们声明一个 List<Integer> sequence ,下面的这个代码也可以正常运行:

sequence.add(5);              // add 5 to the sequence
int second = sequence.get(1); // get the second element

建立列表:ArrayList 与 LinkedList

咱们立刻就会看到,Java区分了两个概念:类型的规格说明——它的行为;类型的实现——代码是是什么。

List, Set, 和 Map都是接口 :他们定义了类型的工做,可是他们不提供具体的实现代码。这有不少优势,其中一个就是咱们能根据具体的环境使用更适合的实现方式。

例如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要求的操做,并且这些操做的行为必须和文档规定的相同。在上面的例子中, firstNameslastNames 的行为同样,也就是说,若是咱们在一串代码中将 ArrayList vs. LinkedList互换,代码依然可以正常工做。

不幸的是,这也是一种负担,既然在Python咱们不用关心列表的具体实现,为何在Java中要关心呢?因为这只会致使程序性能的不一样,在本门课程中咱们不会作相应的要求。当你不肯定时,使用ArrayList

建立集合和映射:HashSets 与 HashMaps

对于集合,咱们默认使用HashSet :

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

Java 也提供了 sorted sets ,它是用 TreeSet 实现的.

对于映射,咱们默认使用 HashMap:

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

迭代

咱们建立了如下聚合类变量:

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]))

对于ListSet ,Java提供了相似的语法:

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

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

咱们不能对Map进行彻底同样的操做,可是咱们能够像上面那样遍历它的keys,结合映射对象提供的方法来遍历全部的对(pairs):

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

实际上,这个for循环用到了 Iterator, 咱们会在后续的课程中讲解这种设计。

警告: 必定要注意在循环的时候不要改变你的循环参量(他是可改变的值)!添加、删除、或者替换都会影响你的循环甚至中断你的程序,咱们会在后面的章节中讲解更多的细节。这对Python也是适用的。例以下面这串代码:

numbers = [100,200,300]
for num in numbers:
    numbers.remove(num) # danger!!! mutates the list we're iterating over
print(numbers) # list should be empty here -- is it?

使用数字索引进行迭代

Java也提供了一种使用数字索引进行迭代的方法(译者注:C的标准写法):

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

除非你真的须要索引ii,不然咱们不推荐这种写法,它可能会引来一些难以发现的bug。


阅读小练习

聚合类型

将下面使用数组声明的变量用List进行声明(不用初始化):

String[] names; -> List<String> names;

int[] numbers; -> List<Integer> numbers;

char[][] grid; -> List<List<Character>> grid;

“找出关键点”

在运行下列代码后:

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) -> 54.0

treasures.get("x") -> 100.0

found -> 229.0


枚举类型

有时候一种类型中会存在一个既小又有限的不可变的值的集合,例如:

  • 一年中的月份: January, February, …, November, December
  • 一周中的每一天:Monday, Tuesday, …, Saturday, Sunday
  • 指南针中的方向:north, south, east, west
  • 能够配出的颜色:black, gray, red, …

当不可变的值的集合知足“小”和“有限”这两个条件时,将这个集合中的全部值统必定义为一个命名常量就是有意义的。在JAVA中,这样的命名常量就称为enumeration(枚举类型) 而且使用关键字 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;
}

你能够在声明变量或函数的时候使用例如PenColor这样的枚举类型名:

PenColor drawingColor;

像引用一个被命名的静态常量同样来引用枚举类型的值:

drawingColor = PenColor.RED;

须要强调的是,枚举类型是一个独特的新类型。较老的语言,像Python2和java的早期版本,它们倾向于使用数字常量或者字符串来表示这样的值的有限集。可是一个枚举型变量更加“类型安全”,由于它能够发现一些类型错误,如类型不匹配:

int month = TUESDAY;  //  若是month定义为整型值(TUESDAY也是一个整型值),那么这样写不会报错(可是从语义上看是错的,由于显然不能将“周四”赋值给一个“月份”,这可能不符合做者的本意)
Month month = DayOfWeek.TUESDAY; // 若是month被定义为枚举类型Month,那么这条语句将会触发静态错误  (static error)

或者拼写错误:

String color = "REd";        // 不报错,拼写错误被忽略
PenColor drawingColor = PenColor.REd; // 当枚举类型的值被拼写错时,会触发静态错误

Python3 如今也有枚举类型, 和java的相似, 可是Python3没有静态类型检查。


Java API 文档

在以前的讲义中已经屡次使用java类的文档连接,它们都是Java platform API的一部分。

API是 应用编程接口(application programming interface )的简称。好比Facebook开放了一个供你编程的API(实际上不止一个,由于须要对不一样的语言和架构开放不一样的API),那么你就能够用它来写一个和Facebook交互的应用。

  • java.lang.StringString类型的全称。咱们仅仅使用"双引号"这样的方式就能够建立一个String类型的对象。
  • java.lang.Integer 和其余原始包装器类。在多数状况下,Java都会自动地在原始类型(如int)和它们被包装(wrapped,或者称为“封装,boxed”)以后的类型之间相互转换。
  • java.util.List 就像Python中的列表,可是在Python中,列表是语言的一部分。在Java中, List 须要用Java来具体实现。
  • java.util.Map 就像Python的字典。
  • java.io.File 用于表示硬盘上的文件。让咱们看看File对象提供的方法:咱们能够测试这个文件是否可读、删除这个文件、查看这个文件最近一次被修改是何时...
  • java.io.FileReader 使咱们可以读取文本文件。
  • java.io.BufferedReader 让咱们高效地读取文本文件。它还提供一个颇有用的特性:一次读取一整行。

让咱们更深刻地看看 BufferedReader的文档。文档中有不少咱们还没讨论过的相关Java特性!保持头脑清醒而且将注意力集中在下图展现的信息中。

在这一页的顶部是BufferedReader继承关系和一系列已经实现的接口。一个BufferedReader对象能够调用这些被列出的类型中定义的全部可用的方法(加上它本身定义的方法)。

接下来会看到它的 直接子类,对于一个接口来讲就是一个实现类。这能够帮助咱们获取诸如HashMapMap的直接子类这样的信息。

再往下是对这个类的描述。有时候这些描述会有一些模棱两可,可是若是你要了解一个类,这里就是你的第一选择

若是你想建立一个BufferedReader,那么constructor summary版块就是你要看的资料。构造函数并非Java中惟一获取一个新对象的方法,但它倒是最为广泛使用的。

接下来是BufferedReader 对象中全部咱们能够调用的方法的列表

在综述下面是每一个方法和构造函数的详细描述。点击一个构造函数或者方法便可看到详细的描述。若是你想弄明白一个方法有什么做用,那你应该查看这里。

每个详细描述包括如下内容:

  • 方法签名(signature):咱们能看到方法的返回值类型,方法名,以及参数。咱们还能够看到一些异常,就目前而言,它们就是运行这个方法可能致使的错误。
  • 完整的描述
  • 参数:描述这个方法接收的参数。
  • 对方法返回值的描述。

规格说明

这些详细的描述称为 规格说明。它们使得咱们可以在不了解具体实现代码的状况下使用诸如 String, Map, 或BufferedReader 这样的工具。

读、写、理解和分析这些规格说名将会是咱们在课程6.031中的主要内容之一,将会在几节课之后开始讲解。


阅读小练习

读Java文档

阅读Java API文档来回答下列问题:

假设咱们有一个 TreasureChest类。在咱们运行以下代码以后:

Map<String, TreasureChest> treasures = new HashMap<>();
treasures.put("beach", new TreasureChest(25));
TreasureChest result = treasures.putIfAbsent("beach", new TreasureChest(75));

(译者注:putIfAbsent :If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value.)

result变量的值是什么?

  • [x] 25 treasure
  • [ ] 75 treasure
  • [ ] another amount of treasure
  • [ ] null

Avast!

在运行下面这段代码以后:

Map<String, String> translations = new HashMap<>();
translations.put("green", "verde");
??? result = translations.replace("green", "verde", "ahdar");

result的值是什么?(译者注:replace :Replaces the entry for the specified key only if currently mapped to the specified value. true if the value was replaced)

  • [ ] "green"
  • [ ] "verde"
  • [ ] "ahdar"
  • [ ] true
  • [ ] false
  • [ ] 1
  • [ ] null
  • [x] 以上没有正确答案(译者注:boolean)
相关文章
相关标签/搜索