1. 程式人生 > 實用技巧 >JavaEE - 12泛型

JavaEE - 12泛型

JavaEE - 12泛型

(1)為什麼要有泛型(Generic)

(1.1)泛型的引入

  • 泛型:標籤。中藥店,每個抽屜外面貼著的標籤。
  • 集合容器類在設計階段/宣告階段不能確定這個容器到底實際存的是什麼型別的物件在JDK1.5之前只能把元素型別設計為Object,在JDK1.5之後使用泛型來解決。
  • 除了元素的型別不確定,其他的部分都是確定的,例如:如何儲存元素、如何管理。因此把元素的型別設計成一個引數,這個型別引數叫做泛型
  • Collection<E>,List<E>,ArrayList<E>這個<E>就是型別引數,即泛型。
  • public interface Collection<E> extends Iterable<E> {}

(1.2)泛型概念

  • 泛型,就是允許在定義類、介面時通過一個標識表示類中某個屬性的型別或者某個方法的返回值及引數型別
    • 這個型別引數將在使用時(繼承或實現這個介面,用這個型別宣告變數、建立物件時)確定。即傳入實際的型別引數,也稱為型別實參。
  • JDK1.5以後,Java引入了"引數化型別(Parameterized type)"的概念,允許建立集合時再指定集合元素的型別。
    • List<String>,表明該List只能儲存字串型別的物件。
  • JDK1.5改寫了集合框架中的全部介面和類,為這些介面、類增加了泛型支援,從而在宣告集合變數、建立集合物件時傳入型別實參。

(1.3)為什麼引入泛型

  • 解決元素儲存的安全性問題,好比商品、藥品標籤,不會弄錯。直接用Object,任何型別都可以新增到集合中,型別不安全。
  • 解決獲取資料元素時,需要型別強制轉換的問題,好比不用每回拿商品、藥品都要辨別。可能有ClassCastException。
    @Test
    public void test1(){
        ArrayList list = new ArrayList();
        // 存放學生的考試成績
        list.add(78);
        list.add(99);
        // 型別不安全
//        list.add("Tom");
for(Object obj: list){ int stuScore = (Integer) obj; //java.lang.ClassCastException System.out.println(stuScore); } }

(2)在集合中使用泛型

  • 集合介面或集合類在JDK5.0時都修改為帶泛型的結構。
  • 在例項化集合類時,可以指明具體的泛型型別。
  • 指明以後,在集合類或介面中凡是定義類或介面時,內部結構(方法、構造器、屬性等)使用到類的泛型的位置,都指定為例項化的泛型型別。
    • 比如:add(E e) -->例項化以後:add(Integet e)
  • 注意點:泛型的型別必須是類,不能是基本資料型別,需要用到基本資料型別的位置,使用包裝類。
  • 如果例項化時,沒有指明泛型的型別,預設型別為java.lang.Object型別。
  • JDK7新特性: 型別推斷Map<String, Integer> map = new HashMap<>();

(2.1)List列表

    @Test
    public void test2(){
        //ArrayList<int> list = new ArrayList<int>();
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(78);
        list.add(99);
//        list.add("Tom");  新增報錯
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            Integer stuScore = iterator.next();
            System.out.println(stuScore);
        }
    }

(2.2)Map集合

    @Test
    public void test3(){
        Map<String, Integer> map = new HashMap<>();    // 型別推斷
        map.put("zhangsan",23);
        map.put("007",13);
        System.out.println(map); //{007=13, zhangsan=23}
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        System.out.println(entries);  //{007=13, zhangsan=23}
    }

(2.3)自然排序

public class Person implements Comparable<Person> {
//public class Person implements Comparable {  
// 沒有指明泛型時的寫法
// @Override // public int compareTo(Object o){ // if(o instanceof Person){ // Person p = (Person) o; // return -this.name.compareTo(p.name); // }else { // throw new RuntimeException("輸入的型別不匹配"); // } // } @Override public int compareTo(Person o){ return -this.name.compareTo(o.name); } }
    @Test
    public void test4(){
        TreeSet<Person> set = new TreeSet<>();
        Person p1 = new Person("Tom",22);
        Person p2 = new Person("Sun",24);
        set.add(p1);
        set.add(p2);

        Iterator<Person> iterator = set.iterator();
        while (iterator.hasNext()){
            Person person = iterator.next();
            System.out.println(person);  // Person{name='Tom', age=22}  Person{name='Sun', age=24}
        }
    }

(2.4)定製排序

    @Test
    public void test4(){
        Comparator<Person> com = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getName().compareTo(o2.getName());
            }
        };
        TreeSet<Person> set = new TreeSet<>(com);
        Person p1 = new Person("Tom",22);
        Person p2 = new Person("Sun",24);
        set.add(p1);
        set.add(p2);

        Iterator<Person> iterator = set.iterator();
        while (iterator.hasNext()){
            Person person = iterator.next();
            System.out.println(person);  // Person{name='Sun', age=24} Person{name='Tom', age=22}  
        }
    }

