異常處理:讓程序更健壯的錯誤處理機制

在Java編程中,錯誤和異常是不可避免的。想象一下,當用户輸入了錯誤的數據,或者程序試圖打開一個不存在的文件時,如果沒有適當的處理機制,程序就會崩潰。異常處理機制就是為了解決這類問題而設計的,它允許程序在出現錯誤時優雅地處理,而不是直接崩潰。

try-catch-finally結構:異常捕獲的三劍客

最基本的異常處理結構是try-catch-finally。你可以把可能出現異常的代碼放在try塊中,然後在catch塊中定義異常發生時的處理邏輯,最後在finally塊中放置無論是否發生異常都必須執行的代碼,比如資源釋放。

複製

public class ExceptionDemo {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        try {
            // 嘗試訪問數組的第四個元素,這會拋出ArrayIndexOutOfBoundsException
            System.out.println(numbers[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            // 捕獲並處理數組越界異常
            System.out.println("發生數組越界異常:" + e.getMessage());
        } finally {
            // 無論是否發生異常,都會執行的代碼塊
            System.out.println("finally塊執行,通常用於資源清理");
        }
    }
}

運行這段代碼,你會看到控制枱輸出:

複製

發生數組越界異常:Index 3 out of bounds for length 3
finally塊執行,通常用於資源清理

這個結構就像給程序上了一道"安全網"。try塊是你進行"危險操作"的地方,catch塊是當危險發生時的"應急預案",而finally塊則是無論發生什麼都必須完成的"收尾工作"。

throws聲明與throw拋出:異常的傳遞與觸發

有時候,一個方法可能無法處理它遇到的異常,這時它可以選擇將異常"拋出",讓調用它的方法來處理。這就是throws聲明的作用。

複製

public class FileReader {
    // 聲明該方法可能拋出IOException
    public void readFile(String fileName) throws IOException {
        if (fileName == null || fileName.isEmpty()) {
            // 手動拋出一個IllegalArgumentException
            throw new IllegalArgumentException("文件名不能為空");
        }
        // 這裏是實際的文件讀取邏輯,可能會拋出IOException
        // ...
    }
}

在這個例子中,readFile方法聲明瞭它可能會拋出IOException。同時,如果文件名是空的,它會主動throw一個IllegalArgumentException。這就像是方法在説:"如果文件名是空的,那肯定不行(主動拋出異常)。如果文件名沒問題,但讀取時出了其他問題(比如文件不存在),那我也處理不了,得交給調用我的人處理(throws聲明)。"

自定義異常:讓異常更具可讀性

Java提供了很多內置的異常類,但有時候我們需要更具體的異常類型來描述程序中特有的錯誤。這時,我們可以創建自定義異常。

複製

// 自定義異常類,繼承自Exception
public class InsufficientFundsException extends Exception {
    private double currentBalance;
    private double requiredAmount;

    public InsufficientFundsException(double currentBalance, double requiredAmount) {
        super("餘額不足:當前餘額 " + currentBalance + ", 需要 " + requiredAmount);
        this.currentBalance = currentBalance;
        this.requiredAmount = requiredAmount;
    }

    // 自定義方法,返回當前餘額
    public double getCurrentBalance() {
        return currentBalance;
    }

    // 自定義方法,返回需要的金額
    public double getRequiredAmount() {
        return requiredAmount;
    }
}

// 使用自定義異常
public class BankAccount {
    private double balance;

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            // 拋出自定義異常
            throw new InsufficientFundsException(balance, amount);
        }
        balance -= amount;
    }
}

自定義異常的好處是,它能讓異常信息更加清晰,也方便調用者針對性地捕獲和處理特定類型的異常。比如,當BankAccount的withdraw方法拋出InsufficientFundsException時,調用者可以明確地知道是餘額不足的問題,而不是其他類型的錯誤。

常見異常類型及處理策略

Java中有許多常見的異常類型,瞭解它們有助於我們更好地處理程序中可能出現的問題。

Java21天學習計劃 - 第八天:異常處理機制與集合框架基礎_List

