java-泛型

编写泛型

类的泛型

//定义
class Box<T> {
    private T element;

    public Box(T element) {
        this.element = element;
    }

    public T get() {
        return this.element;
    }
}

//使用
Box<String> b1 = new Box<>("");
String s = b1.get();

方法的泛型

// 类的泛型
class Box<T> {
    private T element;

    public Box(T element) {
        this.element = element;
    }

    public T get() {
        return this.element;
    }

    // 成员方法的泛型
    public <K, V> V m1() {
        return null;
    }

    // 静态方法的泛型
    public static <K> Box<K> valueOf(K element) {
        return new Box<>(element);
    }
}

Box<String> b1 = Box.valueOf(""); //不用指定泛型,能推导出
b1.<Integer, String>m1().toLowerCase(); //需要指定泛型,不能推导出

多个泛型类型

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

//使用
Pair<String, Integer> p = new Pair<>("test", 123);

擦拭法(泛型原理)

原理

Java实现泛型的方式是擦拭法:虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

Java使用擦拭法实现泛型,导致了:

  • 编译器把类型<T>视为Object
  • 编译器根据<T>实现安全的强制转型。

编译器看到的代码:

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() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();

编译器转换后,虚拟机看到的代码:

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

Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();

泛型的局限

局限一:<T>不能是基本类型,例如int,因为实际类型是ObjectObject类型无法持有基本类型:

Pair<int> p = new Pair<>(1, 2); // compile error!

局限二:无法取得带泛型的Class

Pair<String> p1 = new Pair<>("Hello", "world");
Pair<Integer> p2 = new Pair<>(123, 456);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(c1==c2); // true,c1和c2都是Pair.class
System.out.println(c1==Pair.class); // true

局限三:无法判断带泛型的类型:

Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
// 没有 Pair<String>.class,只有 Pair.class
if (p instanceof Pair<String>) {
}

局限四:不能实例化T类型:

public class Pair<T> {
    private T first;
    private T last;
    public Pair() {
        // Compile error:
        first = new T();
        last = new T();
    }
}

上述代码无法通过编译,因为构造方法的两行语句:

first = new T();
last = new T();

擦拭后实际上变成了:

first = new Object();
last = new Object();

这样一来,创建new Pair()和创建new Pair()就全部成了Object,显然编译器要阻止这种类型不对的代码。

要实例化T类型,我们必须借助额外的Class参数:

public class Pair<T> {
    private T first;
    private T last;
    public Pair(Class<T> clazz) {
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}

不恰当的覆写方法

有些时候,一个看似正确定义的方法会无法通过编译。例如:

public class Pair<T> {
    //'equals(T)' in 'Pair' clashes with 'equals(Object)' in 'java.lang.Object'; both methods have same erasure, yet neither overrides the other
    public boolean equals(T t) {
        return this == t;
    }
}

这是因为,定义的equals(T t)方法实际上会被擦拭成equals(Object t),而这个方法是继承自Object的,编译器会阻止一个实际上会变成覆写的泛型方法定义。

泛型继承

一个类可以继承自一个泛型类。

//父类,是泛型类
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);
    }
}

使用的时候,因为子类IntPair并没有泛型类型,所以,正常使用即可:

IntPair ip = new IntPair(1, 2);

我们无法获取PairT类型,即给定一个变量Pair p,无法从p中获取到Integer类型。

在继承了泛型类型的情况下,子类可以获取父类的泛型类型。例如:IntPair可以获取到父类的泛型类型Integer

Class<IntPair> clazz = IntPair.class;//获取子类Class
Type t = clazz.getGenericSuperclass();//获取父类Type:Pair<java.lang.Integer>
if (t instanceof ParameterizedType) {//判断t是参数化类型
    ParameterizedType pt = (ParameterizedType) t; //强制转换
    Type rawType = pt.getRawType();//泛型擦除后的Type:class Pair
    Type ownerType = pt.getOwnerType();//如果我是内部类,则返回外部类Type:null
    Type[] types = pt.getActualTypeArguments();//泛型类包含的实际类[class java.lang.Integer]
}

extends通配符

介绍

下面会报错:

List<Integer> integerList = new ArrayList<>();
//不兼容的类型: java.util.List<java.lang.Integer>无法转换为java.util.List<java.lang.Number>
List<Number> numberList = integerList;

这样就不会报错:

List<Integer> integerList = new ArrayList<>();
//泛型类型 是Number或其子类
List<? extends Number> list = integerList;

这种使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。

注意

List<? extends Integer> list = new ArrayList<>();
list.add(1);//error,因为内部类型不一定是Integer
list.add(null);//ok,只能传入null

因此,类型List表明了该变量只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)remove(? extends Integer)这些方法。换句话说,这里只会对List进行只读(恶意调用set(null)除外)。

super通配符

介绍

下面会报错:

List<Number> numberList = new ArrayList<>();
//不兼容的类型: java.util.List<java.lang.Number>无法转换为java.util.List<java.lang.Integer>
List<Integer> integerList = numberList;

这样就不会报错:

List<Number> numberList = new ArrayList<>();
//泛型类型 是Integer或其父类
List<? super Integer> list = numberList;

这种使用<? super Integer>的泛型定义称之为下界通配符(Lower Bounds Wildcards),即把泛型类型T的下界限定在Integer了。

注意

List<? super Number> list = new ArrayList<>();
list.add(1);//ok,因为内部类型一定是Number父类,可以传入Integer
Number n = list.get(0);//error,内部类型不一定是Number
Object o = list.get(0);//ok,只能用Object接收

换句话说,使用<? super Integer>通配符作为类型,表示变量只能写,不能读。

对比extends和super通配符

作为方法参数,List<? extends T>类型和List<? super T>类型的区别在于:

  • List<? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • List<? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

使用例子:Collections类定义的copy()方法:

public class Collections {
    // 把src的每个元素复制到dest中:
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i);
            dest.add(t);
        }
    }
}

无限定通配符

(1)无限定通配符(Unbounded Wildcard Type),即只定义一个?

void sample(List<?> p) {}

(2)因为<?>通配符既没有extends,也没有super,因此:

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。

(3)<?>通配符有一个独特的特点,就是:Pair<?>是所有Pair<T>的超类:

List<Integer> integerList = new ArrayList<>();
List<?> list = integerList;// 安全地向上转型
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值