(3)自定義泛型結構

  • 泛型類可能有多個引數,此時應將多個引數一起放在尖括號內。如:<E1,E2,E3>
  • 泛型類的構造器:public GenericClass(){}。錯誤的:public GenericClass<E>(){}
  • 例項化後,操作原來泛型位置的結構必須與指定的泛型型別一致。
  • 泛型不同的引用不能相互賦值。
    • 儘管在編譯時ArrayList<String>和ArrayList<Integer>是兩種型別,但在執行時只有一個ArrayList被載入到JVM中。
  • 泛型如果不指定,將被擦除,泛型對應的型別均按照Object處理,但不等價於Object。經驗:泛型要使用一路都用。要不用,一路都不要用。
  • 如果泛型類是一個介面或抽象類,則不可建立泛型類的物件。
  • JDK1.7,泛型的簡化操作: ArrayList<Fruit> flist = new ArrayList<>();
  • 泛型的指定中不能使用基本資料型別,可以使用包裝類替換。
  • 在類/介面上宣告的泛型,在本類或本介面中即代表某種型別,可以作為非靜態屬性的型別、非靜態方法的引數型別與返回值型別。但在靜態方法中不能使用類的泛型
  • 異常類不能是泛型的。編譯不通過。
  • 不能使用new E[]。但是可以 E[]elements = (E[])new Object[capacity];參考ArrayList原始碼中宣告:Object[] elementDat,而非泛型引數型別陣列。
  • 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型型別:
    • 子類不保留父類的泛型:按需實現
      • 沒有型別擦除
      • 具體型別
    • 子類保留父類的泛型:泛型子類
      • 全部保留
      • 部分保留
    • 子類除了指定或保留父類的泛型,還可以增加自己的泛型。

(3.1)定義泛型類、泛型介面

public class Order<T> {
    int orderId;
    String orderName;
    // 類的內部結構 使用類的泛型
    T orderT;

    public Order(){}

    public Order(int orderId, String orderName, T orderT) {
        this.orderId = orderId;
        this.orderName = orderName;
        this.orderT = orderT;
    }
    // 省略其他程式碼
}

    @Test
    public void test5(){
        // 定義了泛型類,例項化沒有指明類的泛型,泛型型別為Object型別
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ac");
        System.out.println(order); //Order{orderId=0, orderName='null', orderT=ac}

        // 要求: 如果定義了類是帶泛型的,在例項化時要指明類的泛型。
        Order<String> order1 = new Order<String>(123,"NewOrder1","123");
        //order1.setOrderT(123);  報錯
        order1.setOrderT("456");
        System.out.println(order1); //Order{orderId=123, orderName='NewOrder1', orderT=456}
    }

子類在繼承帶泛型的父類時,指明瞭泛型型別,則在例項化子類物件時,不再需要指明泛型。

public class SubOrder extends Order<Integer> {
}
    @Test
    public void test6(){
        SubOrder subOrder = new SubOrder();
        subOrder.setOrderT(1102);
        System.out.println(subOrder); //Order{orderId=0, orderName='null', orderT=1102}
    }

子類在繼承帶泛型的父類時,繼承泛型型別

public class NewSubOrder<T> extends Order<T> {  // 仍然是泛型類
}
    @Test
    public void test7(){
        NewSubOrder subOrder = new NewSubOrder();
        subOrder.setOrderT(1102);
        System.out.println(subOrder); //Order{orderId=0, orderName='null', orderT=1102}

        NewSubOrder<String> subOrder1 = new NewSubOrder<>();
        subOrder1.setOrderT("1104");
        System.out.println(subOrder1); //Order{orderId=0, orderName='null', orderT=1104}
    }

泛型不同的引用不能相互賦值。

    @Test
    public void test8(){
        ArrayList<String> list1 = null;
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        // 泛型不同的引用 不能相互賦值。
//        list1 = list2;
        Person p1 = null;
        Person p2 = null;
        p1 = p2;
    }

不能使用new E[]。

    public Order(){
//        T[] arr = new T[10];
        T[] arr1 = (T[])new Object[10];
    }

