天天看點

2021-07-22一、泛型二、自定義泛型三、泛型通配符四、泛型方法

注:自己學習筆記

一、泛型

注:泛型資料類型要用引用類型

1、引出問題

Java 集合有個缺點,把一個對象"丢進"集合裡之後,集合就會"忘記"這個對象的資料類型,當再次取出該對象時 該對象的編譯類型就變Object類型(其運作時類型沒變),Java集合之是以被設計成這樣,是因為集合的設計者不知道我們會用集合來儲存什麼類型的對象是以他們把集合設計成能儲存任何類型的對象,隻要求具有很好的通用性。但是,這樣做有很大的問題:

public static void main(String[] args) {
    List persons = new ArrayList();
    persons.add("蘇蘇");
    persons.add("劉亦菲");
    persons.add("胡歌");
    //不小心加入了一個int類型的資料
    persons.add(10);
    persons.forEach(person -> System.out.println(((String) person).length()));
}
           

比如上面的程式,如果我們不小心加入了一個int類型的資料,程式報錯。

2、泛型的簡單使用

public class ProblemTest {
    public static void main(String[] args) {
        List<String> persons = new ArrayList<String>();
        persons.add("蘇蘇");
        persons.add("劉亦菲");
        persons.add("胡歌");
        persons.forEach(person -> System.out.println(((String) person).length()));
    }
}
           

Map是key和value兩個資料組成一個元素,key和value都是具有類型的,那這種兩個元素的,我們怎麼辦?

Map<String,Integer> persons = new HashMap<String, Integer>();
persons.put("蘇蘇",18);
persons.put("劉亦菲",23);
persons.put("胡歌",28);
persons.forEach((key, value)-> System.out.println(key+"="+value));
           

3、泛型的“菱形”文法

我們可以看得出來,上面在建立對象的時候,我們定義的時候我們加上了泛型,然後我們在調用具體的構造器的時候,我們也加上了泛型,這個是不是就很多餘啊?因為我們完全可以通過定義的泛型就明确我們使用的泛型是什麼。

一般寫法:

Set<String> persons = new HashSet<>();
Map<String, Integer> persons = new HashMap<>();
           

二、自定義泛型

1、自定義泛型

Iterator對象定義泛型:

2021-07-22一、泛型二、自定義泛型三、泛型通配符四、泛型方法

Map定義泛型:

2021-07-22一、泛型二、自定義泛型三、泛型通配符四、泛型方法

可以看到,上面自定義泛型的時候,我們就是在類名的後面加上類型的替代符,比如上面的T,K,V。我們在使用的時候,就直接把這些T,K,V替換成對應的類型名稱就好了。

public class DefineTest {
    private static class A<T>{
        private T info;

        public A(T info) {
            this.info = info;
        }

        public T getInfo() {
            return info;
        }
    }

    public static void main(String[] args) {
        A<String> a = new A<>("劉亦菲");
        System.out.println(a.getInfo());//劉亦菲
        A<Integer> b = new A<>(25);
        System.out.println(b.getInfo());//25
    }
}
           

2、從泛型派生子類

當建立了帶泛型申明的接口,父類之後,可以為該接口建立實作類,或建立該類派生子類。需要說明,當使用了這些接口,父類就不能再包含泛型參數。

比如下面代碼是不行的:

private static class B extends A<T> {// A<T>是不能直接這樣寫的,不能直接把父類泛型傳過來
    public B(T info) {
        super(info);
    }
}
           

我們可以用如下方式派生一個子類:

private static class B extends A<String> {
        public B(String info) {
            super(info);
        }
    }
    public static void main(String[] args) {
        B b = new B("劉亦菲");
        System.out.println(b.getInfo());
    }
           

但是,有時候,我們在定義一個子類的時候,我們還是無法判斷泛型的類型:

可以這樣做,B也加一個泛型;

private static class B<T> extends A<T> {
        public B(T info) {
            super(info);
        }
    }
    public static void main(String[] args) {
        B<String> b = new B<>("劉亦菲");
        System.out.println(b.getInfo());
    }
           

3、并不存在泛型類

上面運作出來,我們發現a1.getClass()和a2.getClass()相等,那證明不存在泛型類。

三、泛型通配符

1、引出問題

比如我們有如下一個程式,思考一下運作結果是什麼?

public class Test {
    private static void test1(List<Object> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }
    public static void main(String[] args) {
        List<String> persons = new ArrayList<>();
        persons.add("蘇蘇");
        persons.add("劉亦菲");
        persons.add("胡歌");
        test1(persons);
    }
}
           

上面這個程式,編譯就出現錯誤,因為我們要求你傳入的是List不是List,可能會有疑問,會說String不是Object的子類嗎?當然是,但是List不是List的子類。

解決:

public class Test {
    private static void test1(List<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }
    public static void main(String[] args) {
        List<String> persons = new ArrayList<>();
        persons.add("蘇蘇");
        persons.add("劉亦菲");
        persons.add("胡歌");
        test1(persons);
    }
}
           

上面的這個List,其中的?表示的就是一個通配符,表示可以比對任何類型。再試用的時候,通配符裡面的元素,我們用Object類型。

2、設定通配符下限

public class WildcardTest {
    private static class Animal {
        public void say() {
            System.out.println("我是動物,我要說話了");
        }
    }
    private static class Dog extends Animal {
        @Override
        public void say() {
            System.out.println("我是小狗,汪汪汪");
        }
    }
    private static class Cat extends Animal {
        @Override
        public void say() {
            System.out.println("我是小貓,喵喵喵");
        }
    }
    //List<? extends Animal>這個的意思就是限定我們的List中的泛型必須是Animal或者Animal的子類
    private static void animalSay(List<? extends Animal> animals) {
        animals.forEach(animal -> animal.say());
    }

    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Cat());
        animalSay(animals);
        System.out.println("=========================================================");
        List<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        animalSay(dogs);
        System.out.println("=========================================================");
        List<Cat> cats = new ArrayList<>();
        cats.add(new Cat());
        animalSay(cats);
    }
}
           

