Java泛型

299 阅读15分钟

泛型类型的创建

简单创建与使用

简单的,如果我们想使用泛型,可以像下面这样写

public class Wrapper<T> {
    private T instance;

    public T get() {
        return instance;
    }

    public void set(T instance) {
        this.instance = instance;
    }
}

使用

Wrapper<String> stringWrapper = new Wrapper<>();
stringWrapper.set("zzz");
System.out.println(stringWrapper.get());//zzz

我们也可以基于数组做一个类似List的泛型容器

public class DshList<T> {
    private Object[] instances = new Object[0];

    public T get(int index) {
        return (T) instances[index];
    }

    public void set(int index,T instance) {
        this.instances[index] = instance;
    }

    public void add(T newInstance){
        instances = Arrays.copyOf(instances,instances.length+1);
        instances[instances.length-1] = newInstance;
    }

    @Override public String toString() {
        return Arrays.toString(instances);
    }

    public static void main(String[] args) {
        DshList<String> dshList = new DshList<>();
        dshList.add("aaa");
        dshList.add("bbb");
        dshList.add("cccc");
        System.out.println(dshList);//[aaa, bbb, cccc]
        System.out.println(dshList.get(2));//ccc
    }
}

泛型的作用

  1. 帮助检查代码中的类型,提前报错;
  2. 自动强制转型。

「创建一个泛型类型」到底是为了什么?

  • 本质目标或原因:这个类型的不同实例的具体类型可能会有不同,针对的是实例
  • 因此,静态字段和静态方法不能使用泛型类型的类型参数(也就是那个 T )

泛型接口

泛型接口限制了其本身或者子类内部只能使用接口规定的某一个类型

下面设计一个商店接口,商店只能卖某种类型的商品

interface Shop<T> {
    T buy();
    float refund(T item);
}

下面我们创建一个苹果商店

  • implements Shop<Apple>限制了我们只能购买苹果Apple
  • 如果我们接口泛型传入的是Pear,那么编译器会提示我们不能这样做
//Shop<Apple>限制接口范围
class AppleShop implements Shop<Apple> {

    @Override public Apple buy() {
        return null;
    }

    @Override public float refund(Apple item) {
        return 0;
    }

    public static void main(String[] args) {
        //编译错误
        Shop<Pear> appleShop = new AppleShop();
        //编译通过
        Shop<Apple> appleShop2 = new AppleShop();
    }
}

泛型接口的继承

接口的继承为我们提供了对通用接口的拓展

如下:
在上面的基础上,我们新建一个水果榨汁商店,它继承于Shop接口,在购买和退换基础上,JuicingShop增加了榨汁功能

interface JuicingShop<T> extends Shop<T> {
    void juice(T item);
}

然后创建实现类,梨汁商店,归功于接口继承,可以购买退换也可以榨汁,同样的,拓展其他的水果商店也非常轻松

class PearJuicingShop implements JuicingShop<Pear> {

    @Override
    public void juice(Pear item) {
        System.out.println("梨汁榨好了");
    }

    @Override public Pear buy() {
        return new Pear(2f);
    }

    @Override public float refund(Pear item) {
        return item.getPrice();
    }

    public static void main(String[] args) {
        PearJuicingShop reapairShop = new PearJuicingShop();
        reapairShop.juice(new Pear(2f));
    }

}

多参数泛型

HashMap是多参数的泛型,就像下面这样,K、V分别对应key和value

class DshMap<K,V> {

    public void put(K key,V Value){

    }
    public V get(K key){
        return (V) "value";
    }
}

下面我们模拟一个手机卡售卖商店,商店提供了个大运营商的手机卡,他们的共性是都是SIM卡

  • 商店卖sim卡
//不管是实现还是继承都用extends,并且可以extends多个,
// 如果是多个并且有类,那么类必须写在第一个位置,并且只能继承一个类(java单继承特性)
interface SimShop<T,C extends SimCard & Cloneable > extends Shop<T> {
    C getSim(String name,String id);
}
  • 移动的卡继承于SIM卡
public class ChinaMobile implements SimCard, Cloneable {

    private String name;
    private String id;

    public ChinaMobile(String name, String id) {
        this.name = name;
        this.id = id;
    }

