ch15_generics

小兵学习笔记發表於2024-09-13
  • 第15章 泛型
    • 泛型的理解和好處
      • 看一個需求
      • 使用傳統方法的問題分析
      • 泛型快速體驗
      • 泛型的好處
    • 泛型介紹
    • 泛型的語法
      • 泛型的宣告
      • 泛型的例項化
      • 泛型使用舉例
      • 泛型使用的注意事項和細節
    • 泛型課堂型別
      • 泛型課堂練習題
    • 自定義泛型
      • 自定義泛型類
      • 自定義泛型介面
      • 自定義泛型方法
        • 自定義泛型方法練習
    • 泛型的繼承和萬用字元
      • 泛型的繼承和萬用字元說明
    • JUnit
    • 本章作業

第15章 泛型

泛型的理解和好處

看一個需求

請編寫程式,在ArrayList 中,新增3個Dog物件

Dog物件含有name 和 age, 並輸出name 和 age (要求使用getXxx())

package com.hspedu.generic;

import java.util.ArrayList;

@SuppressWarnings({"all"})
public class Generic01 {
    public static void main(String[] args) {

        //使用傳統的方法來解決
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Dog("旺財", 10));
        arrayList.add(new Dog("發財", 1));
        arrayList.add(new Dog("小黃", 5));

        //假如程式設計師,不小心,新增了一隻貓
        arrayList.add(new Cat("招財貓", 8)); // 就會報型別轉換的錯誤

        //遍歷
        for (Object o : arrayList) {
            //向下轉型Object ->Dog
            Dog dog = (Dog) o;
            System.out.println(dog.getName() + "-" + dog.getAge());
        }
    }
}
/*
請編寫程式,在ArrayList 中,新增3個Dog物件
Dog物件含有name 和 age, 並輸出name 和 age (要求使用getXxx())
 */
class Dog {
    private String name;
    private int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class Cat { //Cat類
    private String name;
    private int age;
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

使用傳統方法的問題分析

1)不能對加入到集合ArrayList中的資料型別進行約束(不安全)

2)遍歷的時候,需要進行型別轉換,如果集合中的資料量較大,對效率有影響

泛型快速體驗

public class Generic02 {
    public static void main(String[] args) {

        // 使用傳統的方法來解決===> 使用泛型
        // 1. 當我們 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是Dog型別
        // 2. 如果編譯器發現新增的型別,不滿足要求,就會報錯
        // 3. 在遍歷的時候,可以直接取出 Dog 型別而不是 Object
        // 4. public class ArrayList<E> {} E稱為泛型,那麼 Dog->E
        ArrayList<Dog> arrayList = new ArrayList<Dog>();
        arrayList.add(new Dog("旺財", 10));
        arrayList.add(new Dog("發財", 1));
        arrayList.add(new Dog("小黃", 5));
        // 假如我們的程式設計師,不小心,新增了一隻貓
        // arrayList.add(new Cat("招財貓", 8));
        System.out.println("==== 使用泛型 ====");
        for (Dog dog : arrayList) {
            System.out.println(dog.getName() + "-" + dog.getAge());
        }
    }
}

泛型的好處

1)編譯時,檢查新增元素的型別,提高了安全性

2)減少了型別轉換的次數,提高效率。

不使用泛型

  • Dog -加入-> Object -取出-> Dog //放入到ArrayList 會先轉成Object,在取出時,還需要轉換

使用泛型

  • Dog -> Dog -> Dog // 放入時,和取出時,不需要型別轉換,提高效率

3)不再提示編譯警告

泛型介紹

泛(廣泛)型(型別)=> Integer,String,Dog

  1. 泛型又稱引數化型別,是Jdk5.0出現的新特性,解決資料型別的安全性問題

  2. 在類宣告或例項化時只要指定好需要的具體的型別即可。

  3. Java泛型可以保證如果程式在編譯時沒有發出警告,執行時就不會產生ClassCastException異常。同時,程式碼更加簡潔、健壯。

  4. 泛型的作用是:可以在類宣告時透過一個標識表示類中某個屬性的型別,或者法的返回值的型別,或者是引數型別。

package com.hspedu.generic;