2、設定通配符上限

private static class Apple<T extends Number & Serializable> {
    //Serializable是接口
        private T info;
        public Apple(T info) {
            this.info = info;
        }
        public T getInfo() {
            return info;
        }
    }
    public static void main(String[] args) {
        Apple<Integer> apple1 = new Apple<>(50);
        System.out.println(apple1.getInfo());
    }
           

四、泛型方法

1、引出問題

假設我們有一個方法,會把一個Object數組的元素複制到一個List中:

public class MethodTest {
    private static void arrayToList(Object[] objs, List<Object> list) {
        for (Object obj : objs) {
            list.add(obj);
        }
    }
    public static void main(String[] args) {
        Object[] strArr = {"胡歌", "劉亦菲", "蘇蘇"};
        List<String> list = new ArrayList<>();
        arrayToList(strArr, list);
    }
}
           
2021-07-22一、泛型二、自定義泛型三、泛型通配符四、泛型方法

通配符就可以了,是不是呢?

2021-07-22一、泛型二、自定義泛型三、泛型通配符四、泛型方法

我們發現編譯報錯,為什麼呢?因為Java不允許我們把對象放在一個未知的集合中。

2、泛型方法

為了解決這個問題,可以使用Java提供的泛型方法(Generic Method)。所謂泛型方法,就是在聲明方法時定義一個或多個泛型形參。泛型方法的文法格式如下:

修飾符 <T , S> 傳回值類型 方法名(形參清單){    方法體...}
           

泛型方法其實就是定義我們傳進來的參數的泛型

public class MethodTest {
    //泛型方法其實就是定義我們傳進來的參數的泛型
    private static<T> void arrayToList(T[] objs, List<T> list) {
        for (T obj : objs) {
            list.add(obj);
        }
    }
    public static void main(String[] args) {
        String[] strArr = {"胡歌", "劉亦菲", "蘇蘇"};
        List<String> list = new ArrayList<>();
        arrayToList(strArr, list);
        System.out.println(list);//[胡歌, 劉亦菲, 蘇蘇]
    }
}
           

如下程式:

private static <T> void test(List<T> list1, List<T> list2){
        System.out.println(list1);
        System.out.println(list2);
    }
    public static void main(String[] args) {
        List<Object> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        List<String> list2 = new ArrayList<>();
        list2.add("胡歌");
        list2.add("劉亦菲");
        list2.add("鞠婧祎");
        test(list1, list2);
    }
           

像上面這個程式,要求傳入的參數list1和list2的泛型必須是一樣的,但是,你傳入了不一樣的泛型,這個會導緻編譯報錯。

但是上面的程式如果修改為通配符的模式,可以解決這個問題。

private static <T> void test(List<T> list1, List<? extends T> list2){
    System.out.println(list1);
    System.out.println(list2);
}
           

我們還可以通過定義兩個泛型來解決問題:

private static <T, E> void test(List<T> list1, List<E> list2){
    System.out.println(list1);
    System.out.println(list2);
}
           

3、泛型構造器

public class GenterTest {
    private static class Foo {
        public <T> Foo(T t) {
            System.out.println(t);
        }
    }
    public static void main(String[] args) {
        new Foo("李一桐");//T表示一個String
        new Foo(500);//T表示一個Integer
        new <String> Foo("鞠婧祎");//顯示的指定了泛型,傳入的參數類型要和指定的一樣
        new <String> Foo(400);//編譯報錯
    }
}
           

4、Java8改進泛型推斷判斷

public class InferTest {
    private static class A<T> {
        public static <E> A<E> test1() {
            System.out.println("test1");
            return new A<>();
        }
        public static <E> A<E> test2(E e, A<E> a) {
            System.out.println("test2");
            return new A<>();
        }
        public T head() {
            System.out.println("test3");
            return null;
        }
    }
    public static void main(String[] args) {
        //下面兩行代碼相同
        A<String> a1 = A.test1();
        A<String> a2 = A.<String>test1();
        //下面兩行代碼相同
        A.test2(56, A.test1());
        A.test2(56, A.<Integer>test1());
    }
}
           
//下面代碼如果用自動類型推斷A.test1().head(),它會經過兩次推斷,
//最後就變成了不但能推斷,因為我們自動類型推斷,隻能推斷1次
String s = A.<String>test1().head();
           

5、擦除

在嚴格的泛型代碼裡,帶泛型聲明的類總應該帶着類型參數。但為了與老的 Java 代碼保持一緻,也允許在使用帶泛型聲明的類時不指定實際的類型。如果沒有為這個泛型類指定實際的類型,此時被稱為 raw type (原始類型),預設是聲明該泛型形參時指定的第一個上限類型。

public class ErasureTest {
    private static class A<T extends Number> {
        private T size;
        public A(T size) {
            this.size = size;
        }
        public T getSize() {
            return size;
        }
    }
    public static void main(String[] args) {
        A<Integer> a = new A<>(50);
        int size = a.getSize();
        System.out.println(size);
        A a1 = a;
        int size1 = a1.getSize();
        System.out.println(size1);
    }
}
           

上面的代碼int size1 = a1.getSize();直接編譯錯誤,是因為我們沒有顯示的指定a1的泛型的類型。這個時候a1會丢棄a的泛型的類型。這個時候我們隻能用Number來接收。