<font size="3"> **本文內容來自[MIT_6.031_sp18: Software Construction](http://web.mit.edu/6.031/www/sp18/)課程的Readings部分,採用[CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)協議。**html
因爲咱們學校(哈工大)大二軟件構造課程的大部分素材取自此,也是推薦的閱讀材料之一,因而打算作一些翻譯工做,本身學習的同時也能幫到一些懶得看英文的朋友。另外,該課程的閱讀資料中有許多練習題,可是沒有標準答案,所給出的答案均爲譯者所寫,有錯誤的地方還請指出。java
<br />python
<br />web
審校:李秋豪api
V1.0 Fri Mar 2 16:48:58 CST 2018數組
<br />安全
譯者注:本章主要講解了Java的一些基礎知識,以及Python和Java的一些區別與聯繫,熟悉的讀者能夠略過本章。架構
<br />oracle
遠離bug | 易讀性 | 可改動性 |
---|---|---|
確保如今和未來都是正確的 | 使得將來的閱讀者(包括你本身)可以讀懂代碼意圖 | 架構設計應該允許將來的改動 |
<br />
閱讀 From Python to Java的前六節 (譯者注:這個網站(英文)上詳細介紹了Python和Java的異同和注意點,學過Python沒學過Java的同窗能夠認真看一看):
<br />
語言基礎
下面這塊代碼取自某一個函數中:
int a = 5; // (1) if (a > 10) { // (2) int b = 2; // (3) } else { // (4) int b = 4; // (5) } // (6) b *= 3; // (7)
哪一行會致使編譯時報錯?
7
修改bug
選擇出(多選)最簡單能改掉這個bug的操做:
[x] 在第一行後聲明 int b;
[ ] 在第二行以前賦值 b = 0;
[x] 第三行改成 b = 2;
[x] 第五行改成 b = 4;
[ ] 第七行改成聲明並賦值 int b *= 3;
咱們按照上面的修改策略進行了修改,若是咱們將else
塊註釋掉,會發生什麼呢?
b
爲 0b
爲 3b
爲 6數字與字符串
下面這個程序語句將華氏溫度轉化爲攝氏溫度,在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));
<br />
爲了弄清楚一些隱祕的問題,咱們會畫一些圖來進行解釋。**快照圖(Snapshot diagrams)**能表明程序運行時的各類狀態——它的棧(即方法和局部變量)和它的堆(即如今存在的對象)。
具體來說,使用快照圖有如下優勢:
雖然這些圖像都只是解釋Java中的一些概念,可是不少均可以延伸到別的現代語言中,例如Python, JavaScript, C++, Ruby.
原始值都是以常量來表達的。上面箭頭的來源能夠是一個變量或者一個對象的內部區域(field)。
一個對象用一個圓圈表示。對象內部會有不少區域(field),這些區域又指向它們對應的值。同時這些區域也是有它們的類型的,例如int x
。
經過快照圖咱們能夠視圖化可更改的值和可被從新賦值的改變之間的區別:
例如,若是咱們有一個 String
變量 s
, 咱們能夠將它從 "a"
賦值爲 "ab"
.
String s = "a"; s = s + "b";
String
就是一種不可改變的(immutable)值,這種類型的值在第一次肯定後就不能改變。不可改變性是咱們這門課程的一個重要設計原則,之後的課程中會詳細介紹的。
不可更改的對象(設計者但願它們一直是這個值)在快照圖中以雙圓圈的邊框表示,例如上面的字符串對象。
與此相對應的, StringBuilder
(Java的一個內置類) 是一個可更改的字符串對象,它內置了許多改變其內容的方法:
StringBuilder sb = new StringBuilder("a"); sb.append("b");
可更改性和不可更改性(mutability and immutability)將會對咱們的「安全健壯性」目標起到重要做用。
Java也提供了不可更改的引用:final
聲明,變量一旦被賦值就不能再次改變它的引用(指向的值或者對象)。
final int n = 5;
若是Java編譯器發現final
聲明的變量在運行中被賦值屢次,它就會報錯。因此final
就是爲不可更改的引用提供了靜態檢查。
在快照圖中,不可更改的引用(final
)用雙箭頭表示,例如上圖中的id
,Person
的id
引用不可改變,可是age倒是可改變的。
這裏要特別注意一點,final
只是限定了引用不可變,咱們能夠將其引用到一個可更改的值 (例如final StringBuilder sb
),雖然引用不變,但引用的對象自己的內容能夠改變。
一樣的,咱們也能夠將一個可更改的引用做用到一個不可更改的值(例如String s
),這個時候變量值的改變就是將引用改變。
<br />
閱讀 From Python to Java上的Collections 章節(譯者注:英文)。
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 |
在快照圖中,咱們不用數字索引表示集合的元素(即元素沒有順序的概念),例如一個含有整數的集合:
<br />
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
咱們立刻就會看到,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<>();
ArrayList
和 LinkedList
是實現List
的其中兩種方法。他們都提供的List
要求的操做,並且這些操做的行爲必須和文檔規定的相同。在上面的例子中, firstNames
和 lastNames
的行爲同樣,也就是說,若是咱們在一串代碼中將 ArrayList
vs. LinkedList
互換,代碼依然可以正常工做。
不幸的是,這也是一種負擔,既然在Python咱們不用關心列表的具體實現,爲何在Java中要關心呢?因爲這隻會致使程序性能的不一樣,在本門課程中咱們不會作相應的要求。當你不肯定時,使用ArrayList
。
對於集合,咱們默認使用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]))
對於List
和 Set
,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。
<br />
聚合類型
將下面使用數組聲明的變量用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
<br />
有時候一種類型中會存在一個既小又有限的不可變的值的集合,例如:
當不可變的值的集合知足「小」和「有限」這兩個條件時,將這個集合中的全部值統必定義爲一個命名常量就是有意義的。在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沒有靜態類型檢查。
<br />
在以前的講義中已經屢次使用java類的文檔連接,它們都是Java platform API的一部分。
API是 應用編程接口(application programming interface )的簡稱。好比Facebook開放了一個供你編程的API(實際上不止一個,由於須要對不一樣的語言和架構開放不一樣的API),那麼你就能夠用它來寫一個和Facebook交互的應用。
String
類型的全稱。咱們僅僅使用"雙引號"
這樣的方式就能夠建立一個String
類型的對象。List
須要用Java來具體實現。File
對象提供的方法:咱們能夠測試這個文件是否可讀、刪除這個文件、查看這個文件最近一次被修改是何時...讓咱們更深刻地看看 BufferedReader
的文檔。文檔中有不少咱們還沒討論過的相關Java特性!保持頭腦清醒而且將注意力集中在下圖展現的信息中。
在這一頁的頂部是BufferedReader
的繼承關係和一系列已經實現的接口。一個BufferedReader
對象能夠調用這些被列出的類型中定義的全部可用的方法(加上它本身定義的方法)。
接下來會看到它的 直接子類,對於一個接口來講就是一個實現類。這能夠幫助咱們獲取諸如HashMap
是Map
的直接子類這樣的信息。
再往下是對這個類的描述。有時候這些描述會有一些模棱兩可,可是若是你要了解一個類,這裏就是你的第一選擇。
若是你想建立一個BufferedReader
,那麼constructor summary版塊就是你要看的資料。構造函數並非Java中惟一獲取一個新對象的方法,但它倒是最爲廣泛使用的。
接下來是BufferedReader
對象中全部咱們能夠調用的方法的列表。
在綜述下面是每一個方法和構造函數的詳細描述。點擊一個構造函數或者方法便可看到詳細的描述。若是你想弄明白一個方法有什麼做用,那你應該查看這裏。
每個詳細描述包括如下內容:
這些詳細的描述稱爲 規格說明。它們使得咱們可以在不瞭解具體實現代碼的狀況下使用諸如 String
, Map
, 或BufferedReader
這樣的工具。
讀、寫、理解和分析這些規格說名將會是咱們在課程6.031中的主要內容之一,將會在幾節課之後開始講解。
<br />
讀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
變量的值是什麼?
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
</font>