import java.util.List;
public class Generic03 {
    public static void main(String[] args) {

        //注意,特別強調: E具體的資料型別在定義Person物件的時候指定,即在編譯期間,就確定E是什麼型別
        Person<String> person = new Person<String>("timerring");
        person.show(); //String

        /*
            你可以這樣理解,上面的Person類
            class Person {
                String s ;//E表示 s的資料型別, 該資料型別在定義Person物件的時候指定,即在編譯期間,就確定E是什麼型別

                public Person(String s) {//E也可以是引數型別
                    this.s = s;
                }

                public String f() {//返回型別使用E
                    return s;
                }
            }
         */

        Person<Integer> person2 = new Person<Integer>(100);
        person2.show();//Integer

        /*
            class Person {
                Integer s ;//E表示 s的資料型別, 該資料型別在定義Person物件的時候指定,即在編譯期間,就確定E是什麼型別

                public Person(Integer s) {//E也可以是引數型別
                    this.s = s;
                }

                public Integer f() {//返回型別使用E
                    return s;
                }
            }
         */
    }
}

//泛型的作用是:可以在類宣告時透過一個標識表示類中某個屬性的型別,
// 或者是某個方法的返回值的型別,或者是引數型別

class Person<E> {
    E s ;//E表示 s的資料型別, 該資料型別在定義Person物件的時候指定,即在編譯期間,就確定E是什麼型別

    public Person(E s) {//E也可以是引數型別
        this.s = s;
    }

    public E f() {//返回型別使用E
        return s;
    }

    public void show() {
        System.out.println(s.getClass());//顯示s的執行型別
    }
}

泛型的語法

泛型的宣告

interface 介面<T>{} 和 class 類 <K,V>{}
//比如:List , ArrayList

說明:

  1. 其中,T,K,V不代表值,而是表示型別。
  2. 任意字母都可以。常用T表示,是Type的縮寫

泛型的例項化

要在類名後面指定型別引數的值(型別)。

List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();

泛型使用舉例

舉例說明,泛型在HashSet,HashMap的使用情況

練習:

  1. 建立3個學生物件
  2. 放入到HashSet中學生物件,使用.
  3. 放入到HashMap中,要求Key是String name, Value就是學生物件
  4. 使用兩種方式遍歷
package com.hspedu.generic;

import java.util.*;

@SuppressWarnings({"all"})
public class GenericExercise {
    public static void main(String[] args) {
        // 使用泛型方式給HashSet 放入3個學生物件
        HashSet<Student> students = new HashSet<Student>();
        students.add(new Student("jack", 18));
        students.add(new Student("tom", 28));
        students.add(new Student("mary", 19));

        // 遍歷
        for (Student student : students) {
            System.out.println(student);
        }

        // 使用泛型方式給HashMap 放入3個學生物件
        // K -> String V->Student
        HashMap<String, Student> hm = new HashMap<String, Student>();
        /*
            public class HashMap<K,V>  {}
         */
        hm.put("milan", new Student("milan", 38));
        hm.put("smith", new Student("smith", 48));
        hm.put("hsp", new Student("hsp", 28));

        //迭代器 EntrySet
        /*
        public Set<Map.Entry<K,V>> entrySet() {
            Set<Map.Entry<K,V>> es;
            return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
        }
         */
        Set<Map.Entry<String, Student>> entries = hm.entrySet();
        /*
            public final Iterator<Map.Entry<K,V>> iterator() {
                return new EntryIterator();
            }
         */
        Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
        System.out.println("==============================");
        while (iterator.hasNext()) {
            Map.Entry<String, Student> next =  iterator.next();
            System.out.println(next.getKey() + "-" + next.getValue());
        }
    }
}
/**
 * 建立 3 個學生物件
 * 放入到 HashSet 中學生物件, 使用.
 * 放入到  HashMap中,要求 Key 是 String name, Value 就是 學生物件
 * 使用兩種方式遍歷
 */