從圖中可以看出,所有異常都繼承自Throwable類,它有兩個主要的子類:Error和Exception。Error通常是嚴重的系統錯誤,比如OutOfMemoryError,程序一般無法處理。Exception則是程序可以處理的異常,它又分為"運行時異常"(RuntimeException及其子類)和"檢查型異常"(其他Exception子類)。

常見的運行時異常包括:

  • NullPointerException:嘗試訪問null對象的方法或屬性。
  • ArrayIndexOutOfBoundsException:數組索引超出範圍。
  • ArithmeticException:算術錯誤,比如除以零。
  • ClassCastException:類型轉換錯誤。

常見的檢查型異常包括:

  • IOException:輸入輸出錯誤,比如文件操作失敗。
  • SQLException:數據庫操作錯誤。
  • ClassNotFoundException:找不到指定的類。

處理策略:

  1. 預防為主:在可能出現異常的地方進行檢查,比如使用if (obj != null)來避免NullPointerException。
  2. 合理捕獲:只捕獲你能處理的異常,不要用一個catch (Exception e)捕獲所有異常。
  3. 具體處理:對不同類型的異常進行不同的處理,比如FileNotFoundException可能需要提示用户檢查文件路徑,而EOFException可能需要提示用户文件格式錯誤。
  4. 不要忽略異常:不要寫空的catch塊,至少要記錄異常信息,否則可能會掩蓋嚴重的問題。

用户輸入驗證案例:異常處理的實際應用

讓我們通過一個用户輸入驗證的案例來綜合應用所學的異常處理知識。假設我們需要一個程序,讓用户輸入年齡,然後程序輸出用户的出生年份。

複製

import java.util.Scanner;

public class AgeInputValidator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.print("請輸入您的年齡:");
            String input = scanner.nextLine();
            int age = parseAndValidateAge(input);
            int birthYear = 2023 - age; // 假設當前是2023年
            System.out.println("您的出生年份大概是:" + birthYear);
        } catch (NumberFormatException e) {
            System.out.println("輸入錯誤:請輸入有效的數字。");
        } catch (IllegalArgumentException e) {
            System.out.println("輸入錯誤:" + e.getMessage());
        } finally {
            scanner.close();
        }
    }

    private static int parseAndValidateAge(String input) {
        if (input == null || input.trim().isEmpty()) {
            throw new IllegalArgumentException("年齡不能為空,請重新輸入。");
        }
        int age;
        try {
            age = Integer.parseInt(input.trim());
        } catch (NumberFormatException e) {
            throw new NumberFormatException("輸入的不是有效的數字:" + input);
        }
        if (age < 0) {
            throw new IllegalArgumentException("年齡不能為負數,請重新輸入。");
        }
        if (age > 150) {
            throw new IllegalArgumentException("年齡過大,不太可能吧?請重新輸入。");
        }
        return age;
    }
}

在這個案例中,parseAndValidateAge方法負責將用户輸入的字符串轉換為整數年齡,並進行驗證。如果輸入為空、不是數字、為負數或過大,它會拋出相應的異常。main方法則負責捕獲這些異常,並給用户友好的提示。

這個程序就像一個"嚴格的保安",它會仔細檢查每一個"進入"的年齡值,只有符合要求的才能通過。通過異常處理,我們的程序變得更加健壯和友好。

集合框架入門:高效管理數據的容器

在編程中,我們經常需要處理一組數據。比如,一個學生列表、一組商品信息、一系列訂單記錄等。如果只用數組來管理這些數據,會非常不方便,因為數組的長度固定,而且缺乏很多實用的操作方法。Java集合框架(Collection Framework)就是為了解決這些問題而設計的,它提供了一系列接口和類,讓我們可以方便地存儲、檢索和操作數據。

集合框架概覽:認識數據容器的家族樹

Java集合框架非常龐大,但核心可以分為兩大接口:Collection和Map。Collection用於存儲一組元素,而Map用於存儲鍵值對(key-value pairs)。

Java21天學習計劃 - 第八天:異常處理機制與集合框架基礎_System_02

