詳解JAVA泛型

小編這次要給大家分享的是詳解JAVA泛型,文章內(nèi)容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。

我們提供的服務(wù)有:網(wǎng)站制作、成都網(wǎng)站建設(shè)、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、印臺ssl等。為成百上千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的印臺網(wǎng)站制作公司

什么是泛型

泛型的概念:Java泛型(generics)是JDK1.5中引入的一個新特性,泛型提供了編譯時的類型安全監(jiān)測機制,該機制允許我們在編譯時檢測到非法的類型數(shù)據(jù)結(jié)構(gòu)。

泛型的本質(zhì)就是類型參數(shù)化,也就是所操作的數(shù)據(jù)類型被指定為一個參數(shù)。

使用泛型的好處:

1  在編譯期間提供了類型檢查

2  取數(shù)據(jù)時無須進行類型裝換

泛型類、接口

泛型類

語法:

class 類名稱 <泛型標(biāo)識,泛型標(biāo)識,泛型標(biāo)識,...> {
 private 泛型標(biāo)識 變量名;
 // ...
}

常用的泛型標(biāo)識:T、E、K、V

使用語法:

類名 <具體的數(shù)據(jù)類型> 對象名 = new 類名<具體的數(shù)據(jù)類型>();

JDK 1.7 之后,后面的 <> 中的具體的數(shù)據(jù)類型可以省略不寫。

定義一個簡單的泛型類:

/**
 * 泛型類 T:類型形參,在類創(chuàng)建對象時,指定具體的數(shù)據(jù)類型
 * @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) {
  // 在創(chuàng)建對象時指定具體的數(shù)據(jù)類型
  GenericDemo01<String> genericDemo01 = new GenericDemo01<>("java");
  // 泛型類不支持基本數(shù)據(jù)類型,但可以使用基本類型對應(yīng)的包裝類
  GenericDemo01<Integer> genericDemo02 = new GenericDemo01<>(1);
  // 在泛型類對象時,不指定具體的數(shù)據(jù)類型,將會使用Object類型來接收

  // 同一個泛型類,根據(jù)不同數(shù)據(jù)類型創(chuàng)建的對象,本質(zhì)上是同一類型,公用同一個類模板
  // 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());
  
 }
}

注意事項:

泛型類,如果沒有指定具體的數(shù)據(jù)類型,按Object類型來接收

泛型的類型參數(shù)只能是類類型,也就是引用數(shù)據(jù)類型,不能是基本數(shù)據(jù)類型

泛型類型在邏輯上可以看成是多個不同的類型,但實際上都是相同類型

/**
 * 抽獎池
 *
 * @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());
}

從泛型類派生子類

子類也是泛型類,子類的泛型標(biāo)識 T 要和父類的泛型標(biāo)識 T 保持一致,或者是包含關(guān)系,子類的泛型標(biāo)識包含父類的泛型標(biāo)識

class ChildGeneric<T> extends ParentGeneric<T>
class ChildGeneric<T, E> extends ParentGeneric<T>

子類不是泛型類,父類要明確泛型的數(shù)據(jù)類型

class ChildGeneric extends ParentGeneric<String>

泛型接口

語法:

interface 接口名稱 <泛型標(biāo)識,泛型標(biāo)識,...> {
 泛型標(biāo)識 方法名();
}

實現(xiàn)泛型接口的類,不是泛型類,需要明確實現(xiàn)泛型接口的數(shù)據(jù)類型

public class Apple implements Generic<String> {}

實現(xiàn)類也是泛型類,實現(xiàn)類和接口的泛型類型要一致,或者是包含關(guān)系,實現(xiàn)類的泛型標(biāo)識包含泛型接口的泛型標(biāo)識

public class Apple<K> implements Generic<K> {}
public class Apple<K, V> implements Generic<K> {}

定義一個泛型接口

public interface Generic<K> {
 K getKey();

}

實現(xiàn)其中方法:

/**
 * 泛型接口的實現(xiàn)類,是一個泛型類,
 * 那么要保證實現(xiàn)接口的泛型類的泛型標(biāo)識包含泛型接口的泛型標(biāo)識
 */
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<>("數(shù)學(xué)", 100);
  System.out.println(pair.toString());
  // Pair{key=數(shù)學(xué), value=100}
 }
}

