20145216史婧瑤《Java程式設計》第5周學習總結

20145216史婧瑤發表於2016-04-01

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的相關知識 

參考資料

相關文章