您好,登錄后才能下訂單哦!
小編這次要給大家分享的是詳解JAVA泛型,文章內容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。
泛型的概念:Java泛型(generics)是JDK1.5中引入的一個新特性,泛型提供了編譯時的類型安全監測機制,該機制允許我們在編譯時檢測到非法的類型數據結構。
泛型的本質就是類型參數化,也就是所操作的數據類型被指定為一個參數。
使用泛型的好處:
1 在編譯期間提供了類型檢查
2 取數據時無須進行類型裝換
語法:
class 類名稱 <泛型標識,泛型標識,泛型標識,...> { private 泛型標識 變量名; // ... }
常用的泛型標識:T、E、K、V
使用語法:
類名 <具體的數據類型> 對象名 = new 類名<具體的數據類型>();
JDK 1.7 之后,后面的 <> 中的具體的數據類型可以省略不寫。
定義一個簡單的泛型類:
/** * 泛型類 T:類型形參,在類創建對象時,指定具體的數據類型 * @author rainszj * 2020/3/19 */ public class GenericDemo01<T> { private T value; public GenericDemo01() { } public GenericDemo01(T value) { this.value = value; } @Override public String toString() { return "GenericDemo01{" + "value=" + value + '}'; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
測試一下:
public class Test { public static void main(String[] args) { // 在創建對象時指定具體的數據類型 GenericDemo01<String> genericDemo01 = new GenericDemo01<>("java"); // 泛型類不支持基本數據類型,但可以使用基本類型對應的包裝類 GenericDemo01<Integer> genericDemo02 = new GenericDemo01<>(1); // 在泛型類對象時,不指定具體的數據類型,將會使用Object類型來接收 // 同一個泛型類,根據不同數據類型創建的對象,本質上是同一類型,公用同一個類模板 // class com.rainszj.GenericDemo01 System.out.println(genericDemo01.getClass()); // class com.rainszj.GenericDemo01 System.out.println(genericDemo02.getClass()); // true System.out.println(genericDemo01.getClass() == genericDemo02.getClass()); } }
注意事項:
泛型類,如果沒有指定具體的數據類型,按Object類型來接收
泛型的類型參數只能是類類型,也就是引用數據類型,不能是基本數據類型
泛型類型在邏輯上可以看成是多個不同的類型,但實際上都是相同類型
/** * 抽獎池 * * @author rainszj * 2020/3/19 */ public class ProductGetter<T> { // 獎品 private T product; private ArrayList<T> list = new ArrayList<>(); /** * 添加獎品 * * @param product */ public void addProduct(T product) { list.add(product); } /** * 抽取隨機獎品 * * @return */ public T getProduct() { return list.get(new Random().nextInt(list.size())); } @Override public String toString() { return "ProductGetter{" + "product=" + product + '}'; } } public static void main(String[] args) { ProductGetter<String> productGetter1 = new ProductGetter<>(); // 獎品類型 禮物 String[] products1 = {"華為手機", "蘋果手機", "掃地機器人", "微波爐"}; // 添加獎品 for (int i = 0, length = products1.length; i < length; i++) { productGetter1.addProduct(products1[i]); } // 獲取獎品 String product1 = productGetter1.getProduct(); System.out.println("恭喜您抽中了," + product1.toString()); ProductGetter<Integer> productGetter2 = new ProductGetter<>(); // 獎品類型 money Integer[] products2 = {1000, 3000, 10000, 500}; for (Integer money : products2) { productGetter2.addProduct(money); } Integer product2 = productGetter2.getProduct(); System.out.println("恭喜您抽中了," + product2.toString()); }
子類也是泛型類,子類的泛型標識 T 要和父類的泛型標識 T 保持一致,或者是包含關系,子類的泛型標識包含父類的泛型標識
class ChildGeneric<T> extends ParentGeneric<T> class ChildGeneric<T, E> extends ParentGeneric<T>
子類不是泛型類,父類要明確泛型的數據類型
class ChildGeneric extends ParentGeneric<String>
語法:
interface 接口名稱 <泛型標識,泛型標識,...> { 泛型標識 方法名(); }
實現泛型接口的類,不是泛型類,需要明確實現泛型接口的數據類型
public class Apple implements Generic<String> {}
實現類也是泛型類,實現類和接口的泛型類型要一致,或者是包含關系,實現類的泛型標識包含泛型接口的泛型標識
public class Apple<K> implements Generic<K> {} public class Apple<K, V> implements Generic<K> {}
定義一個泛型接口
public interface Generic<K> { K getKey(); }
實現其中方法:
/** * 泛型接口的實現類,是一個泛型類, * 那么要保證實現接口的泛型類的泛型標識包含泛型接口的泛型標識 */ public class Pair<K, V> implements Generic<K> { private K key; private V value; public Pair() { } public Pair(K key, V value) { this.key = key; this.value = value; } @Override public K getKey() { return key; } public V getValue() { return value; } @Override public String toString() { return "Pair{" + "key=" + key + ", value=" + value + '}'; } }
測試:
public class MyTest { public static void main(String[] args) { Pair<String, Integer> pair = new Pair<>("數學", 100); System.out.println(pair.toString()); // Pair{key=數學, value=100} } }
泛型類,是在實例化類時指明泛型的具體類型。
泛型方法,是在調用方法時,指明泛型的具體類型。
語法:
修飾符 <T,E,...> 返回值類型 方法名(形參列表) { // 方法體... }
public
與返回值中間 <T,E,...>
(泛型列表)非常重要,可以理解為聲明此方法為泛型方法。
只有聲明了 <T,E,...>
的方法才是泛型方法,泛型類中使用了泛型的成員方法并不是泛型方法
<T>
表明該方法將使用泛型類型 T,此時才可以在方法中使用泛型類型 T。
public class ProductSetter<T> { private T product; private Random random= new Random(); private ArrayList<T> list = new ArrayList<>(); public void addProduct(T product) { list.add(product); } /** * @param list * @param <E> 泛型方法的類型,是在調用泛型方法時確定的 * @return */ public <E> E getProduct(ArrayList<E> list) { return list.get(random.nextInt(list.size())); } public T getProduct() { return list.get(random.nextInt(list.size())); } @Override public String toString() { return "ProductSetter{" + "product=" + product + '}'; } }
測試:
public static void main(String[] args) { ProductSetter<String> productSetter = new ProductSetter<>(); String[] products1 = {"華為手機", "蘋果手機", "掃地機器人", "微波爐"}; for (int i = 0; i < products1.length; i++) { productSetter.addProduct(products1[i]); } System.out.println(productSetter.getProduct()); ArrayList<String> list1 = new ArrayList<>(); list1.add("華碩電腦"); list1.add("蘋果電腦"); list1.add("華為電腦"); String product1 = productSetter.getProduct(list1); System.out.println(product1 + "\t" + product1.getClass().getSimpleName()); // 華為電腦 String ArrayList<Integer> list2 = new ArrayList<>(); list2.add(1); list2.add(2); list2.add(3); Integer product2 = productSetter.getProduct(list2); System.out.println(product2 + "\t" + product2.getClass().getSimpleName()); // 2 Integer }
public static <T, E, K> void pringType(T k1, E k2, K k3) { System.out.println(k1 + "\t" + k1.getClass().getSimpleName()); System.out.println(k2 + "\t" + k2.getClass().getSimpleName()); System.out.println(k3 + "\t" + k3.getClass().getSimpleName()); } // 方法的調用 ProductSetter.pringType(1, "hello", false);
// 輸出結果
1 Integer
hello String
false Boolean
注意:
// 在泛型類中無法添加靜態的 帶有泛型成員方法,但可以添加靜態的 泛型方法 public class Test<T> { // 帶有泛型的成員方法 // 錯誤 public static T getKey(T key) { return key; } // 泛型方法 // 正確 public static <E> E getKey(E key) { return key; } }
public class MyTest { public static void main(String[] args) { MyTest.print(1, 2, 3); } /** * 泛型方法中的可變長參數 * @param value * @param <E> */ public static <E> void print(E ... value) { for (int i = 0; i < value.length; i++) { System.out.println(value[i]); } } }
總結:
泛型方法能使方法獨立于類而產生變化。
如果 static
方法要使用泛型能力,就必須使其成為泛型方法。
類型通配符一般是使用 ?
代替具體的類型實參。
類型通配符是類型實參,而不是類型形參。
我們先來定義一個簡單的泛型類:
public class Box<T> { private T width; public static void showBox(Box<Number> box) { Number width = box.getWidth(); System.out.println(width); } public T getWidth() { return width; } public void setWidth(T width) { this.width = width; } }
main方法:
public static void main(String[] args) { Box<Number> box1 = new Box<Number>(); box1.setWidth(100); showBox(box1); }
當我們在 main 方法中增加這一段代碼時,就會報錯
Box<Integer> box2 = new Box<>(); box2.setWidth(200); showBox(box2);
雖然 Integer 類繼承自 Number 類,但在類型通配符中不存在繼承這一概念!
也許你會使用方法的重載,但是 在同一個泛型類中,根據不同數據類型創建的對象,本質上是同一類型,公用同一個類模板,所以無法通過方法的重載,傳遞不同的泛型類型。
這時可以使用類型通配符 ?
,來代表具體的類型實參!
public static void showBox(Box<?> box) { Object width = box.getWidth(); System.out.println(width); }
在我們上面的showBox()代碼中,發現 box.getWidth()
得到的還是Object類型,這和我們不使用類型通配符,得到的結果是一樣的。這時我們可以使用類型通配符的上限。
語法:
類/接口 <? entends 實參類型>
要求該泛型的類型,只能是實參類型,或者是實參類型的子類類型。
public static void showBox(Box<? extends Number> box) { Number width = box.getWidth(); System.out.println(width); } public static void main(String[] args) { Box<Integer> box2 = new Box<>(); box2.setWidth(200); showBox(box2); }
使用類型通配符的下限,無法得知該類型具體是指定的類型,還是該類型的子類類型,因此無法在 List 集合中執行添加該類或者該類子類的操作!
public static void showAnimal(List<? extends Cat> list) { // 錯誤 list.add(new Cat()); list.add(new MiniCat()); }
語法
類/接口 <? super 實參類型>
要求該泛型的類型,只能是實參類型,或者是實參類型的父類類型。
下面通過 TreeSet 集合中的一個構造方法來進一步理解 類型通配符的下限
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
首先是一個Animal類,只有一個 name 屬性
public class Animal { private String name; public Animal(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Animal{" + "name='" + name + '\'' + '}'; } }
然后它的一個子類,Cat添加一個屬性:age
public class Cat extends Animal { private int age; public Cat(String name, int age) { super(name); this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Cat{" + "age=" + age + '}'; } }
最后是 Cat 的子類,MiniCat,再添加一個屬性 level
public class MiniCat extends Cat { private int level; public MiniCat(String name, int age, int level) { super(name, age); this.level = level; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } @Override public String toString() { return "MiniCat{" + "level=" + level + '}'; } }
測試,首先我們要在MyTest類通過靜態內部類的方式,實現比較的接口,在構造TreeSet時,傳遞比較器
public class MyTest { public static void main(String[] args) { // 正常 // TreeSet<Cat> animals = new TreeSet<Cat>(new Comparator1()); // 正常 TreeSet<Cat> animals = new TreeSet<Cat>(new Comparator2()); // 報錯 // TreeSet<Cat> animals = new TreeSet<Cat>(new Comparator3()); List<Cat> list = Arrays.asList(new Cat("a", 12), new Cat("c", 9), new Cat("b", 20)); animals.addAll(list); animals.forEach(System.out::println); } public static class Comparator1 implements Comparator<Animal> { @Override public int compare(Animal o1, Animal o2) { return o1.getName().compareTo(o2.getName()); } } public static class Comparator2 implements Comparator<Cat> { @Override public int compare(Cat o1, Cat o2) { return o1.getAge() - o2.getAge(); } } public static class Comparator3 implements Comparator<MiniCat> { @Override public int compare(MiniCat o1, MiniCat o2) { return o1.getLevel() - o2.getLevel(); } } }
結論:
通過以上的比較,我們可以看出,類型通配符的下限,只能傳遞實參類型的或者實參類型的父類類型。
我們每次比較使用的都是 Cat 類型,但在 Comparator1
比較的是 Animal 中的 name 屬性,這是因為 我們在初始化 Cat 對象的時候,一定會先初始化 Animal 對象,也就是創建子類對象的時候,一定會先創建父類對象,所以才可以進行比較。
如果是使用 類型通配符的上限,在創建對象時,比較的是該類的子類對象中的屬性,就會造成空指針異常!也就是Comparator3
無法使用的原因, 所以在 TreeSet
中才會使用 <? super E>
,類型通配符的下限。
泛型是Java 1.5 引進的概念,在這之前是沒有泛型的,但是,泛型代碼能夠很好地和之前的代碼兼容。那是因為,泛型信息只存在編譯階段,在進入 JVM 之前,與泛型相關的信息會被擦除掉,我們稱之為——類型擦除。
先定義一個泛型類:
public class Erasure<T> { private T key; public T getKey() { return key; } public void setKey(T key) { this.key = key; } }
輸出結構:
public static void main(String[] args) { Erasure<Integer> erasure = new Erasure<>(); Class<? extends Erasure> cls = erasure.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName() + ":" + field.getType().getSimpleName()); // key:Object } }
可以發現在編譯完成后的字節碼文件中,T --> Object 類型
還是剛才的泛型類,只不過加了泛型的上限
public class Erasure<T extends Number> {// ...}
測試不變,輸出結果:
key:Number
當我們指定了泛型的上限時,它會將我們的泛型擦除為上限類型
同樣對泛型方法,也是一樣的道理
// 泛型方法 public <E extends List> E test(E t) { return t; }
Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName()); } // 輸出結果 // getKey:Number // test:List // setKey:void
泛型接口
public interface Info<T> { T test(T value); }
泛型接口的實現類
public class InfoImpl implements Info<Integer> { @Override public Integer test(Integer value) { return value; } }
測試
public static void main(String[] args) { Class cls = InfoImpl.class; Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName()); } } // 輸出結果: // test:Integer // test:Object
原本 InfoImpl 中只是實現了 Info 接口中的一個方法,但通過反射卻拿到了兩個方法。其中返回值為 Object 的方法就是橋接方法。
在編譯完成后,類型擦除的結果是這樣的:
public interface Info { Object test(Object value); }
public class InfoImpl implements Info { public Integer test(Integer value) { return value; } // 橋接方法:保持接口和類的實現關系 @Override public Object test(Object value) { return (Integer)value; } }
開發中,一般常用的是泛型集合
泛型數組的創建:
可以聲明帶泛型的數組引用,但是不能直接創建帶泛型數組對象。
可以通過 java.lang.reflect.Array
的 newInstance(Class<T>, int)
創建 T[ ] 數組。
// 可以創建帶泛型的數組引用 ArrayList<String>[] arrayLists1 = new ArrayList[3]; // 無法創建帶泛型的數組對象 ArrayList<String>[] arrayLists2 = new ArrayList<String>[3];
簡單使用 java.lang.reflect.Array
的 newInstance(Class<T>, int)
創建 T[ ] 數組。 封裝一個泛型數組
public class GenericArray<T> { private T[] array; public GenericArray(Class cls, int length) { this.array = (T[]) Array.newInstance(cls, length); } public void put(int index, T item) { this.array[index] = item; } public T get(int index) { return this.array[index]; } public T[] getArray() { return this.array; } public static void main(String[] args) { GenericArray<String> ga = new GenericArray<>(String.class, 3); ga.put(0, "白虎"); ga.put(1, "青龍"); ga.put(2, "朱雀"); System.out.println(Arrays.toString(ga.getArray())); } }
反射常用的泛型類:
Class
Constructor
通過反射創建對象,帶泛型和不帶泛型
Class<Cat> catClass1 = Cat.class; try { Constructor<Cat> c1 = catClass1.getConstructor(); Cat cat = c1.newInstance(); } catch (Exception e) { e.printStackTrace(); } Class catClass2 = Cat.class; try { Constructor c2 = catClass2.getConstructor(); Object cat2 = c2.newInstance(); } catch (Exception e) { e.printStackTrace(); }
看完這篇關于詳解JAVA泛型的文章,如果覺得文章內容寫得不錯的話,可以把它分享出去給更多人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。