class Student {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

泛型使用的注意事項和細節

interface List{} , public class HashSet{}..等等

說明:T,E只能是引用型別,看看下面語句是否正確?

List<lnteger> list = new ArrayList<lnteger>()://OK
List<int> list2 = new ArrayList<int>();//錯誤

在給泛型指定具體型別後,可以傳入該型別或者其子類型別

泛型使用形式

List<lnteger> list1 =new ArrayList<lnteger>();
List<lnteger> list2 = new ArrayList<>(); // 推薦省略寫法

如果我們這樣寫List list3 = new ArrayList();預設給它的泛型是<E> E就是Object。如果是這樣寫 泛型預設是 Object

package com.hspedu.generic;

import java.util.ArrayList;
import java.util.List;

@SuppressWarnings({"all"})
public class GenericDetail {
    public static void main(String[] args) {
        //1.給泛型指向資料型別是,要求是引用型別,不能是基本資料型別
        List<Integer> list = new ArrayList<Integer>(); //OK
        //List<int> list2 = new ArrayList<int>();//錯誤

        //2. 說明
        //因為 E 指定了 A 型別, 構造器傳入了 new A()
        //在給泛型指定具體型別後,可以傳入該型別或者其子類型別
        Pig<A> aPig = new Pig<A>(new A());
        aPig.f();
        Pig<A> aPig2 = new Pig<A>(new B());
        aPig2.f();

        //3. 泛型的使用形式
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        List<Integer> list2 = new ArrayList<Integer>();
        //在實際開發中,我們往往簡寫
        //編譯器會進行型別推斷, 老師推薦使用下面寫法
        ArrayList<Integer> list3 = new ArrayList<>();
        List<Integer> list4 = new ArrayList<>();
        ArrayList<Pig> pigs = new ArrayList<>();

        //4. 如果是這樣寫 泛型預設是 Object
        ArrayList arrayList = new ArrayList();//等價 ArrayList<Object> arrayList = new ArrayList<Object>();

        /*
            public boolean add(Object e) {
                ensureCapacityInternal(size + 1);  // Increments modCount!!
                elementData[size++] = e;
                return true;
            }
         */
        Tiger tiger = new Tiger();
        /*

            class Tiger {//類
                Object e;

                public Tiger() {}

                public Tiger(Object e) {
                    this.e = e;
                }
            }

         */

    }
}
class Tiger<E> {//類
    E e;

    public Tiger() {}

    public Tiger(E e) {
        this.e = e;
    }
}

class A {}
class B extends A {}

class Pig<E> {//
    E e;

    public Pig(E e) {
        this.e = e;
    }

    public void f() {
        System.out.println(e.getClass()); //執行型別
    }
}

泛型課堂型別

泛型課堂練習題

定義Employee類

  1. 該類包含: private成員變數name, sal, birthday,其中 birthday 為 MyDate 類的物件;
  2. 為每一個屬性定義getter, setter方法;
  3. 重寫toString方法輸出name, sal, birthday
  4. MyDate類包含:private成員變數month, day, year; 併為每一個屬性定義 getter,setter方法;
  5. 建立該類的3個物件,並把這些物件放入ArrayList集合中(ArrayList需使用泛型來定義),對集合中的元素進行排序,並遍歷輸出:
  6. 排序方式:呼叫ArrayList的sort方法,傳入 Comparator物件[使用泛型],先按照name排序,如果name相同,則按生日日期的先後排序。【即:定製排序】
package com.hspedu.generic;

public class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public int compareTo(MyDate o) { //把對year-month-day比較放在這裡

        int yearMinus = year - o.getYear();
        if(yearMinus != 0) {
            return  yearMinus;
        }
        //如果year相同,就比較month
        int monthMinus = month - o.getMonth();
        if(monthMinus != 0) {
            return monthMinus;
        }
        //如果year 和 month
        return day - o.getDay();
    }
}
package com.hspedu.generic;

public class Employee {
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "\nEmployee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }
}
package com.hspedu.generic;

import java.util.ArrayList;
import java.util.Comparator;

@SuppressWarnings({"all"})
public class GenericExercise02 {
    public static void main(String[] args) {

        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("tom", 20000, new MyDate(1980,12,11)));
        employees.add(new Employee("jack", 12000, new MyDate(2001,12,12)));
        employees.add(new Employee("tom", 50000, new MyDate(1980,12,10)));

        System.out.println("employees=" + employees);


        employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee emp1, Employee emp2) {
                //先按照name排序,如果name相同,則按生日日期的先後排序。【即:定製排序】
                //先對傳入的引數進行驗證
                if(!(emp1 instanceof  Employee && emp2 instanceof Employee)) {
                    System.out.println("型別不正確..");
                    return 0;
                }
                //比較name
                int i = emp1.getName().compareTo(emp2.getName());
                if(i != 0) {
                    return i;
                }

                //下面是對birthday的比較,因此,我們最好把這個比較,放在MyDate類完成
                //封裝後,將來可維護性和複用性,就大大增強.
                return emp1.getBirthday().compareTo(emp2.getBirthday());
            }
        });

        System.out.println("==對僱員進行排序==");
        System.out.println(employees);

    }
}
/**
 * 定義Employee類
 * 1) 該類包含:private成員變數name,sal,birthday,其中 birthday 為 MyDate 類的物件;
 * 2) 為每一個屬性定義 getter, setter 方法;
 * 3) 重寫 toString 方法輸出 name, sal, birthday
 * 4) MyDate類包含: private成員變數month,day,year;併為每一個屬性定義 getter, setter 方法;
 * 5) 建立該類的 3 個物件,並把這些物件放入 ArrayList 集合中(ArrayList 需使用泛型來定義),對集合中的元素進行排序,並遍歷輸出:
 *
 * 排序方式: 呼叫ArrayList 的 sort 方法 ,
 * 傳入 Comparator物件[使用泛型],先按照name排序,如果name相同,則按生日日期的先後排序。【即:定製排序】
 */