從圖中可以看出,Collection有三個主要的子接口:

  • List:有序集合,允許重複元素。比如ArrayList和LinkedList。
  • Set:不允許重複元素的集合。比如HashSet和TreeSet。
  • Queue:隊列,通常按特定順序(如FIFO)處理元素。比如LinkedList(也實現了Queue接口)和PriorityQueue。

Map接口則有HashMap、TreeMap、LinkedHashMap等實現類,用於存儲鍵值對。

ArrayList:動態數組的實現

ArrayList是最常用的List實現類,它基於動態數組實現,可以根據需要自動擴容。

創建ArrayList

複製

// 創建一個存儲字符串的ArrayList
List<String> fruits = new ArrayList<>();

// 創建一個初始容量為10的ArrayList
List<Integer> numbers = new ArrayList<>(10);

// 從其他集合創建ArrayList
List<String> vegetables = new ArrayList<>(Arrays.asList("土豆", "白菜", "蘿蔔"));

常用操作

複製

List<String> fruits = new ArrayList<>();

// 添加元素
fruits.add("蘋果");
fruits.add("香蕉");
fruits.add("橙子");
System.out.println(fruits); // 輸出: [蘋果, 香蕉, 橙子]

// 在指定位置添加元素
fruits.add(1, "草莓");
System.out.println(fruits); // 輸出: [蘋果, 草莓, 香蕉, 橙子]

// 獲取元素
String firstFruit = fruits.get(0);
System.out.println(firstFruit); // 輸出: 蘋果

// 修改元素
fruits.set(2, "葡萄");
System.out.println(fruits); // 輸出: [蘋果, 草莓, 葡萄, 橙子]

// 刪除元素
fruits.remove(3); // 刪除索引為3的元素
fruits.remove("草莓"); // 刪除值為"草莓"的元素
System.out.println(fruits); // 輸出: [蘋果, 葡萄]

// 判斷是否包含元素
boolean hasApple = fruits.contains("蘋果");
System.out.println(hasApple); // 輸出: true

// 獲取元素個數
int size = fruits.size();
System.out.println(size); // 輸出: 2

// 遍歷元素
for (String fruit : fruits) {
    System.out.println(fruit);
}
// 輸出:
// 蘋果
// 葡萄

// 清空列表
fruits.clear();
System.out.println(fruits.isEmpty()); // 輸出: true

ArrayList的優點是隨機訪問速度快(通過索引訪問元素的時間複雜度是O(1)),缺點是插入和刪除元素(尤其是在列表中間)的效率較低,因為需要移動大量元素。

LinkedList:鏈表結構的實現

LinkedList是另一種常用的List實現類,它基於雙向鏈表(doubly linked list)實現。

創建LinkedList

複製

// 創建一個存儲字符串的LinkedList
List<String> names = new LinkedList<>();

// 從其他集合創建LinkedList
List<Integer> nums = new LinkedList<>(Arrays.asList(1, 2, 3));

LinkedList的常用操作方法(如add、get、set、remove等)與ArrayList基本相同,這裏就不再重複了。

LinkedList的優點是插入和刪除元素(尤其是在列表頭部或尾部)的效率高(時間複雜度是O(1)),缺點是隨機訪問速度慢(需要從頭或尾遍歷,時間複雜度是O(n))。

ArrayList與數組對比:為什麼選擇ArrayList

雖然ArrayList內部使用數組實現,但它比普通數組更加靈活和強大。

Java21天學習計劃 - 第八天:異常處理機制與集合框架基礎_數組_03

主要區別:

  1. 動態大小:數組的長度固定,創建後不能改變;ArrayList的大小可以動態增長和縮小。
  2. 類型安全:通過泛型,ArrayList可以確保只存儲指定類型的元素,避免類型轉換錯誤。
  3. 豐富的方法:ArrayList提供了許多方便的方法,如add、remove、contains、size等,而數組沒有這些方法。
  4. 自動擴容:當ArrayList中的元素數量達到當前容量時,它會自動擴容(通常是原來的1.5倍),而數組需要手動創建新數組並複製元素。
  5. 內存佔用:對於存儲基本數據類型,數組更節省內存;ArrayList存儲的是對象引用,且有額外的開銷(如容量管理)。