    @Override public String toString() {
        return "中国电信电话卡{" +
                "姓名:'" + name + '\'' +
                ", 身份证号:'" + id + '\'' +
                ", 余额:'" + (int)(Math.random()*1000_000) + '\'' +
                '}';
    }
}
  • 移动卡商店只卖移动的卡,由于我们泛型的第二个值C约束了参数类型必须是SimCard类型,所以getSim最后返回的也必须是SimCard类型的
class MobileSimShop<T,C extends SimCard & Cloneable> implements SimShop<T,C> {
    @Override
    public C getSim(String name, String id) {
        return (C) new ChinaMobile(name,id);
    }

    @Override public T buy() {
        return null;
    }

    @Override public float refund(T item) {
        return 0;
    }

    public static void main(String[] args) {
        MobileSimShop<String, ChinaMobile> mobileSimShop = new MobileSimShop<>();
        ChinaMobile simCard = mobileSimShop.getSim("dsh", "330101xxxxxx");
        System.out.println(simCard);//中国电信电话卡{姓名:'dsh', 身份证号:'330101xxxxxx', 余额:'988935'}
    }
}

注意事项

  • 不管是实现还是继承都用extends,并且可以extends多个,
  • 如果是多个并且有类,那么类必须写在第一个位置,并且只能继承一个类(java单继承多实现的特性)
    interface SimShop<T,C extends SimCard & Cloneable > extends Shop<T>

泛型类型实例化的上界与下界

向上转型与向下转型

    //通过子类对象(小范围)实例化父类对象(大范围),这种属于自动转换,Apple是Fruit的子类
    Fruit fruit = new Apple(2.0f);
    //通过父类对象(大范围)实例化子类对象(小范围),这种属于强制转换
    Apple apple = (Apple) fruit;

泛型的上界

1.在泛型中,父类的泛型声明不能用子类的泛型声明去实例

    //由于实例了一个只能装Apple的List,声明的List要求能装各种水果,所以这里会编译错误
    ArrayList<Fruit> fruits = new ArrayList<Apple>();
    fruits.add(new Pear(2f));//添加了错误的对象
    Apple apple1 = (Apple) fruits.get(0);//GG 向下转型失败
  1. 通配符 ? 解开限制, ? 规定了泛型的上界
    • 可以正常声明,但是添加的时候由于? extends Fruit不能确定具体类型,所以编译器依然会报错
    //加上通配符 ? 解开限制,? 规定了泛型的上界,在这里是Fruit
    //声明List的时候不报错
    ArrayList<? extends Fruit> fruits2 = new ArrayList<Apple>();
    //但是添加对象的时候依然报错
    // ? extends Fruit , 编译器并不能确定添加的到底是什么,所以即便添加Apple也会编译错误
    fruits2.add(new Apple(2.0f));
  1. 数组没有泛型擦除
    • 编译的时候,不会报错
    • 但是如果添加了错误的类型,运行时是会报错的
    //数组泛型,数组没有泛型擦除
    Fruit[] fruits1 = new Apple[10];
    fruits1[0] = new Pear(3f);//GG,运行时错误  ArrayStoreException
    System.out.println("Fruit 数组添加元素完成");
  1. ArrayList 泛型擦除
    • 如果强制转换,在运行时泛型信息会被擦除
    • 如果通过强制转换获取错误类型的对象,运行时会报错
    //强制转换 -> 运行时等价于 ArrayList fruits2 = (ArrayList) new ArrayList();
    //泛型信息被擦除
    ArrayList<Fruit> fruits3 = (ArrayList) new ArrayList<Apple>();
    fruits3.add(new Pear(2f));//没有报错,正常运行
    Apple apple2 = (Apple) fruits3.get(0);//GG 运行时异常 转型错误ClassCastException
    System.out.println("Fruit List 添加元素完成");
  1. ? 通配符的场景化使用
    • 如果某些方法的参数是我们的泛型List,我们不可能对每一种泛型都编写重载方法,这时候使用 ?通配符可以提高方法的通用性
    main{
        //? 通配符的场景化使用
        getTotalPrice(fruits3);
        ArrayList<Apple> apples = new ArrayList<Apple>();
        getTotalPrice(apples);//泛型限制,编译错误
        getTotalPriceImprove(apples);//通配符,可以编译
    }
    
    //不接收子类
    static float getTotalPrice(List<Fruit> fruits){
        float total = 0;
        for (Fruit fruit:fruits) {
            total += fruit.getPrice();
        }
        return total;
    }

    //接收子类
    static float getTotalPriceImprove(List<? extends Fruit> fruits){
        return getTotalPrice((List<Fruit>) fruits);
    }
    

