Java的泛型介绍

什么是泛型

  • 背景:Java推出泛型前,程序员可以构建一个元素类型为 Object 的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。


package com.heima.demo;
//泛型产生的背景
public class MainClass{
   public static void main(String[] args){
       ArrayList list = new ArrayList();
       list.add("java");
       list.add(100);
       list.add(true);
  }
   for(int i=0; i<list.size(); i++){
       Object o = list.get(i);
       String str = (String)o;
       System.out.println(str);
  }
}

泛型的概念

  • Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构(像是编译器类型检测机制)。

  • 泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。


ArrayList<String> strList = new ArrayList<>();
strList.add(100); //此时编译器报错
strList.add("a");
for(int i=0; i<strList.size(); i++){
   //strList.get(i).var
   String s = strList.get(i);
   System.out.println(s);
}
  • 上述表达式采用泛型指定了数据存储的类型必须是String

  • 泛型的好处

    • 类型安全

    • 去除了强制类型的转换

泛型类、接口

  • 泛型类的定义语法

class 类名称 <泛型标识, 泛型标识, ···> {
   private 泛型标识 变量名;
   ······
}
  • 常用的泛型标识:T、E、K、V


package com.heima.demo;

public class Generic<T>{ //泛型标识T 可理解为类型的形参
   //T 由外部使用类的时候具体指定
   private T key; //可以用泛型标识声明成员变量
   
   public Generic(T key){ //T做构造方法的参数类型
       this.key = key;
  }
   public T getKey(){ //T做方法的返回值类型
       return key;
  }
   public void setKey(T key){ //T做普通方法的参数类型
       this.key = key;
  }
   //为了展示效果,重写toString方法。
   @Override
   public String toString(){
       return "Generic{" +
           "key=" + key +
           '}';
  }
}
  • 在创建对象的时候需要为 T 指定具体的数据类型

  • 泛型类的使用语法

    • 类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();

  • Java 1.7 以后,后面的<>中的具体的数据类型可以省略不写

    • 类名 <具体的数据类型> 对象名 = new 类名<>();


package com.heima.demo;
/*
* 泛型类
*/
public class MainClass{
   public static void main(String[] args){
       //泛型类在创建对象的时候来指定操作的具体数据类型
       Generic<String> strGeneric= new Generic<>("a");
       String key1 = strGeneric.getKey();
       System.out.println("key1:" + key1);
       
       Generic<Integer> intGeneric = new Generic<>("100");
       Integer key2 = intGeneric.getKey();
       //int key2 = intGeneric.getKey(); 自动拆箱
       System.out.println("key2:" + key2);
       
       //泛型类在创建对象的时候没有指定类型将按照Object类型来操作
       Generic generic = new Generic("A"); //此时括号内类型为Object,传递任意八大基本类型都可以。
       Object key3  = generic.getKey();
       System.out.println("key3:" + key3);
       
       System.out.println("****************");
       System.out.println(intGeneric.getClass());
       System.out.println(strGeneric.getClass());
       System.out.println(strGeneric.getClass() == strGeneric.getClass()); //对比看内存地址是否相等
  }
}
  • 泛型类不支持基本数据类型传入,它只支持类类型,因为我们在使用泛型的时候,编译器会把 T 转换成 Object 类型,在使用类成员的时候又挑选适当的时机,把 Object 的 T 转换成传递进来的实际类型。int 并非继承自 Object ,所以在编译时底层把 T 转换成 Object 来处理的时候,Object 无法持有八大基本类型的 int ,所以声明无法完成。

  • Generic<int> generic = new Generic<int>(100); 会报错

  • 同一泛型类,根据不同的数据类型创建的类对象,本质上是同一类型。

  • 泛型类的注意事项

    • 如果没有指定具体的数据类型,操作类型就是默认的 Object

    • 泛型的类型参数只能是类类型,不能是基本数据类型

    • 泛型类的类型在逻辑上可以看成是多个不同的类型,但实际上是相同的类型,就是他们定义的泛型类类型,因为存在类型擦除机制。


package com.heima.demo;
//泛型类抽奖器
public class Bonus<T>{
   Random random = new Random();
   private T bonus; //奖品:是物品还是奖金只有使用的时候才知道
   ArrayList<T> list = new ArrayList<>(); //奖品池
   //定义一个添加奖品的方法
   public void addBonus(T t){
       list.add(t);
  }
   //抽奖的方法
   public T getBonus(){
       bonus = list.get(random.nextInt(list.size())); //随机获取奖品
       return bonus;
  }
}