適用場景:

  • 如果需要頻繁隨機訪問元素,且元素數量變化不大,數組可能更高效。
  • 如果需要頻繁添加或刪除元素,或者元素數量不確定,ArrayList通常是更好的選擇。

HashMap:鍵值對的存儲與查找

HashMap是Map接口最常用的實現類,它基於哈希表(hash table)實現,提供了快速的鍵值對存儲和查找功能。

創建HashMap

複製

// 創建一個存儲String-Integer鍵值對的HashMap
Map<String, Integer> studentScores = new HashMap<>();

// 創建一個初始容量為16,加載因子為0.75的HashMap
Map<String, String> countryCapitals = new HashMap<>(16, 0.75f);

常用操作

複製

Map<String, Integer> studentScores = new HashMap<>();

// 添加鍵值對
studentScores.put("張三", 90);
studentScores.put("李四", 85);
studentScores.put("王五", 95);
System.out.println(studentScores);
// 輸出: {張三=90, 李四=85, 王五=95} (順序可能不同,因為HashMap不保證順序)

// 獲取值
int zhangsanScore = studentScores.get("張三");
System.out.println("張三的分數: " + zhangsanScore); // 輸出: 張三的分數: 90

// 檢查是否包含鍵
boolean hasLiSi = studentScores.containsKey("李四");
System.out.println("是否包含李四: " + hasLiSi); // 輸出: true

// 檢查是否包含值
boolean hasScore95 = studentScores.containsValue(95);
System.out.println("是否有95分的學生: " + hasScore95); // 輸出: true

// 修改值
studentScores.put("李四", 88); // 如果鍵已存在,會替換值
System.out.println("李四的新分數: " + studentScores.get("李四")); // 輸出: 88

// 刪除鍵值對
studentScores.remove("王五");
System.out.println(studentScores); // 輸出: {張三=90, 李四=88}

// 獲取所有鍵
Set<String> names = studentScores.keySet();
System.out.println("學生名單: " + names); // 輸出: 學生名單: [張三, 李四]

// 獲取所有值
Collection<Integer> scores = studentScores.values();
System.out.println("分數列表: " + scores); // 輸出: 分數列表: [90, 88]

// 遍歷鍵值對
for (Map.Entry<String, Integer> entry : studentScores.entrySet()) {
    System.out.println(entry.getKey() + "的分數是: " + entry.getValue());
}
// 輸出:
// 張三的分數是: 90
// 李四的分數是: 88

// 清空
studentScores.clear();
System.out.println(studentScores.isEmpty()); // 輸出: true

HashMap的特點是查找、添加、刪除鍵值對的效率都很高(平均時間複雜度是O(1)),但它不保證鍵值對的順序。如果需要保持插入順序,可以使用LinkedHashMap;如果需要按鍵排序,可以使用TreeMap。

學生管理系統集合應用案例

讓我們通過一個簡單的學生管理系統案例,來綜合應用所學的集合知識。這個系統可以添加學生、刪除學生、查詢學生信息,並展示所有學生。

複製

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

// 學生類
class Student {
    private String id;
    private String name;
    private int age;

    public Student(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    // getter和setter方法
    public String getId() { return id; }
    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return "Student{id='" + id + "', name='" + name + "', age=" + age + "}";
    }
}

// 學生管理系統
public class StudentManagementSystem {
    private Map<String, Student> studentMap; // 用ID作為鍵,存儲學生對象
    private List<Student> studentList;       // 存儲所有學生,用於按添加順序展示

    public StudentManagementSystem() {
        studentMap = new HashMap<>();
        studentList = new ArrayList<>();
    }

    // 添加學生
    public void addStudent(Student student) {
        if (studentMap.containsKey(student.getId())) {
            System.out.println("錯誤:ID為" + student.getId() + "的學生已存在!");
            return;
        }
        studentMap.put(student.getId(), student);
        studentList.add(student);
        System.out.println("添加成功:" + student);
    }