泛型的下界

  1. 在泛型中,用 ?通配符+super关键字声明,就可以用父类的泛型实例去声明子类
    • 问题是,? super Apple 仍不能确定list的具体类型,所以get的时候依然会编译错误
    • 使用强制转换获取对象
    //6
    //在泛型中,子类的泛型声明也不能用父类的泛型声明去实例
    List<Apple> apples1 = new ArrayList<Fruit>();//编译错误
    //用 ?通配符+super关键字声明
    List<? super Apple> apples2 = new ArrayList<Fruit>();
    apples2.add(new Apple(5f));
    Apple apple3 = apples2.get(0);//编译错误,apples2不能确定具体类型
    Apple apple4 = (Apple)apples2.get(0);//使用强制转换获取对象
  1. 泛型下界接收父类
    • 这样做的限制是,当接收方法想要获取确定类型的对象时,需要强制转换
    • 否则只能用Object接收
    main{
        //7
        //泛型下界接收父类
        Apple apple5 = new Apple(4f);
        List<Fruit> list = new ArrayList<>();
        list.add(apple5);
        addToList(list);//不接收子类,编译错误
        addToListImprove(list);//addToListImprove 允许接收父类
    }
    
    //不接收父类
    private static void addToList(List<Apple> list) {

    }

    //接收父类
    private static void addToListImprove(List<? super Apple> list) {
        //只能用Object接收,有损失
        Apple apple = list.get(0);
        //如果传入确定的Apple类型,可以使用强转获取对象
        Apple apple2 = (Apple)list.get(0);
        //用Object接收不会编译错误
        Object object = list.get(0);
    }
    

泛型方法和类型推断

我们继续扩展Shop接口,其中tradeIn和take就是泛型方法

interface Shop<T> {
    T buy();
    float refund(T item);
    //加钱换新
    <E> E tradeIn(E item,float money);
    //取货 <R> 定义一个返回类型 R 返回类型
    <R> R take();
}

泛型方法及实例

  • 泛型类型的实例化是在声明的时候确定的
  • 泛型方法也有实例化,因为泛型方法也可以把类型参数的类型进行确定。 具体呢?每一次泛型方法的调用就是一次对这个泛型方法的实例化。
    //1. 泛型类型的实例化
    //Shop<Phone>尖括号内的Phone就是泛型类型的实例化
    Shop<Phone> phoneShop = null;
    AndroidPhone androidPhone = phoneShop.tradeIn(new AndroidPhone(), 100f);
    //完整写法,因为有类型推断,所以尖括号<>可以省略
    AndroidPhone androidPhone2 = phoneShop.<AndroidPhone>tradeIn(new AndroidPhone(), 100f);

    //2. 泛型方法的实例化,
    WinPhone winPhone = phoneShop.<WinPhone>tradeIn(new WinPhone(), 200f);

类型推断

  • 如果参数类型与返回类型相同,可以推断
  • 如果是无参方法,通过等号左边的值推断出返回值
    //3. 类型推断,即<WinPhone>可以省略
    WinPhone winPhone = phoneShop.tradeIn(new WinPhone(), 200f);
    //完整写法
    WinPhone winPhone = phoneShop.<WinPhone>tradeIn(new WinPhone(), 200f);

    //4.如果是无参方法,通过等号左边的值推断出返回值
    ApplePhone applePhone = phoneShop.take();
    ApplePhone applePhone2 = phoneShop.<ApplePhone>take();//完整写法
    ApplePhone applePhone3 = phoneShop.<WinPhone>take();//类型错误,编译错误

静态泛型方法

泛型方法调用与对象本身无关,泛型方法不局限于非静态方法,静态方法也可以写成泛型方法

//泛型方法调用与对象本身无关,泛型方法不局限于非静态方法,静态方法也可以写成泛型方法
    static <C> void filter (List<C> list){
        for (C c:list) {
            //do something
        }
    }

安卓findViewById的泛型方法