泛型方法

普通泛型方法

泛型類,是在實例化類時指明泛型的具體類型。

泛型方法,是在調(diào)用方法時,指明泛型的具體類型。

語法:

修飾符 <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> 泛型方法的類型,是在調(diào)用泛型方法時確定的
  * @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
}

靜態(tài)泛型方法

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());
}
// 方法的調(diào)用
ProductSetter.pringType(1, "hello", false);

// 輸出結(jié)果
1 Integer
hello String
false Boolean

注意:

// 在泛型類中無法添加靜態(tài)的 帶有泛型成員方法,但可以添加靜態(tài)的 泛型方法
public class Test<T> {
 
 // 帶有泛型的成員方法
	// 錯誤
	public static T getKey(T key) {
	 return key;
	}
 
 // 泛型方法
	// 正確
	public static <E> E getKey(E key) {
	 return key;
	}
}

泛型方法中的可變參數(shù)

public class MyTest {
 public static void main(String[] args) {
  MyTest.print(1, 2, 3);
 }

 /**
  * 泛型方法中的可變長參數(shù)
  * @param value
  * @param <E>
  */
 public static <E> void print(E ... value) {
  for (int i = 0; i < value.length; i++) {
   System.out.println(value[i]);
  }
 }
}

總結(jié):

泛型方法能使方法獨立于類而產(chǎn)生變化。

如果 static 方法要使用泛型能力,就必須使其成為泛型方法。

類型通配符

類型通配符一般是使用 &#63; 代替具體的類型實參。

類型通配符是類型實參,而不是類型形參。

我們先來定義一個簡單的泛型類:

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);
}

當(dāng)我們在 main 方法中增加這一段代碼時,就會報錯

Box<Integer> box2 = new Box<>();
box2.setWidth(200);
showBox(box2);

雖然 Integer 類繼承自 Number 類,但在類型通配符中不存在繼承這一概念!

也許你會使用方法的重載,但是 在同一個泛型類中,根據(jù)不同數(shù)據(jù)類型創(chuàng)建的對象,本質(zhì)上是同一類型,公用同一個類模板,所以無法通過方法的重載,傳遞不同的泛型類型。

這時可以使用類型通配符 &#63;,來代表具體的類型實參!

public static void showBox(Box<&#63;> box) {
 Object width = box.getWidth();
 System.out.println(width);
}

類型通配符的上限

在我們上面的showBox()代碼中,發(fā)現(xiàn) box.getWidth()得到的還是Object類型,這和我們不使用類型通配符,得到的結(jié)果是一樣的。這時我們可以使用類型通配符的上限。

語法:

類/接口 <&#63; entends 實參類型> 

要求該泛型的類型,只能是實參類型,或者是實參類型的子類類型。

public static void showBox(Box<&#63; 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 集合中執(zhí)行添加該類或者該類子類的操作!

public static void showAnimal(List<&#63; extends Cat> list) {
  // 錯誤
  list.add(new Cat());
 	 list.add(new MiniCat());
}

類型通配符的下限

語法

類/接口 <&#63; super 實參類型> 

要求該泛型的類型,只能是實參類型,或者是實參類型的父類類型。

下面通過 TreeSet 集合中的一個構(gòu)造方法來進一步理解 類型通配符的下限

public TreeSet(Comparator<&#63; 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類通過靜態(tài)內(nèi)部類的方式,實現(xiàn)比較的接口,在構(gòu)造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();
  }
 }
}

結(jié)論:

通過以上的比較,我們可以看出,類型通配符的下限,只能傳遞實參類型的或者實參類型的父類類型。

我們每次比較使用的都是 Cat 類型,但在 Comparator1比較的是 Animal 中的 name 屬性,這是因為 我們在初始化 Cat 對象的時候,一定會先初始化 Animal 對象,也就是創(chuàng)建子類對象的時候,一定會先創(chuàng)建父類對象,所以才可以進行比較。

