20145216 《Java程式設計》第5周學習總結
教材學習內容總結
第八章 異常處理
8.1 語法與繼承架構
-
Java中所有錯誤都會被打包為物件,運用try、catch,可以在錯誤發生時顯示友好的錯誤資訊。如:
import java.util.*; public class Average2 { public static void main(String[] args) { try { Scanner console = new Scanner(System.in); double sum = 0; int count = 0; while (true) { int number = console.nextInt(); if (number ==0) { break; } sum += number; count++; } System.out.printf("平均 %.2f%n",sum / count); } catch (InputMismatchException ex) { System.out.println("必須輸入整數"); } } }
-
運用try、catch,還可以在捕捉處理錯誤之後,嘗試恢復程式正常執行流程。如:
import java.util.*; public class Average3 { public static void main(String[] args) { Scanner console = new Scanner(System.in); double sum = 0; int count = 0; while (true) { try { int number = console.nextInt(); if (number == 0) { break; } sum += number; count++; } catch (InputMismatchException ex) { System.out.printf("略過非整數輸入:%s%n", console.next()); } } System.out.printf("平均 %.2f%n", sum / count); } }
-
如果父類異常物件在子類異常前被捕捉,則catch子類異常物件的區塊將永遠不會被執行。
-
catch括號中列出的異常不得有繼承關係,否則會發生編譯錯誤。
-
在catch區塊進行完部分錯誤處理之後,可以使用throw(注意不是throws)將異常再丟擲。如:
import java.io.*; import java.util.Scanner; public class FileUtil { public static String readFile(String name) throws FileNotFoundException { StringBuilder text = new StringBuilder(); try { Scanner console = new Scanner(new FileInputStream(name)); while (console.hasNext()) { text.append(console.nextLine()) .append('\n'); } } catch (FileNotFoundException ex) { ex.printStackTrace(); throw ex; } return text.toString(); } }
-
如果丟擲的是受檢異常,表示你認為客戶端有能力且應該處理異常,此時必須在方法上使用throws宣告;如果丟擲的異常是非受檢異常,表示你認為客戶端呼叫方法的時機錯了,丟擲異常是要求客戶端修正這個漏洞再來呼叫方法,此時也就不用throws宣告。
-
如果使用繼承時,父類某個方法宣告throws某些異常,子類重新定義該方法時可以:
不宣告throws任何異常。 throws父類該方法中宣告的某些異常。 throws父類該方法中宣告異常的子類。
但是不可以:
throws父類方法中未宣告的其他異常。
throws父類方法中宣告異常的父類。
-
在多重方法呼叫下,異常發生點可能是在某個方法之中,若想得知異常發生的根源,以及多重方法呼叫下的堆疊傳播,可以利用異常物件自動收集的堆疊追蹤來取得相關資訊,例如呼叫異常物件的printStackTrace()。如:
public class StackTraceDemo1 { public static void main(String[] args) { try { c(); } catch (NullPointerException ex) { ex.printStackTrace(); } } static void c() { b(); } static void b() { a(); } static String a() { String text = null; return text.toUpperCase(); } }
-
要善用堆疊追蹤,前提是程式程式碼中不可有私吞異常的行為。
-
在使用throw重拋異常時,異常的追蹤堆疊起點,仍是異常的發生根源,而不是重拋異常的地方。如:
public class StackTraceDemo2 { public static void main(String[] args) { try { c(); } catch (NullPointerException ex) { ex.printStackTrace(); } } static void c() { try { b(); } catch (NullPointerException ex) { ex.printStackTrace(); throw ex; } } static void b() { a(); } static String a() { String text = null; return text.toUpperCase(); } }
-
程式執行的某個時間點或某個情況下,必然處於或不處於何種狀態,這是一種斷言。
-
何時該使用斷言?
斷言客戶端呼叫方法前,已經準備好某些前置條件(通常在private方法之中) 斷言客戶端呼叫方法後,具有方法承諾的結果。 斷言物件某個時間點下的狀態。 使用斷言取代批註。 斷言程式流程中絕對不會執行到的程式程式碼部分。
-
斷言是判定程式中的某個執行點必然是或不是某個狀態,所以不能當作像if之類的判斷式來使用,assert不應當作程式執行流程的一部分。
-
若想最後一定要執行關閉資源的動作,try、catch語法可以搭配finally,無論try區塊中有無發生異常,若撰寫有finally區塊,則finally區塊一定會被執行。如:
import java.io.*; import java.util.Scanner; public class FileUtil { public static String readFile(String name) throws FileNotFoundException { StringBuilder text = new StringBuilder(); Scanner console = null; try { console = new Scanner(new FileInputStream(name)); while (console.hasNext()) { text.append(console.nextLine()) .append('\n'); } } finally { if(console != null) { console.close(); } } return text.toString(); } }
-
如果程式撰寫的流程中先return了,而且也有finally區塊,那finally區塊會先執行完後,再講將值返回。如:
public class FinallyDemo { public static void main(String[] args) { System.out.println(test(true)); } static int test(boolean flag) { try { if(flag) { return 1; } } finally { System.out.println("finally..."); } return 0; } }
-
嘗試關閉資源語法:想要嘗試自動關閉資源的物件,是撰寫在try之後的括號中,如果無須catch處理任何異常,可以不用撰寫,也不用撰寫finally自行嘗試關閉資源。如:
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Scanner; public class FileUtil2 { public static String readFile(String name) throws FileNotFoundException { StringBuilder text = new StringBuilder(); try(Scanner console = new Scanner(new FileInputStream(name))) { while (console.hasNext()) { text.append(console.nextLine()) .append('\n'); } } return text.toString(); } }
-
嘗試關閉資源語法可套用的物件,必須操作java.lang.AutoCloseable介面。如:
public class AutoClosableDemo { public static void main(String[] args) { try(Resource res = new Resource()) { res.doSome(); } catch(Exception ex) { ex.printStackTrace(); } } } class Resource implements AutoCloseable { void doSome() { System.out.println("作一些事"); } @Override public void close() throws Exception { System.out.println("資源被關閉"); } }
-
嘗試關閉資源語法也可以同時關閉兩個以上的物件資源,只要中間以分號分隔。如:
import static java.lang.System.out; public class AutoClosableDemo2 { public static void main(String[] args) { try(ResourceSome some = new ResourceSome(); ResourceOther other = new ResourceOther()) { some.doSome(); other.doOther(); } catch(Exception ex) { ex.printStackTrace(); } } } class ResourceSome implements AutoCloseable { void doSome() { out.println("作一些事"); } @Override public void close() throws Exception { out.println("資源Some被關閉"); } } class ResourceOther implements AutoCloseable { void doOther() { out.println("作其它事"); } @Override public void close() throws Exception { out.println("資源Other被關閉"); } }
-
在try的括號中,越後面撰寫的物件資源會越早被關閉。
第九章 Collection與Map
9.1 使用Collection收集物件
-
收集物件的行為,像是新增物件的add()方法、移除物件的remove()方法等,都是定義在java.util.Collection中。既然可以收集物件,也要能逐一取得物件,這就是java.lang.Iterable定義的行為,它定義了iterator()方法返回java.lang.Iterable操作物件,可以讓你逐一取得收集的物件。
-
收集物件的共同行為定義在Collection中,然而收集物件會有不同的需求。如果希望收集時記錄每個物件的索引順序,並可依索引取回物件,這樣的行為定義在java.util.List介面中。如果希望收集的物件不重複,具有集合的行為,則由java.util.Set定義。如果希望收集物件時以佇列方式,收集的物件加入至尾端,取得物件時從前端,則可以使用java.util.Queue。如果希望Queue的兩端進行加入、移除等操作,則可以使用java.util.Deque。
-
List是一種Collection,作用是收集物件,並以索引方式保留收集的物件順序,其操作類之一是java.util.ArrayList。如:
import java.util.*; import static java.lang.System.out; public class Guest { public static void main(String[] args) { List names = new java.util.ArrayList(); collectNameTo(names); out.println("訪客名單:"); printUpperCase(names); } static void collectNameTo(List names) { Scanner console = new Scanner(System.in); while(true) { out.print("訪客名稱:"); String name = console.nextLine(); if(name.equals("quit")) { break; } names.add(name); } } static void printUpperCase(List names) { for(int i = 0; i < names.size(); i++) { String name = (String) names.get(i); out.println(name.toUpperCase()); } } }
-
陣列在記憶體中會是連續的線性空間,根據索引隨機存取時速度快,如果操作上有這類需求時,像是排序,就可使用ArrayList,可得到較好的速度表現。
-
LinkedList在操作List介面時,採用了連結(Link)結構。
-
若收集的物件經常會有變動索引的情況,也許考慮連結方式操作的List會比較好,像是隨時會有客戶端登入或登出的客戶端List,使用LinkedList會有比較好的效率。
-
在收集過程中若有相同物件,則不再重複收集,如果有這類需求,可以使用Set介面的操作物件。如:
import java.util.*; public class WordCount { public static void main(String[] args) { Scanner console = new Scanner(System.in); System.out.print("請輸入英文:"); Set words = tokenSet(console.nextLine()); System.out.printf("不重複單字有 %d 個:%s%n", words.size(), words); } static Set tokenSet(String line) { String[] tokens = line.split(" "); return new HashSet(Arrays.asList(tokens)); } }
-
HashSet的操作概念是,在記憶體中開設空間,每個空間會有個雜湊編碼。
-
Queue繼承自Collection,所以也具有Collection的add()、remove()、element()等方法,然而Queue定義了自己的offer()、poll()與peek()等方法,最主要的差別之一在於:add()、remove()、element()等方法操作失敗時會丟擲異常,而offer()、poll()與peek()等方法操作失敗時會返回特定值。
-
如果物件有操作Queue,並打算以佇列方式使用,且佇列長度受限,通常建議使用offer()、poll()與peek()等方法。
-
LinkedList不僅操作了List介面,與操作了Queue的行為,所以可以將LinkedList當作佇列來使用。如:
import java.util.*; interface Request { void execute(); } public class RequestQueue { public static void main(String[] args) { Queue requests = new LinkedList(); offerRequestTo(requests); process(requests); } static void offerRequestTo(Queue requests) { for (int i = 1; i < 6; i++) { Request request = new Request() { public void execute() { System.out.printf("處理資料 %f%n", Math.random()); } }; requests.offer(request); } } static void process(Queue requests) { while(requests.peek() != null) { Request request = (Request) requests.poll(); request.execute(); } } }
-
想對佇列的前端與尾端進行操作,在前端加入物件與取出物件,在尾端加入物件與取出物件,Queue的子介面Deque就定義了這類行為。
-
java.util.ArrayDeque操作了Deque介面,可以使用ArrayDeque來操作容量有限的堆疊。
-
泛型語法:類名稱旁有角括號<>,這表示此類支援泛型。實際加入的物件是客戶端宣告的型別。如:
import java.util.Arrays; public class ArrayList<E> { Object[] elems; private int next; public ArrayList(int capacity) { elems = new Object[capacity]; } public ArrayList() { this(16); } public void add(E e) { if(next == elems.length) { elems = Arrays.copyOf(elems, elems.length * 2); } elems[next++] = e; } public E get(int index) { return (E) elems[index]; } public int size() { return next; } }
-
相對於匿名類語法來說,Lambda表示式的語法省略了介面型別與方法名稱,->左邊是引數列,而右邊是方法本體。
-
在Lambda表示式中使用區塊時,如果方法必須有返回值,在區塊中就必須使用return。
-
interator()方法提升至新的java.util.Iterable父介面。
-
Collections的sort()方法要求被排序的物件必須操作java.lang.Comparable介面,這個介面有個compareTo()方法必須返回大於0、等於0或小於0的數。
-
Collections的sort()方法有另一個過載版本,可接受java.util.Comparator介面的操作物件,如果使用這個版本,排序方式將根據Comparator的compare()定義來決定。如:
import java.util.*; class StringComparator implements Comparator<String> { @Override public int compare(String s1, String s2) { return -s1.compareTo(s2); } } public class Sort5 { public static void main(String[] args) { List<String> words = Arrays.asList("B", "X", "A", "M", "F", "W", "O"); Collections.sort(words, new StringComparator()); System.out.println(words); } }
-
在java的規範中,與順序有關的行為,通常要不物件本身是Comparable,要不就是另行指定Comparator物件告知如何排序。
-
若要根據某個鍵來取得對應的值,可以事先利用java.util.Map介面的操作物件來建立鍵值對應資料,之後若要取得值,只要用對應的鍵就可以迅速取得。常用的Map操作類為java.util.HashMap與java.util.TreeMap,其繼承自抽象類java.util.AbstractMap。
-
Map也支援泛型語法,如使用HashMap的範例:如:
import java.util.*; import static java.lang.System.out; public class Messages { public static void main(String[] args) { Map<String, String> messages = new HashMap<>(); messages.put("Justin", "Hello!Justin的訊息!"); messages.put("Monica", "給Monica的悄悄話!"); messages.put("Irene", "Irene的可愛貓喵喵叫!"); Scanner console = new Scanner(System.in); out.print("取得誰的訊息:"); String message = messages.get(console.nextLine()); out.println(message); out.println(messages); } }
-
如果使用TreeMap建立鍵值對應,則鍵的部分則會排序,條件是作為鍵的物件必須操作Comparable介面,或者是在建立TreeMap時指定操作Comparator介面的物件。如:
import java.util.*; public class Messages2 { public static void main(String[] args) { Map<String, String> messages = new TreeMap<>(); messages.put("Justin", "Hello!Justin的訊息!"); messages.put("Monica", "給Monica的悄悄話!"); messages.put("Irene", "Irene的可愛貓喵喵叫!"); System.out.println(messages); } }
-
Properties類繼承自Hashtable,HashTable操作了Map介面,Properties自然也有Map的行為。雖然也可以使用put()設定鍵值對應、get()方法指定鍵取回值,不過一般常用Properties的setProperty()指定字串型別的鍵值,getProperty()指定字串型別的鍵,取回字串型別的值,通常稱為屬性名稱與屬性值。
-
如果想取得Map中所有的鍵,可以呼叫Map的keySet()返回Set物件。由於鍵是不重複的,所以用Set操作返回是理所當然的做法,如果想取得Map中所有的值,則可以使用values()返回Collection物件。如:
import java.util.*; import static java.lang.System.out; public class MapKeyValue { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("one", "一"); map.put("two", "二"); map.put("three", "三"); out.println("顯示鍵"); map.keySet().forEach(key -> out.println(key)); out.println("顯示值"); map.values().forEach(key -> out.println(key)); } }
-
如果想同時取得Map的鍵與值,可以使用entrySet()方法,這會返回一個Set物件,每個元素都是Map.Entry例項。可以呼叫getKey()取得鍵,呼叫getValue()取得值。如:
import java.util.*; public class MapKeyValue2 { public static void main(String[] args) { Map<String, String> map = new TreeMap<>(); map.put("one", "一"); map.put("two", "二"); map.put("three", "三"); foreach(map.entrySet()); } static void foreach(Iterable<Map.Entry<String, String>> iterable) { for(Map.Entry<String, String> entry: iterable) { System.out.printf("(鍵 %s, 值 %s)%n", entry.getKey(), entry.getValue()); } } }
教材學習中的問題和解決過程
問題1:
Error與Exception的區別:
解決過程:
通過反覆看教材,我總結出了以下區別:
Error與其子類例項代表嚴重系統錯誤,如硬體層面錯誤、JVM錯誤或記憶體不足等問題,雖然也可以使用try、catch來
處理Error物件,但並不建議,發生嚴重系統錯誤時,Java應用程式本身是無力回覆的。
Exception或其子類例項代表程式設計本身的錯誤,所以通常稱錯誤處理為異常處理(Exception Handling)。
問題2:
Exception與RuntimeException的區別:
解決過程:
教材中對Exception與RuntimeException有所解釋,但是我並沒有完全理解二者的區別,於是我通過上網查資料,總結出以下區別:
Exception:在程式中必須使用try、catch進行處理。
RuntimeException:可以不使用try、catch進行處理,但是如果有異常產生,則異常將由JVM進行處理。
歸納總結:
異常的繼承結構:基類為Throwable,Error和Exception繼承Throwable,RuntimeException和IOException
等繼承Exception。
Exception或其子物件,但非屬於RuntimeException或其子物件,稱為受檢異常;屬於RuntimeException衍生出
來的類例項,稱為非受檢異常。
程式碼除錯中的問題和解決過程
問題:
書上p233頁的程式碼範例中的“!input.matches("\\d*")”是什麼意思?
解決過程:
通過看書上對程式碼的解析,得到如下解釋:String 的 matches() 方法中設定了"\\d*",這是規則表示式,表示檢查字串中的字元是不是數字,若是則 matches() 會返回true。
其他(感悟、思考等,可選)
本週學習了第八、第九章,給我留下的最深刻的印象就是各種類、介面、方法、行為越來越多,有些還很相似,結果經常分不清楚,看到後面的內容又忘記了前面的知識點,學習時一直前後反覆翻教材,後來我就嘗試做筆記,將重要的、容易混淆的知識點都記下來,發現這樣在回顧知識點時很方便,也很清晰明瞭。雖然這周學習效率不是很高,但是通過對這兩章內容的學習,我體會到勤於做筆記和總結對學習是一個很有效的方法。
託管程式碼截圖:
學習進度條
程式碼行數(新增/累積) | 部落格量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 4500行 | 30篇 | 350小時 | 能將java運用自如 |
第一週 | 150/150 | 2/2 | 15/15 | 學習了與java相關的基礎知識 |
第二週 | 200/350 | 1/3 | 20/35 |
學習了java的基本語法 |
第三週 | 450/800 | 1/4 | 25/60 |
學習了物件和封裝的相關知識 |
第四周 | 687/ 1487 | 1/5 | 30/90 |
學習了繼承與介面的相關知識 |
第五週 | 803/2290 | 1/6 | 30/120 |
學習了異常處理以及Collection與Map的相關知識 |