通过类型推断,findViewById的返回声明可以使用View及其各种派生类

    //使用:安卓开发中的泛型方法 <TextView>泛型可以省略
    TextView textView = <TextView>findViewById(R.id.et_name);
    //findViewById源码 -> 泛型方法
    public <T extends View > T findViewById(@IdRes int id) {
        return this.getDelegate().findViewById(id);
    }

泛型的本质,什么时候使用泛型

泛型的意义

泛型的意义在于:泛型的创建者让泛型的使用者可以在使用时(实例化时) 细化类型信息,从而可以触及到「使用者所细化的子类」的 API。 或者,泛型是「有远⻅的创造者」创造的「方便使用者」的工具。

  • 所以泛型参数可以是一个方法的返回值类型
 T buy();
  • 也可以是放在一个接口的参数里,等着实现类去写出不同的实现
public interface Comparable<T> { 
    public int compareTo(T o);
}
...
public String implements Comparable<String> { 
    public int compareTo(String anotherString);
}

泛型的拓展性

泛型是「有远见的创造者」,拓展性很强

  • 使用泛型,不需要转型
  • 泛型可以检查类型

①由于Shop使用了泛型,我们可以通过一个接口声明创建出多个不同类型的Shop
②如果使用非泛型商店,则我们需要强制转型,并且传入错误的参数也不会被检查处理
③ 固定类型的商店同样可以达到①的效果,但是拓展性几乎没有,如果想要创建其他类型的商店,不得不增加新的接口,而泛型接口完全不需要

main{
    //1. 泛型商店
    Shop<Apple> appleShop = null;
    //使用泛型,不需要转型
    Apple apple = appleShop.buy();
    appleShop.refund(apple);
    //编译错误,泛型可以检查类型
    appleShop.refund(new Pear(2f));


    //2. 非泛型商店
    NonGenericShop nonGenericShop = null;
    //不使用泛型,需要转型
    Apple apple2 = (Apple) nonGenericShop.buy();
    nonGenericShop.refund(apple2);
    //编译通过,不会检查类型
    nonGenericShop.refund(new Pear(3f));

    //3. 固定类型的商店
    //也可以达到类似泛型的效果
    AppleShop appleShop1 = null;
    Apple apple1 = appleShop1.buy();
    appleShop1.refund(apple1);
    appleShop1.refund(new Pear(4f));


    //4.假设我们想要创建一个香蕉Banana商店
    //4.1 使用泛型,可以直接使用Shop接口
    //4.2 不使用泛型,则需要新创建一个Banana的接口
}
    //非泛型接口
    interface NonGenericShop {
        Object buy();
        float refund(Object object);
    }
    //固定类型接口
    interface AppleShop{
        Apple buy();
        float refund(Apple object);
    }

延伸用途: 类型约束

下面我们改造接口,约定接口参数类型必须同时继承Apple并实现Serializable接口,通过这样的方式,我们就可以要求类型信息必须是多重的

  • 首先不仅可以约束接口使用的类型
  • 也能够通过定义一个方法参数,约束两个参数是同一类型
  • 类似这样的例子有很多
    //多重限制
    //5.1 多重类型约束
    interface SerializableAppleShop<T extends Apple & Serializable>{
        //5.2. 约束两个参数为同一类型
        <P> void exchange (P p1,P p2);
    }
main{
    //5. 多重类型约束 
    //编译错误,类型约束
    SerializableAppleShop<Apple> serializableAppleShop= null;
    //编译通过
    SerializableAppleShop<SerializableApple> serializableAppleShop1 = null;
}
class SerializableApple extends Apple implements Serializable {...}

Comparable接口泛型约束

public interface Comparable<T> {
    public int compareTo(T o);
}

java中有很多类实现了Comparable接口,比如Integer 所以其实Integer实现Comparable泛型接口的原因就是约束其参数类型必须为Integer

public final class Integer extends Number implements Comparable<Integer>{
        public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
}

现在我们让Shop实现Comparable接口

public interface Shop<T> extends Comparable<Shop<T>>{...}
class ComparableShop implements Shop<Apple> {
    ...
    
    /**
     *
     * @param old 被比较的旧对象
     * @return 当前对象大于被比较的旧对象 返回正数
     *          等于                   返回0
     *          小于                   返回负数
     */
    @Override
    public int compareTo(Shop<Apple> old) {
        return this.refund(new Apple(2f))>old.refund(new Apple(3f))?1:-1;
    }
}