子類繼承帶泛型的父類

class Father<T1,T2> {
}
// 1) 子類不保留父類泛型 - 沒有型別、擦除: 等價於 class Son1 extends Father<Object,Object>
class Son1 extends Father{
}
// 2) 子類不保留父類泛型 - 具體型別
class Son2 extends Father<Integer, String>{
}
// 3) 子類保留父類泛型 - 全部保留
class Son3<T1,T2> extends Father<T1, T2>{
}
// 4) 子類保留父類泛型 - 部分保留
class Son4<T2> extends Father<Integer, T2>{
}

(3.2)泛型方法

  • 在方法中出現泛型的結構,泛型引數與類的泛型引數沒有任何關係。泛型方法所屬的類是不是泛型類都沒有關係。
  • 泛型方法,可以宣告為靜態的。原因:泛型引數是在呼叫方法時確定的,並非在例項化類時確定。

如下方法不是泛型方法

    public T getOrderT() {
        return orderT;
    }

    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }

泛型方法

    public <E> List<E> copyFromArrayToList(E[] arr){
        ArrayList<E> list = new ArrayList<>();
        for(E e : arr){
            list.add(e);
        }
        return list;
    }

    @Test
    public void test9() {
        Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
        // 泛型方法在呼叫時,指明泛型引數的型別
        List<Integer> list = copyFromArrayToList(arr);
        System.out.println(list);
    }

靜態泛型方法

    public static <E> List<E> copyFromArrayToList(E[] arr){
        ArrayList<E> list = new ArrayList<>();
        for(E e : arr){
            list.add(e);
        }
        return list;
    }

    @Test
    public void test9() {
        Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
        List<Integer> list = GenericTest.copyFromArrayToList(arr);
        System.out.println(list);
    }

(4)泛型在繼承上的體現

類A是類B的父類, G<A>和 G<B>二者不具備子父類關係,二者是並列關係。

    @Test
    public void test10(){
        Object object = null;
        String string = null;
        object = string;

        Object[] arr1 = null;
        String[] arr2 = null;
        arr1 = arr2;

        List<Object> list1 = null;
        List<String> list2 = null; 
// 此時list1 和 list2 的型別不具有子父類關係 ;編譯不通過
// list1 = list2; // Required type: List<Object> Provided : List<String> }

類A是類B的父類, A<G>是 B<G> 的父類

    @Test
    public void test11(){
        AbstractList<String> list1 = null;
        List<String> list2 = null;
        ArrayList<String> list3 = null;
        list1 = list3;
        list2 = list3;
    }

public class Dao<T> {
    // 新增一條記錄
    public void add(T t){
    }

    // 刪除一條記錄
    public boolean remove(int index){
        return false;
    }

    public  List<T> getForList(int index){
        return null;
    }

    // 泛型方法: 獲取表中一共有多少條記錄? 獲取最大的員工年齡
    public <E> E getValue(){
        return null;
    }
}
public class PersonDao extends Dao<Person> {
}
   @Test
    public void test1(){
        PersonDao personDao = new PersonDao();
        personDao.add(new Person());

        List<Person> personList = personDao.getForList(10);

    }

(5)萬用字元的使用

  • 使用型別萬用字元:? 如List<?> Map<?,?>。List<?>是 List<String>、List<Object>等各種泛型List的父類。
  • 讀取List<?>的物件list中的元素時,永遠是安全的,因為不管list的真實型別是什麼,它包含的都是Object。
  • 寫入list中的元素時,不行。因為不知道c的元素型別,不能向其中新增物件。唯一的例外是null,它是所有型別的成員。
  • <?>允許所有泛型的引用呼叫
  • 萬用字元指定上限:上限extends:使用時指定的型別必須是繼承某個類,或者實現某個介面,即 <=
  • 萬用字元指定下限:下限super: 使用時指定的型別不能小於操作的類,即>=
    • <? extends Number> (無窮下,Number] 只允許泛型為Number及Number子類的引用呼叫
    • <? super Number> [Number,無窮大) 只允許泛型為Number及Number父類的引用呼叫
    • <? extends Comparable> 只允許泛型為實現Comparable介面的實現類的引用呼叫

類A是類B的父類, G<A>和G<B>是沒有關係的,二者共同的父類是: G<?>

    @Test
    public void test12() throws NullPointerException {
        List<Object> list1 = null ;
        List<String> list2 = null;
        List<?> list = null;
        list = list1;
        list = list2;
    
        print(list1);
        print(list2);
    }
public void print(List<?> list) { Iterator<?> iterator = list.iterator(); while (iterator.hasNext()){ Object obj = iterator.next(); System.out.println(obj); } }

List<?>不能新增資料,除了null。可以讀取資料,資料型別為Object。

    @Test
    public void test12(){
        List<String> list3 = new ArrayList<>();
        list3.add("AAA");
        List<?> list = new ArrayList<>();
        list = list3;
//        list.add("DDD"); // 對於List<?> 就不能向其內部新增資料。除了null之外。
        System.out.println(list); // [AAA]
        Object o = list.get(0); // 允許讀取資料,資料型別為Object
        System.out.println(o); //AAA
    }

  public void test13(){
        List<? extends Person> list1 = null;
        List<? super Person> list2 = null;
        List<Student> list3 = null;
        List<Person> list4 = null;
        List<Object> list5 = null;

        list1 = list3;
        list1 = list4;
//        list1 = list5; // list1 的範圍(無窮, Person]
//        list2 = list3;  // list2 的範圍 [Person, 無窮)
        list2 = list4;
        list2 = list5;

        Person p1 = list1.get(0);
//        Student  s1 = list1.get(0); //編譯不通過
        Object o1 = list2.get(0);
//        Person p2 = list2.get(0); // 編譯不通過

//        list1.add(new Student());  // 編譯不通過
//        list1.add(new Person());   // 編譯不通過
//        list1.add(new Object())    // 編譯不通過
        list2.add(new Person());
        list2.add(new Student());
    }

(6)泛型應用

(6.1)泛型巢狀

    @Test
    public void test14(){
        HashMap<String, ArrayList<Person>> map = new HashMap<>();
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("孫悟空",1000));
        list.add(new Person("豬八戒",400));
        list.add(new Person("沙僧",100));
        list.add(new Person("唐僧",30));
        map.put("唐僧",list);
        
        Set<Map.Entry<String, ArrayList<Person>>> entrySet = map.entrySet();
        Iterator<Map.Entry<String, ArrayList<Person>>> iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, ArrayList<Person>> entry = iterator.next();
            String key = entry.getKey();
            ArrayList<Person> value = entry.getValue();
            System.out.println("領導:" + key);  //領導:唐僧
            //團隊成員[Person{name='孫悟空', age=1000}, Person{name='豬八戒', age=400}, Person{name='沙僧', age=100}, Person{name='唐僧', age=30}]
            System.out.println("團隊成員" + value); 
        }
    }

