Java泛型知识点总结

参考:
<-----------------------------廖雪峰学java------------------------------>

  • 泛型就是定义一种模板,例如ArrayList<T>,然后在代码中为用到的类创建对应的ArrayList<类型>;实现:编写一次,万能匹配,又通过编译器保证了类型安全
  • ArrayList<Integer>和ArrayList<Number>两者完全没有继承关系;总之<>中的模板数据类型必须要保持一致
  • 如果不定义泛型类型时,泛型类型实际上就是Object
  • 可以在接口中使用泛型;例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口
  • 泛型类型不能用于静态方法,需要在static关键字后加<>;
  • 对于静态方法,我们可以单独改写为“泛型”方法,需要使用另一个类型标识;因为静态方法是归属于类的,在我们还没有创建实例时就可以直接调用,因此当我们使用静态方法时需要直接指定使用的数据类型。
public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }

    // 静态泛型方法应该使用其他类型区分:
    public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
    }
}
  • 泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>
  • Java语言的泛型实现方式是擦拭法(Type Erasure),即虚拟机对泛型其实一无所知,所有的工作都是编译器做的:
  1. 编译器把类型<T>视为Object
  2. 编译器根据<T>实现安全的强制转型
  • 因擦拭法导致的局限:

局限一:不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型
局限二:无法取得带泛型的Class,即泛型类的Class是不带泛型的
局限三:无法判断带泛型的类型:p instanceOf P<String> // Compile error

  • 实例化T类型,我们必须借助额外的Class参数:总之需要让编译器知道具体的类型信息
public class Pair<T> {
    private T first;
    private T last;
    public Pair(Class<T> clazz) {
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}

Pair<String> pair = new Pair<>(String.class);
  • 编译器会阻止一个实际上会变成覆写的泛型方法定义
public class Pair<T> {
    public boolean equals(T t) { // 编译失败,因T t会被擦拭为Object t,因此实际上是赋写Object类的equals方法
        return this == t;
    }
}

public class Pair<T> {
    public boolean same(T t) { // 编译成功
        return this == t;
    }
}
  • 父类是泛型类型(泛型T)的情况下,编译器就必须把类型T保存到子类的class文件中,此时子类可以获取父类的泛型类型
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Main {
    public static void main(String[] args) {
        Class<IntPair> clazz = IntPair.class;
        Type t = clazz.getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) t;
            Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型
            Type firstType = types[0]; // 取第一个泛型类型
            Class<?> typeClass = (Class<?>) firstType;
            System.out.println(typeClass); // Integer
        }

    }
}

class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

class IntPair extends Pair<Integer> {
    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}
  • 在这里插入图片描述
  • 使用static void Pair <? extends Number> 使得方法接收所有泛型类型为Number或Number子类的Pair类型
  • <? extends Number>称为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number
  • <? extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给(包括Number类型)setFirst(? extends Number);唯一的例外是可以给方法参数传入null
  • 方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)、remove(? extends Integer)这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法(恶意调用set(null)除外):
    注意到List<? extends Integer>的限制:
  1. 允许调用get()方法获取Integer的引用;
  2. 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。
int sumOfList(List<? extends Integer> list) {
    int sum = 0;
    for (int i=0; i<list.size(); i++) {
        Integer n = list.get(i);
        sum = sum + n;
    }
    return sum;
}
  • 定义泛型类型Pair的时候,也可以使用extends通配符来限定T的类型:public class Pair { … }
  • Pair <? super Integer> 表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型
  • 使用<? super Integer>通配符表示:
  1. 允许调用set(? super Integer)方法传入Integer的引用;
  2. 不允许调用get()方法获得Integer的引用。
    唯一例外是可以获取Object的引用:Object o = p.getFirst()

表示方法内部代码对于参数只能写,不能读

  • 何时使用extends,何时使用super?PECS原则:Producer Extends Consumer Super ;即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符
  • Java的泛型允许使用无限定通配符(Unbounded Wildcard Type),即只定义一个?
  • 因为<?>通配符既没有extends,也没有super,因此:既不能读,也不能写,那只能做一些null判断
  1. 不允许调用set(T)方法并传入引用(null除外);
  2. 不允许调用T get()方法并获取T引用(只能获取Object引用)。
  • <?>通配符有一个独特的特点:Pair<?>是所有Pair的超类
public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        Pair<?> p2 = p; // 安全地向上转型
        System.out.println(p2.getFirst() + ", " + p2.getLast());
    }
  • Java的部分反射API也是泛型。例如:Class就是泛型
  • 调用Class的getSuperclass()方法返回的Class类型是Class<? super T>
  • 构造方法Constructor也是泛型
  • 可以声明带泛型的数组,但不能用new操作符创建带泛型的数组,必须通过强制转型实现带泛型的数组:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
  • 使用泛型数组要特别小心,因为数组实际上在运行期没有泛型,编译器可以强制检查变量ps,因为它的类型是泛型数组;因此当一个泛型数组与一个普通数组指向同一个对象时,可能导致泛型数组在获取元素时报错
  • 带泛型的数组实际上是编译器的类型擦除
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;

System.out.println(ps.getClass() == Pair[].class); // true

String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();
  • 不能直接创建泛型数组T[],因为擦拭后代码变为Object[],必须借助Class来创建泛型数组:
T[] createArray(Class<T> cls) {
    return (T[]) Array.newInstance(cls, 5);
}
  • 还可以利用可变参数创建泛型数组T[]:
public class ArrayHelper {
    @SafeVarargs
    static <T> T[] asArray(T... objs) {
        return objs;
    }
}

String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
  • 编译器对所有可变泛型参数都会发出警告,除非确认完全没有问题,才可以用@SafeVarargs消除警告
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值