泛型中的T、<>、?、extends、super

Type Parameter 和 Type Argument

泛型的创建和泛型的实例化

Type Parameter:

  • public class Shop 里面的那个 ;
    • 表示「我要创建一个 Shop 类,它的内部会用到一个统一的类型,这 个类型姑且称他为 T 」。

Type Argument:

  • 其它地方尖括号里的全是 Type Argument,比如 Shop appleShop; 的 Apple ;
  • 表示「那个统一代号,在这里的类型我决定是这个」。

T 情景归纳

  • 写在类名(接口名)右边的括号里,表示 Type Parameter 的声明,「我要创建 一个代号」;
  • 写在类或接口里的其他地方,表示「这个类型就是我那个代号的类型」;
    • 很容易搞错的场景:当继承或实现一个泛型类型的时候,如果父类(或父接 口)名的右边也写了 :

       interface RepairableShop<T> extends Shop<T> { }
      

      这个右边父类(接口)右边的 表示:

      • 我要对父类(接口)进行实例化,即确定它类型参数的实际值
      • 实例化的具体类型是我的这个类型参数

      所以它从父类和自己这两个不同⻆度来看,同时具有「已确定的实例化」和 「未确定的声明」这两个属性。

    • 使用自己的类型参数(Type Parameter)来作为

  • 只在这个类或者接口里有用,出了类或接口就没用了(对于泛型方法是,只在这 个方法里有用,出了这个方法就没用了)。
    • 所以不能这么写:
     ArrayList<T> list;
    
    • 也不能这么写:
    new ArrayList<T>();   
    

? 号情景归纳

  • 只能写在泛型实例化的地方:表示「这个类型是什么都行,只要不超出 ? extends 或者 ? super 的限制」。
    Shop<? super Fruit> fruitShop;
    
  • 虽然用于实例化,但因为它表示「类型还有待进一步确定」,它不能用在类型参 数最终确定的时候:
    new Shop<? extends Fruit>(); // 不能这么写
    
  • 「类型还有待进一步确定」的特殊场景:嵌套泛型实例化:
 List<? extends Shop<? extends Fruit>> shops = new ArrayList<Shop<? extends Fruit>>();

包在多层尖括号里面的实例化,因为全都属于「类型有待进一步确定」,所以全都可以加问号。

extends 情景归纳

  • 用在泛型类型或泛型方法声明的时候,对类型参数设置上界:
  • 用在泛型实例化时 ? 号可以出现的地方,用来设置上界,缩小 ? 号的限制范 围。
    interface Shop<T extends Fruit & Runnable> { }
    

super 情景归纳

  • 只有一个地方:用在泛型实例化时 ? 号可以出现的地方,用来设置下界,缩小 ? 号的限制范围。

泛型中的「重复」和「嵌套」

  • 的重复: 表示对父类(父接口)的扩展。
     public interface RefundableShop<T> extends Shop<T> { 
        float refund(T item);
     }
    
  • 类名的重复: 同样表示对父类(父接口)的扩展(囧)。
     public class String implements Comparable<String> { 
        public native int compareTo(String anotherString);
     }
    
  • 正常的实例化嵌套
    List<Shop<Apple>>   
    
  • 类型参数的上界是一个泛型类型,相对复杂的嵌套
     interface DshList<T extends List<Shop<Apple>>> { }
    
  • 极端案例:Enum
    public abstract class Enum<E extends Enum<E>> implements Comparable<E>{}
    
    • extends Enum 表示一个上界,即「 E 需要是 Enum 的子类」
    • 这是个 Comparable 的实现,所以 compareTo(E o) 的参数就需要是 个 Enum 的子类
      • 进一步:这个类也是一个 Enum ,所以表示「你必须和跟自己一样的 类作比较」
    • Enum 这个类还有一个 getDecalaringClass() 方法:
      public final Class<E> getDeclaringClass() {
          ...
      }
      
      所以这个方法的返回值也需要是一个 Enum 的子类。