自定義泛型

自定義泛型類

class 類名 <T,R..> {//..表示可以有多個泛型
	成員
}

注意細節

  1. 普通成員可以使用泛型(屬性、方法)
  2. 使用泛型的陣列,不能初始化:因為沒有確定型別,就不知道到底要開闢多大的空間。
  3. 靜態方法中不能使用類的泛型,因為靜態是與類相關的,因此類的載入時物件還沒有建立,因此無法指定靜態方法/變數的型別。如果靜態方法和靜態屬性使用了泛型,JVM就無法完成初始化。
  4. 泛型類的型別,是在建立物件時確定的(因為建立物件時,需要指定確定型別
  5. 如果在建立物件時,沒有指定型別,預設為Object
class Tiger<T, R, M>{
   	String name;
   	R r;
   	M m;
   	T t;
}
package com.hspedu.customgeneric;

import java.util.Arrays;

@SuppressWarnings({"all"})
public class CustomGeneric_ {
    public static void main(String[] args) {

        //T=Double, R=String, M=Integer
        Tiger<Double,String,Integer> g = new Tiger<>("john");
        g.setT(10.9); //OK
        //g.setT("yy"); //錯誤,型別不對
        System.out.println(g);
        Tiger g2 = new Tiger("john~~");//OK T=Object R=Object M=Object
        g2.setT("yy"); //OK ,因為 T=Object "yy"=String 是Object子類
        System.out.println("g2=" + g2);

    }
}

//1. Tiger 後面泛型,所以我們把 Tiger 就稱為自定義泛型類
//2, T, R, M 泛型的識別符號, 一般是單個大寫字母
//3. 泛型識別符號可以有多個.
//4. 普通成員可以使用泛型 (屬性、方法)
//5. 使用泛型的陣列,不能初始化
//6. 靜態方法中不能使用類的泛型
class Tiger<T, R, M> {
    String name;
    R r; //屬性使用到泛型
    M m;
    T t;
    //因為陣列在new 不能確定T的型別,就無法在記憶體開空間
    T[] ts;

    public Tiger(String name) {
        this.name = name;
    }

    public Tiger(R r, M m, T t) {//構造器使用泛型

        this.r = r;
        this.m = m;
        this.t = t;
    }

    public Tiger(String name, R r, M m, T t) {//構造器使用泛型
        this.name = name;
        this.r = r;
        this.m = m;
        this.t = t;
    }

    //因為靜態是和類相關的,在類載入時,物件還沒有建立
    //所以,如果靜態方法和靜態屬性使用了泛型,JVM就無法完成初始化
//    static R r2;
//    public static void m1(M m) {
//
//    }

    //方法使用泛型

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public R getR() {
        return r;
    }

    public void setR(R r) {//方法使用到泛型
        this.r = r;
    }

    public M getM() {//返回型別可以使用泛型.
        return m;
    }

    public void setM(M m) {
        this.m = m;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Tiger{" +
                "name='" + name + '\'' +
                ", r=" + r +
                ", m=" + m +
                ", t=" + t +
                ", ts=" + Arrays.toString(ts) +
                '}';
    }
}

自定義泛型介面

interface 介面名 <T,R...> {
}

注意細節

  1. 介面中,靜態成員也不能使用泛型(這個和泛型類規定一樣)
  2. 泛型介面的型別,在繼承介面或者實現介面時確定
  3. 沒有指定型別,預設為Object
package com.hspedu.customgeneric;

public class CustomInterfaceGeneric {
    public static void main(String[] args) {

    }
}

/**
 *  泛型介面使用的說明
 *  1. 介面中,靜態成員也不能使用泛型
 *  2. 泛型介面的型別, 在繼承介面或者實現介面時確定
 *  3. 沒有指定型別,預設為Object
 */

//在繼承介面 指定泛型介面的型別
interface IA extends IUsb<String, Double> {

}
//當我們去實現IA介面時,因為IA在繼承IUsb 介面時,指定了U 為String R為Double
//,在實現IUsb介面的方法時,使用String替換U, 是Double替換R
class AA implements IA {

    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {

    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }
}

//實現介面時,直接指定泛型介面的型別
//給U 指定Integer 給 R 指定了 Float
//所以,當我們實現IUsb方法時,會使用Integer替換U, 使用Float替換R
class BB implements IUsb<Integer, Float> {

    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {

    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {

    }
}
//沒有指定型別,預設為Object
//建議直接寫成 IUsb<Object,Object>
class CC implements IUsb { //等價 class CC implements IUsb<Object,Object> {
    @Override
    public Object get(Object o) {
        return null;
    }
    @Override
    public void hi(Object o) {
    }
    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {

    }

}

interface IUsb<U, R> {

    int n = 10;
    //U name; 不能這樣使用

    //普通方法中,可以使用介面泛型
    R get(U u);

    void hi(R r);

    void run(R r1, R r2, U u1, U u2);

    //在jdk8 中,可以在介面中,使用預設方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }
}

自定義泛型方法

修飾符 <T,R..> 返回型別 方法名 (引數列表){}

注意細節

  1. 泛型方法,可以定義在普通類中,也可以定義在泛型類中

  2. 當泛型方法被呼叫時,型別會確定

  3. public void eat(E e) {}.修 飾符後沒有 <T,R..>

    eat方法不是泛型方法,而是使用了泛型。泛型方法,可以使用類宣告的泛型,也可以使用自己宣告泛型。

package com.hspedu.customgeneric;

import java.util.ArrayList;

@SuppressWarnings({"all"})
public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("寶馬", 100);//當呼叫方法時,傳入引數,編譯器,就會確定型別
        System.out.println("=======");
        car.fly(300, 100.1);//當呼叫方法時,傳入引數,編譯器,就會確定型別

        //測試
        //T->String, R-> ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList(), 11.3f);
    }
}

//泛型方法,可以定義在普通類中, 也可以定義在泛型類中
class Car {//普通類

    public void run() {//普通方法
    }
    //說明 泛型方法
    //1. <T,R> 就是泛型
    //2. 是提供給 fly使用的
    public <T, R> void fly(T t, R r) {//泛型方法
        System.out.println(t.getClass());//String
        System.out.println(r.getClass());//Integer
    }
}

class Fish<T, R> {//泛型類
    public void run() {//普通方法
    }
    public<U,M> void eat(U u, M m) {//泛型方法

    }
    //說明
    //1. 下面hi方法不是泛型方法
    //2. 是hi方法使用了類宣告的 泛型
    public void hi(T t) {
    }
    // 泛型方法,可以使用類宣告的泛型,也可以使用自己宣告泛型
    public<K> void hello(R r, K k) {
        System.out.println(r.getClass());//ArrayList
        System.out.println(k.getClass());//Float
    }
}

自定義泛型方法練習

下面程式碼是否正確,如果有錯誤,修改正確,並說明輸出什麼?

package com.hspedu.customgeneric;

public class CustomMethodGenericExercise {
    public static void main(String[] args) {
        //T->String, R->Integer, M->Double
        Apple<String, Integer, Double> apple = new Apple<>();
        apple.fly(10);//10 會被自動裝箱 Integer10, 輸出Integer
        apple.fly(new Dog());//Dog

    }
}

class Apple<T, R, M> {//自定義泛型類

    public <E> void fly(E e) {  //泛型方法
        System.out.println(e.getClass().getSimpleName());
    }

    //public void eat(U u) {}//錯誤,因為U沒有宣告
    public void run(M m) {
    } //ok
}

class Dog {
}

泛型的繼承和萬用字元

泛型的繼承和萬用字元說明

  1. 泛型不具備繼承性
List <Object> list = new ArrayList<String>(); //錯誤
  1. :支援任意泛型型別
  2. :支援A類以及A類的子類,規定了泛型的上限
  3. :支援A類以及A類的父類,不限於直接父類,規定了泛型的下限
package com.hspedu;

import java.util.ArrayList;
import java.util.List;

public class GenericExtends {
    public static void main(String[] args) {

        Object o = new String("xx");

        //泛型沒有繼承性
        //List<Object> list = new ArrayList<String>();

        //舉例說明下面三個方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();

        //如果是 List<?> c ,可以接受任意的泛型型別
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        //List<? extends AA> c: 表示 上限,可以接受 AA或者AA子類
//        printCollection2(list1);//×
//        printCollection2(list2);//×
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√

        //List<? super AA> c: 支援AA類以及AA類的父類,不限於直接父類
        printCollection3(list1);//√
        //printCollection3(list2);//×
        printCollection3(list3);//√
        //printCollection3(list4);//×
        //printCollection3(list5);//×


    }
    // ? extends AA 表示 上限,可以接受 AA或者AA子類
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

    //說明: List<?> 表示 任意的泛型型別都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 萬用字元,取出時,就是Object
            System.out.println(object);
        }
    }



    // ? super 子類類名AA:支援AA類以及AA類的父類,不限於直接父類,
    //規定了泛型的下限
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

}