    // 刪除學生
    public void removeStudent(String id) {
        Student student = studentMap.remove(id);
        if (student == null) {
            System.out.println("錯誤:ID為" + id + "的學生不存在!");
            return;
        }
        studentList.remove(student);
        System.out.println("刪除成功:" + student);
    }

    // 查詢學生
    public Student queryStudent(String id) {
        return studentMap.get(id);
    }

    // 展示所有學生
    public void showAllStudents() {
        if (studentList.isEmpty()) {
            System.out.println("當前沒有學生信息。");
            return;
        }
        System.out.println("所有學生信息:");
        for (Student student : studentList) {
            System.out.println(student);
        }
    }

    public static void main(String[] args) {
        StudentManagementSystem system = new StudentManagementSystem();
        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.println("\n===== 學生管理系統 =====");
            System.out.println("1. 添加學生");
            System.out.println("2. 刪除學生");
            System.out.println("3. 查詢學生");
            System.out.println("4. 顯示所有學生");
            System.out.println("5. 退出");
            System.out.print("請選擇操作(1-5):");

            int choice;
            try {
                choice = Integer.parseInt(scanner.nextLine());
            } catch (NumberFormatException e) {
                System.out.println("輸入錯誤,請輸入數字1-5。");
                continue;
            }

            switch (choice) {
                case 1:
                    System.out.print("請輸入學生ID:");
                    String id = scanner.nextLine();
                    System.out.print("請輸入學生姓名:");
                    String name = scanner.nextLine();
                    System.out.print("請輸入學生年齡:");
                    int age;
                    try {
                        age = Integer.parseInt(scanner.nextLine());
                        system.addStudent(new Student(id, name, age));
                    } catch (NumberFormatException e) {
                        System.out.println("年齡輸入錯誤,請輸入有效的數字。");
                    }
                    break;
                case 2:
                    System.out.print("請輸入要刪除的學生ID:");
                    id = scanner.nextLine();
                    system.removeStudent(id);
                    break;
                case 3:
                    System.out.print("請輸入要查詢的學生ID:");
                    id = scanner.nextLine();
                    Student student = system.queryStudent(id);
                    if (student != null) {
                        System.out.println("查詢結果:" + student);
                    } else {
                        System.out.println("沒有找到ID為" + id + "的學生。");
                    }
                    break;
                case 4:
                    system.showAllStudents();
                    break;
                case 5:
                    System.out.println("謝謝使用,再見!");
                    scanner.close();
                    return;
                default:
                    System.out.println("輸入錯誤,請輸入1-5之間的數字。");
            }
        }
    }
}

在這個案例中,我們使用了HashMap(studentMap)來存儲學生對象,以學生ID為鍵,這樣可以快速根據ID查找、添加和刪除學生(時間複雜度O(1))。同時,我們使用了ArrayList(studentList)來按添加順序存儲所有學生,以便按插入順序展示學生列表。

這個系統展示了集合框架的強大之處:通過選擇合適的集合類型,我們可以高效地管理數據,實現各種功能。

泛型基礎:類型安全的保障

在Java 5之前,集合可以存儲任何類型的對象,這就可能導致類型轉換錯誤。例如,一個List中可能既有String又有Integer,當你試圖將所有元素都當作String處理時,就會拋出ClassCastException。泛型(Generics)的出現解決了這個問題,它允許我們在定義集合時指定元素的類型,從而在編譯時就確保類型安全。

參數化類型:給集合貼上"類型標籤"

泛型的核心思想是"參數化類型"(parameterized type),即允許在定義類、接口或方法時使用類型參數(type parameter)。

泛型類

最常見的泛型應用是在集合類中。例如,ArrayList的定義如下(簡化版):

複製

public class ArrayList<E> {
    private E[] elements;

    public void add(E element) { ... }
    public E get(int index) { ... }
    // ...
}

這裏的<E>就是類型參數,它像一個佔位符,表示"這個類可以存儲某種類型的元素"。當我們創建ArrayList實例時,需要指定具體的類型:

複製

List<String> strList = new ArrayList<String>(); // Java 7之前的寫法
List<Integer> numList = new ArrayList<>();     // Java 7及以後可以省略右邊的類型(菱形語法)