泛型中的类型擦除和「不可以」以及突破「不可以」

  • 运行时,所有的 T 以及尖括号里的东⻄都会被擦除;
    interface Shop<T>{
        
    }
    
    Shop<Apple> shop = ...;
    
        运行时,等价于
            ↓
            
    interface Shop<Apple>{
        
    }
    
    
  • List 和 List 以及 List 都是一个类型;
    List<String> list = new ArrayList<String>();
        运行时,泛型被擦除
              ↓
    List list = new ArrayList();          
    
  • 但是所有代码中声明的变量或参数或类或接口,在运行时可以通过反射获取到泛 型信息;
  • 但但是,运行时创建的对象,在运行时通过反射也获取不到泛型信息(因为 class 文件里面没有);
  • 但但但是,有个绕弯的方法就是创建一个子类(哪怕用匿名类也行),用这个子 类来生成对象,这样由于子类在 class 文件里就有,所以可以通过反射拿到运行 时创建的对象的泛型信息。
    比如 Gson 的 TypeToken 就是这么干的。

协变和逆变

  • 泛型不支持协变和逆变,有类型擦除
    • 通过「 ?extends 」使其支持 covariant 协变
    • 通过「 ?super 」使其支持 contravariant 逆变/反变
  • 数组支持协变,但是没有类型擦除
    //泛型不支持协变和逆变,有类型擦除
    //编译错误
    List<Object> objects = new ArrayList<String>();
    //编译通过 通过「 ?extends 」使其支持 covariant 协变
    //【左边List是右边ArrayList的父类,左边泛型Object是右边泛型String的父类,】
    List<? extends Object> objects2 = new ArrayList<String>();

    //编译错误,边界错误
    List<String> objects4 = new ArrayList<Object>();
    //编译通过 通过「 ?super 」使其支持 contravariant 逆变/反变
    // 【左边String是右边Object的子类,右边ArrayList是左边List的子类】
    List<? super String> objects3 = new ArrayList<Object>();

    //数组支持协变,但是没有类型擦除
    //编译错误, 由于数组没有泛型擦除,所以数组声明不能使用泛型
    Shop<Apple>[] appleShop = new Shop<Apple>[10];
    //编译通过
    Shop[] appleShop2 = new Shop[10];

Kotlin中的泛型

场景跟 Java 一样,不过用法有一点不一样

  • Java 的 <? extends> 在 Kotlin 里写作 ;Java 的 <? super> 在 Kotlin 里写作 ;
  • 另外,Kotlin 还增加了 out T in T 的修饰,来在类或接口的声明处就限制 使用,这样你在使用时就不必再每次都写;
  • Kotlin 的 * 号相当于 Java 的 ? 号,基本一样,只是有些细节不一样。
class KotlinTest {
  fun main(){
    //使用
    val ktShop:KotlinShop<Apple> = KotlinShop<Apple>();
    //out 等价于 java中 ? extends
    val ktShop2:KotlinShop<out Fruit> = KotlinShop<Apple>()
    //in 等价于 java中 ? super
    val ktShop3:KotlinShop<in Apple> = KotlinShop<Fruit>()
    //* 等价于 java中 ?
    val ktShop4:KotlinShop<*> = KotlinShop<Fruit>()


    //返回值为T , 参数没有T, 可以省略out
    val ktShop5:KotlinShop2<Fruit> = KotlinShop2<Apple>()// use-site variant 使用处
    //返回值没有T , 参数有T, 可以省略in
    val ktShop6:KotlinShop3<Apple> = KotlinShop3<Fruit>()// use-site variant 使用处

  }
}

泛型类

class KotlinShop<T>{
  fun buy():T{
    return null as T;
  }
  fun refund(item:T):Float{
    return 1f;
  }
}

有返回值为T的方法,没有参数为T的方法

class KotlinShop2<out T>{//declare-site variance 声明处
  fun buy():T{
    return null as T;
  }
}

有参数为T的方法,没有返回值为T的方法

class KotlinShop3<in T>{//declare-site variance 声明处
  fun refund(item:T):Float{
    return 1f;
  }
}

泛型接口

interface ktInterface<T> {
  fun buy():T;
}

class ktImpl:ktInterface<Apple>{
  override fun buy(): Apple {
    println("I buy an Apple");
    return Apple(1f);
  }

  fun main(){
    val ktApple: ktImpl = ktImpl();
    ktApple.buy();
  }

}

戳->学习代码在git