class AA {
}

class BB extends AA {
}

class CC extends BB {
}

JUnit

  1. 一個類有很多功能程式碼需要測試,為了測試,就需要寫入到main方法中

  2. 如果有多個功能程式碼測試,就需要來回登出,切換很麻煩

  3. 如果可以直接執行一個方法,就方便很多,並且可以給出相關資訊,就好了,可以用 JUnit 測試框架

JUnit是一個Java語言的單元測試框架

多數Java的開發環境都已經整合了JUnit作為單元測試的工具,不用直接在main中例項物件再呼叫方法了,可以直接單獨執行方法。

使用方法:先寫 @Test ,然後 Alt + Enter 從Maven 新增 Junit 即可。

package com.hspedu.junit_;

import org.junit.jupiter.api.Test;

public class JUnit_ {
    public static void main(String[] args) {
        //傳統方式
        //new JUnit_().m1();
        //new JUnit_().m2();
    }

    @Test
    public void m1() {
        System.out.println("m1方法被呼叫");
    }

    @Test
    public void m2() {
        System.out.println("m2方法被呼叫");
    }

    @Test
    public void m3() {
        System.out.println("m3方法被呼叫");
    }
}

本章作業

1.程式設計題

定義個泛型類DAO,在其中定義一個Map成員變數,Map的鍵為String 型別,值為T型別。