如果是使用 類型通配符的上限,在創(chuàng)建對象時,比較的是該類的子類對象中的屬性,就會造成空指針異常!也就是Comparator3無法使用的原因, 所以在 TreeSet 中才會使用 <&#63; super E> ,類型通配符的下限。

類型擦除

泛型是Java 1.5 引進的概念,在這之前是沒有泛型的,但是,泛型代碼能夠很好地和之前的代碼兼容。那是因為,泛型信息只存在編譯階段,在進入 JVM 之前,與泛型相關(guān)的信息會被擦除掉,我們稱之為——類型擦除。

無限類型擦除

先定義一個泛型類:

public class Erasure<T> {
 private T key;

 public T getKey() {
 return key;
 }

 public void setKey(T key) {
 this.key = key;
 } 
}

輸出結(jié)構(gòu):

public static void main(String[] args) {
 Erasure<Integer> erasure = new Erasure<>();
 Class<&#63; extends Erasure> cls = erasure.getClass();
 Field[] fields = cls.getDeclaredFields();
 for (Field field : fields) {
 System.out.println(field.getName() + ":" + field.getType().getSimpleName()); // key:Object
 }
}

可以發(fā)現(xiàn)在編譯完成后的字節(jié)碼文件中,T --> Object 類型

有限類型擦除

還是剛才的泛型類,只不過加了泛型的上限

public class Erasure<T extends Number> {// ...}

測試不變,輸出結(jié)果:

key:Number

當(dāng)我們指定了泛型的上限時,它會將我們的泛型擦除為上限類型

同樣對泛型方法,也是一樣的道理

// 泛型方法
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());
}

// 輸出結(jié)果
// getKey:Number
// test:List
// setKey:void

橋接方法

泛型接口

public interface Info<T> {

 T test(T value);
}

泛型接口的實現(xiàn)類

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());
 }
}

// 輸出結(jié)果:
// test:Integer
// test:Object

原本 InfoImpl 中只是實現(xiàn)了 Info 接口中的一個方法,但通過反射卻拿到了兩個方法。其中返回值為 Object 的方法就是橋接方法。

在編譯完成后,類型擦除的結(jié)果是這樣的:

public interface Info {

 Object test(Object value);
}
public class InfoImpl implements Info {

 public Integer test(Integer value) {
  return value;
 }
	
 	// 橋接方法:保持接口和類的實現(xiàn)關(guān)系
 @Override
 public Object test(Object value) {
  return (Integer)value;
 }
}

泛型數(shù)組

開發(fā)中,一般常用的是泛型集合

泛型數(shù)組的創(chuàng)建:

可以聲明帶泛型的數(shù)組引用,但是不能直接創(chuàng)建帶泛型數(shù)組對象。

可以通過 java.lang.reflect.Array newInstance(Class<T>, int)創(chuàng)建 T[ ] 數(shù)組。

// 可以創(chuàng)建帶泛型的數(shù)組引用
ArrayList<String>[] arrayLists1 = new ArrayList[3];
// 無法創(chuàng)建帶泛型的數(shù)組對象
ArrayList<String>[] arrayLists2 = new ArrayList<String>[3];

簡單使用 java.lang.reflect.Array newInstance(Class<T>, int)創(chuàng)建 T[ ] 數(shù)組。 封裝一個泛型數(shù)組

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

通過反射創(chuàng)建對象,帶泛型和不帶泛型

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();
}

看完這篇關(guān)于詳解JAVA泛型的文章,如果覺得文章內(nèi)容寫得不錯的話,可以把它分享出去給更多人看到。

網(wǎng)站題目:詳解JAVA泛型
標(biāo)題URL:http://www.muchs.cn/article10/ghoggo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、靜態(tài)網(wǎng)站搜索引擎優(yōu)化、標(biāo)簽優(yōu)化、云服務(wù)器品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁設(shè)計公司