package com.heima.demo;
//使用抽奖器
public class MainClass{
   public static void main(String[] args){
       //创建抽奖器对象,指定数据类型。
       Bonus<String> strBonus = new Bonus<>();
       String[] str = {"iphone", "HUAWEI", "Robot", "Caffe"};
       //给抽奖器填充奖品
       for(int i=0; i<str.length; i++){
           strBonus.addBonus(str[i]);
      }
       //抽奖
       String bonus1 = strBonus.getBonus();
       System.out.println("恭喜您抽中了:" + bonus1);
       
       System.out.println("**************");
       
       Bonus<Integer> intBonus = new Bonus<>();
       int[] intbonus = {10000, 5000, 3000, 500, 30000};
       for(int i=0; i<intbonus.length; i++){
           intBonus.addBonus(intbonus[i]);
      }
       
       Integer bonus2 = intBonus.getBonus();
       System.out.println("恭喜您抽中了:" + bonus2);
  }
}

泛型类的继承情况

  • 从泛型类派生子类

    • 子类也是泛型类,子类和父类的泛型类型要一致。

      • class ChildGeneric<T> extends Generic<T>

    • 子类不是泛型类,父类要明确泛型的数据类型。

      • class ChildGeneric extends Generic<String>


package com.heima.demo;

public class Parent<E>{
   private E value;
   
   public E getValue(){
       return value;
  }
   
   public void setValue(E value){
       this.value = value;
  }
}

package com.heima.demo;

public class ChildOne<T> extends Parent{
   @Override
   public Object getValue(){
       return super.getValue(); //不给继承的父类指定类型时,它默认继承Object。
  }
}

package com.heima.demo;
//创建子类对象之前会先去调用父类构造函数
public class ChildOne<T> extends Parent<T>{
   @Override
   public T getValue(){
       return super.getValue(); //泛型子类去继承泛型父类时泛型标识一定要相同,若子类有多个泛型形参,父类必与其中一个相同即可。
       //public class ChildOne<T, E, K> extends Parent<T>
  }
}

package com.heima.demo;
//泛型类派生子类,如果子类不是泛型类,那么父类要明确数据类型。
public class ChildTwo extends Parent<Integer>{
   @Override
   public Integer getValue(){
       return super.getValue();
  }
   @Override
   public void setValue(Integer value){
       super.setValue(value);
  }
}

package com.heima.demo;

public class Test{
   public static void main(String[] args){
       ChildOne<String> childOne = new ChildOne<>();
       childOne.setValue("abc");
       String value = childOne.getValue();
       System.out.println(value);
       
       System.out.println("***********");
       
       ChildTwo childTwo = new ChildTwo();
       childTwo.setValue(100);
       Integer value1 = childTwo.getValue();
       System.out.println(value1);
  }
}

泛型接口

  • 泛型接口的定义语法

interface 接口名称 <泛型标识, 泛型标识, ···>{
   泛型标识 方法名();
   ······
}
  • 泛型接口的使用

    • 实现类不是泛型类的时候,接口要明确数据类型。

    • 实现类也是泛型类的时候,实现类和接口的泛型类型要一致。


package com.heima.demo;
//泛型接口
public interface Generator<T>{
   T getKey();
}

package com.heima.demo;

public class Apple implements Generator{
   @Override
   public Object getKey(){ //隐式明确接口类型为Object
       return null;
  }
}

/*实现泛型接口的类不是泛型类时的情况如下,需要明确实现泛型接口的数据类型。
*public class Apple implements Generator<String>{
*   @Override
*   public String getKey(){
*       return "hello generic";
*   }
*}
*/

package com.heima.demo;
//泛型接口
public class Test{
   public static void main(String[] args){
       Apple apple = new Apple();
       String key = apple.getKey();
       System.out.println(key);
       
       System.out.println("****************");
       
       Pair<String, Integer> pair = new Pair<>("count", 100);
       String key1 = pair.getKey();
       Integer value1 = pair.getValue();
       System.out.println(key1 + "=" + value1);
  }
}

package com.heima.demo;
//泛型接口的实现类是一个泛型类,那么要保证实现接口的泛型类的泛型标识包含泛型接口的泛型标识。
public class Pair<T, E> implements Generator<T>{
   private T key;
   private E value;
   
   public Pair(T key, E value){
       this.key = key;
       this.value = value;
  }
   
   @Override
   public T getKey(){
       return key;
  }
   
   public E getValue(){
       return value;
  }
}