分別建立以下方法:
(1) public void save(String id,T entity):儲存T型別的物件到Map成員變數

(2) public T get(String id):從map中獲取id對應的物件

(3) public void update(String id,T entity):替換 map 中key為id的內容,改為entity物件

(4) public List list):返回map中存放的所有T物件

(5) public void delete(String id):刪除指定id物件

定義一個 User類:

該類包含:private成員變數(int型別) id,age; (String型別)name。

建立 DAO 類的物件,分別呼叫其save、get、update、list、delete方法來操作User物件,使用 Junit 單元測試類進行測試。

package com.hspedu.homework;

/**
 * 該類包含:private成員變數(int型別) id,age;(String 型別)name
 */
public class User {
    private int id;
    private int age;
    private String name;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
package com.hspedu.homework;

import java.util.*;

/**
 * 定義個泛型類 DAO<T>,在其中定義一個Map 成員變數,Map 的鍵為 String 型別,值為 T 型別。
 *  *
 *  * 分別建立以下方法:
 *  * (1) public void save(String id,T entity): 儲存 T 型別的物件到 Map 成員變數中
 *  * (2) public T get(String id):從 map 中獲取 id 對應的物件
 *  * (3) public void update(String id,T entity):替換 map 中key為id的內容,改為 entity 物件
 *  * (4) public List<T> list():返回 map 中存放的所有 T 物件
 *  * (5) public void delete(String id):刪除指定 id 物件
 */
public class DAO<T> {//泛型類
    private Map<String, T> map = new HashMap<>();

    public T get(String id) {
        return map.get(id);
    }
    public void update(String id,T entity) {
        map.put(id, entity);
    }
    //返回 map 中存放的所有 T 物件
    //遍歷map [k-v],將map的 所有value(T entity),封裝到ArrayList返回即可
    public List<T> list() {
        //建立 Arraylist
        List<T> list = new ArrayList<>();

        //遍歷map
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            //map.get(key) 返回就是 User物件->ArrayList
            list.add(map.get(key)); //也可以直接使用本類的 get(String id)
        }

        return list;
    }
    public void delete(String id) {
        map.remove(id);
    }
    public void save(String id,T entity) {//把entity儲存到map
        map.put(id, entity);
    }
}