通過構造器設定資訊屬性內容

public class Employee<T extends Info> {
    private T info;
    public Employee(T info){
        this.info = info;
    }

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "info=" + info +
                '}';
    }
}

(6.2)案例: User - Dao - UserDao - UserTest

User.class

public class User {
    private int id;
    private String name;
    private int age;
    //省略其他程式碼
}

Dao.class

public class Dao<T> {
    private Map<String,T> map = new HashMap<>();
    // 儲存 T型別的物件到 Map成員變數中
    public void save(String id, T entity){
        map.put(id,entity);
    }
    // 從map中獲取id對應的物件
    public T get(String id){
        return map.get(id);
    }
    // 替換map中key 為id的內容,改為entity物件
    public void update(String id, T entity){ 
if(map.containsKey(id){ map.put(id, entity);
} } // 返回 map中存放的所有 T 物件
public List<T> list(){ List<T> list = new ArrayList<>(); for(T t : map.values()){ list.add(t); } return list; } // 刪除指定 id 物件 public void delete(String id){ map.remove(id); } }

UserDao.class

public class UserDao extends Dao<User> {
}

UserTest.class

    @Test
    public void test1(){
        UserDao userDao = new UserDao();
        User u1 = new User(1,"AAA",13);
        User u2 = new User(2,"BBB",23);
        User u3 = new User(3,"CCC",15);
        userDao.save("AAA",u1);
        userDao.save("BBB",u2);
        userDao.save("CCC",u3);

        List<User> list = userDao.list();
        System.out.println(list); //[User{id=1, name='AAA', age=13}, User{id=3, name='CCC', age=15}, User{id=2, name='BBB', age=23}]

        User user = userDao.get("AAA");
        System.out.println(user);//User{id=1, name='AAA', age=13}

        userDao.update("CCC", new User(13, "CCC", 18));
        userDao.save("Tom",new User(4, "Tom",21));
        userDao.delete("BBB");
        List<User> list1 = userDao.list();
        System.out.println(list1); //[User{id=1, name='AAA', age=13}, User{id=13, name='CCC', age=18}, User{id=4, name='Tom', age=21}]
    }