泛型方法

  • 泛型类,是在实例化类的时候指明泛型的具体类型。

  • 泛型方法,是在调用方法的时候指明泛型的具体类型。

  • 语法( <T> 叫泛型列表):

修饰符 <T, E, ···> 返回值类型 方法名(形参列表){
   方法体···
}
  • public 与返回值中间的 <T> 非常重要,可以理解为声明此方法为泛型方法。

  • 只有声明了 <T> 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

  • <T> 表明该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T。

  • 与泛型类的定义一样,此处 T 可以随便写为任意标识,常见的如 T、E、K、V 等形式的参数常用于表示泛型。


package com.heima.demo;
//泛型方法的定义,其中list是参数,<E>是泛型标识,其具体类型在调用方法的时候指定,注意本例包含在上方Bonus类中。
public class Bonus<T>{
   public <E> E getBonus(ArrayList<E> list){
       return list.get(random.nexInt(list.size())); //对随机索引进行限制,最大不要超过数组长度。
  }
}

package com.heima.demo;
//泛型方法的使用
public class Test{
   public static void main(String[] args){
       Bonus<Integer> bonus = new Bonus<>();
       //区分一下前面定义的成员方法与后面的泛型方法
       int[] products = {100, 200, 500};
       for(int i=0; i<products.length; i++){
           bonus.addBonus(products[i]); //调用类的成员方法,只不过它的参数类型采用了类的泛型类型。
      }
       //泛型类的成员方法的调用,注意成员方法返回值类型遵从类的返回值类型,所以此处bonus.getBonus().var出来的类型就是Integer。
       Integer product = bonus.getBonus();
       System.out.println(product + "\t" + product.getClass().getSimpleName());
       
       System.out.println("*****************");
       
       ArrayList<String> strList = new ArrayList<>();
       strList.add("mac");
       strList.add("iphone 13 pro max");
       strList.add("HUAWEI MATE50");
       //对泛型方法的调用,它的类型是在调用方法的时候指定的。
       String product1 = bonus.getBonus(strList);
       System.out.println(product1 + "\t" + product1.getClass().getSimpleName());
       
       System.out.println("*****************");
       
       ArrayList<Integer> intList = new ArrayList<>();
       intList.add(1000);
       intList.add(10000);
       intList.add(100000);
       Integer product2 = bonus.getBonus(intList);
       System.out.println(product2 + "\t" + product2.getClass().getSimpleName());
  }
}
  • 此时我们发现泛型方法的泛型列表类型是独立于泛型类的类泛型类列表类型的

  • 我们发现强制要求成员方法的返回值泛型必须遵从类的定义泛型,但是类的泛型方法却是在调用的时候去独立指定的。

注意事项

  • 泛型类的普通的成员方法如果采用了类的泛型作为返回值类型或者参数类型后,这个方法将不能再被声明为静态方法,无法使用static修饰符。

  • 但是泛型方法可以被声明为static静态方法


package com.heima.demo;
//接上述Bonus类,定义静态的泛型方法,泛型成员列表使用多个类型成员。
public class Bonus<T>{
   public static <T, E, K> void printType(T t, E e, K k){
       System.out.println(t + "\t" + t.getSimpleName());
       System.out.println(e + "\t" + e.getSimpleName());
       System.out.println(k + "\t" + k.getSimpleName());
  }
}

package com.heima.demo;
//接上述测试
public class Test{
   public static void main(String[] args){
       System.out.println("*****************");
       //调用多个泛型类型的静态泛型方法
       bonus.printType(100, "java", true);
       bonus.printType(false, false, true);
  }
}

泛型的方法与可变参数

  • 语法(在泛型标识后面加上三个点

public <E> void print(E... e){
   for (E e1 : e){
       System.out.println(e);
  }
}

package com.heima.demo;
//接上述Bonus类,进行泛型的可变参数的定义。
public class Bonus<T>{
   public static <E> void print(E... e){
       System.out.println(e[i]);
  }
}

package com.heima.demo;
//接上述测试
public class Test{
   public static void main(String[] args){
       System.out.println("*****************");
       //泛型可变参数方法的调用
       bonus.print(1, 2, 3, 4, 5);
       bonus.print('a', 'b', 'c');
  }
}
  • 泛型方法总结

    • 泛型方法能使方法对立于类而产生变化

    • 如果 static 方法要使用泛型功能,就必须先称为泛型方法。

    • 生活中能尽量用泛型方法的问题就不要去用泛型类,毕竟泛型方法更为灵活。

posted on 2021-11-30 12:21  愿将过往均储藏  阅读(92)  评论(0)    收藏  举报

导航