現在,strList只能存儲String類型的元素,numList只能存儲Integer類型的元素。如果試圖添加其他類型的元素,編譯器會報錯:

複製

strList.add(123); // 編譯錯誤: incompatible types: int cannot be converted to String

這就像給集合貼上了"類型標籤",確保了集合中元素的類型一致性。

泛型方法

除了泛型類,我們還可以定義泛型方法。泛型方法可以在調用時指定類型參數。

複製

public class ArrayUtils {
    // 泛型方法:將數組轉換為列表
    public static <T> List<T> toList(T[] array) {
        List<T> list = new ArrayList<>();
        for (T element : array) {
            list.add(element);
        }
        return list;
    }
}

// 使用泛型方法
String[] strArray = {"蘋果", "香蕉", "橙子"};
List<String> strList = ArrayUtils.<String>toList(strArray); // 可以顯式指定類型參數
System.out.println(strList); // 輸出: [蘋果, 香蕉, 橙子]

Integer[] numArray = {1, 2, 3, 4};
List<Integer> numList = ArrayUtils.toList(numArray); // 通常可以省略類型參數,編譯器會自動推斷
System.out.println(numList); // 輸出: [1, 2, 3, 4]

泛型方法的類型參數在方法返回類型前面聲明,即<T>。這個T表示"方法可以處理某種類型的參數,並返回該類型的結果"。

泛型類與方法:自定義泛型組件

我們也可以自定義泛型類和泛型方法,以創建更通用和類型安全的組件。

自定義泛型類

複製

// 泛型類:Pair,用於存儲一對相關聯的對象
public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }

    @Override
    public String toString() {
        return "Pair{" + key + "=" + value + "}";
    }
}

// 使用泛型類
Pair<String, Integer> person = new Pair<>("年齡", 25);
System.out.println(person.getKey() + ": " + person.getValue()); // 輸出: 年齡: 25

Pair<Integer, String> student = new Pair<>(1001, "張三");
System.out.println("學號: " + student.getKey() + ", 姓名: " + student.getValue()); // 輸出: 學號: 1001, 姓名: 張三

Pair<K, V>有兩個類型參數K(鍵的類型)和V(值的類型)。通過泛型,我們可以創建存儲不同類型鍵值對的Pair對象,而且在編譯時就能確保類型正確。

自定義泛型方法

複製

public class MathUtils {
    // 泛型方法:返回數組中的最大值
    public static <T extends Comparable<T>> T max(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }
}

// 使用泛型方法
Integer[] numbers = {3, 1, 4, 1, 5, 9, 2, 6};
System.out.println("最大值: " + MathUtils.max(numbers)); // 輸出: 最大值: 9

String[] words = {"apple", "banana", "orange", "grape"};
System.out.println("最大單詞: " + MathUtils.max(words)); // 輸出: 最大單詞: orange (按字典序)

這個泛型方法max有一個類型參數T extends Comparable<T>,表示T必須是實現了Comparable<T>接口的類型(這樣才能調用compareTo方法比較大小)。通過這種方式,我們可以創建一個通用的max方法,用於找出任何可比較類型數組中的最大值。

泛型類型擦除:編譯時的"類型檢查員"

Java的泛型是通過"類型擦除"(type erasure)實現的。這意味着泛型信息只在編譯時存在,在運行時會被擦除。編譯器會將泛型類型替換為它們的邊界類型(如果沒有指定邊界,則替換為Object)。

Java21天學習計劃 - 第八天:異常處理機制與集合框架基礎_數組_04

從圖中可以看到,泛型類Info<T>在類型擦除後,T被替換為Object。

例如:

複製

List<String> strList = new ArrayList<>();
strList.add("hello");
String str = strList.get(0);

在編譯時,編譯器會檢查add的參數是否為String,get的返回值是否被賦值給String類型。但在運行時,strList實際上是一個ArrayList<Object>(但編譯器會確保我們只能把它當作ArrayList<String>來使用)。

類型擦除的影響:

  1. 不能使用基本類型作為類型參數:因為類型擦除後會替換為Object,而基本類型不是Object的子類。所以必須使用包裝類,如List<Integer>而不是List<int>。
  2. 不能在運行時檢查泛型類型:strList instanceof List<String>是不合法的,因為運行時strList的類型是List,沒有泛型信息。
  3. 泛型數組創建受限:不能直接創建泛型數組,如new ArrayList<String>[10]是不允許的。

雖然類型擦除有一些限制,但它確保了泛型代碼與舊的非泛型代碼的兼容性。對於我們日常使用泛型來説,瞭解類型擦除的存在即可,不需要深入底層實現細節。

泛型的好處

  1. 類型安全:編譯時就能檢查出類型不匹配的錯誤,避免了運行時的ClassCastException。
  2. 代碼複用:一個泛型類或方法可以處理多種類型的數據,減少了重複代碼。例如,ArrayList可以用於存儲任何類型的對象,而不需要為每種類型都編寫一個專門的列表類。
  3. 可讀性更好:代碼中明確指定了類型,使代碼更易於理解。例如,List<User>一眼就能看出這是一個存儲User對象的列表。
  4. 消除強制類型轉換:在沒有泛型的情況下,從集合中獲取元素時需要強制轉換類型。有了泛型,編譯器會自動插入類型轉換,代碼更簡潔。

總結與實踐

今日知識點回顧

今天我們學習了Java中兩個非常重要的知識點:異常處理機制和集合框架基礎,以及泛型基礎。

異常處理

  • try-catch-finally結構用於捕獲和處理異常。
  • throws聲明用於聲明方法可能拋出的異常,throw用於手動拋出異常。
  • 可以通過繼承Exception類創建自定義異常。
  • 常見異常分為運行時異常和檢查型異常,處理策略包括預防、捕獲、具體處理和不忽略異常。

集合框架

  • Collection接口用於存儲一組元素,主要子接口有List、Set、Queue。
  • Map接口用於存儲鍵值對。
  • ArrayList是基於動態數組的List實現,適合隨機訪問。
  • LinkedList是基於鏈表的List實現,適合頻繁插入刪除。
  • HashMap是基於哈希表的Map實現,提供快速的鍵值對存取。

泛型基礎

  • 泛型允許在定義類、接口、方法時使用類型參數,實現參數化類型。
  • 泛型提供了類型安全、代碼複用和可讀性。
  • Java泛型通過類型擦除實現,泛型信息在運行時被擦除。

實踐練習

為了鞏固今天所學的知識,建議你完成以下練習:

  1. 異常處理練習:創建一個Calculator類,包含除法方法divide(int a, int b),當除數b為0時,拋出ArithmeticException。然後在main方法中調用這個方法,並使用try-catch捕獲異常並處理。
  2. 集合操作練習:使用ArrayList存儲一組學生姓名,實現添加、刪除、修改和遍歷功能。然後嘗試用LinkedList完成同樣的功能,比較兩者在使用上的異同。
  3. HashMap練習:創建一個HashMap,鍵為員工ID(String),值為員工姓名(String)。添加5個員工信息,然後遍歷輸出所有員工ID和姓名,並統計員工總數。
  4. 泛型練習:創建一個泛型類Box<T>,它可以存儲一個T類型的對象。實現set、get方法,以及一個isEmpty方法判斷是否為空。然後創建Box<String>和Box<Integer>的實例並測試。
  5. 綜合練習:結合異常處理、集合和泛型,創建一個簡單的圖書管理系統。功能包括添加圖書(書名、作者、ISBN)、刪除圖書(按ISBN)、查詢圖書(按ISBN或書名)、顯示所有圖書。使用HashMap按ISBN快速查找圖書,使用ArrayList存儲所有圖書以保持添加順序。在用户輸入錯誤時(如ISBN重複、查詢不存在的圖書),使用異常處理給出友好提示。

通過這些練習,你將能夠更好地理解和應用今天所學的知識,為後續的Java學習打下堅實的基礎。記住,編程是一門實踐的藝術,只有多寫代碼,才能真正掌握這些概念和技術。

祝你學習愉快!