泛型

什么是泛型

泛型就是编写模板代码来适应类型,好处就是不必强制转型,通过编译器对类型进行检查

List list = new ArrayList<>();

这就是泛型,集合限定了元素必须为String类型,否则报错

泛型接口

除了在集合中使用泛型,有许多接口也用到了泛型,比如 Comparable

可以直接对String类型的数组进行排序,因为String实现了Comparable接口

public final class String implements Serializable, Comparable, CharSequence
String[] s = {"B", "A", "C"};
Arrays.sort(s);
System.out.println(Arrays.toString(s));

如何实习自定义类的排序呢?实现Comparable接口

public class Person implements Comparable{
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    @Override
    public int compareTo(Person o) {
        return this.getAge()-o.getAge();
    }
}

测试:

Person[] people = {new Person("lucy", 21), new Person("mary", 19), new Person("Mali", 33)};
System.out.println("排序前");
for (Person person : people) {
    System.out.println(person.getName() + " : " + person.getAge());
}
Arrays.sort(people);
System.out.println("排序后");
for (Person person : people) {
    System.out.println(person.getName() + " : " + person.getAge());
}

通过控制台输出就可以发现Person数组已经按照年龄进行了升序排序

编写泛型

public class Pair {
    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;
    }
}

注意静态方法不能使用,应该与实例类型的泛型区分开:

public static  Pair create(K first, K last) {
    return new Pair(first, last);
}

擦拭法

虚拟机对泛型是一无所知的,视所有泛型为Object,在需要转型的时候编译器会根据T类型自动为我们安全的强制转型

这就造成了使用泛型时一些的局限

  1. 不能是基本类型,因为Object无法持有基本类型
  2. 在获取class时因为擦拭法导致取到的对象是同一个对象
  3. 也无法判断泛型的类型
  4. 不能实例化泛型

可以借助Class来实例化泛型对象

public Pair(Class clazz) {
    first = clazz.newInstance();
    last = clazz.newInstance();
}

泛型继承

public class IntPair extends Pair {
    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}

子类获取父类的泛型类型:

public static void main(String[] args) {
    Class clazz = IntPair.class;
    Type type = clazz.getGenericSuperclass();    // 获得带有泛型的父类
    if (type instanceof ParameterizedType){        // 判断是否为参数化类型,即泛型
        ParameterizedType p = (ParameterizedType) type;
        Type[] types = p.getActualTypeArguments();    // 父类可能有多个泛型
        Type firstType = types[0];    // 取第一个泛型
        System.out.println(firstType.getTypeName());
    }
}

extends

在平时的继承关系中IntegerNumber的子类,但是在泛型中Pair不是Pair的子类

比如我们定义了一个方法,限定了传入的参数为Pair类型,如果传入Pair就会报错

static int add(Pair p) {
    return p.getFirst().intValue() + p.getLast().intValue();
}
public static void main(String[] args) {
    Pair p = new Pair<>(3,5);
    Pair.add(p);
}

image-20220322163550707

那如何传入Integer呢?这就需要用到extends通配符来解决了,改造一下那个方法

static int add(Pair p) {
    return p.getFirst().intValue() + p.getLast().intValue();
}

这种通配符被称为上界通配符,把泛型类型T的上界限定在了Number

使用extends须知:

add()中是不能获取Integer的引用的,下面代码是无法通过编译的,要求你强制转型

Integer first = p.getFirst();

因为我们虽然限定了泛型的上界为Number,但是传入的具体类型到底是Integer还是其他Number的子类是不知道的,编译器只能确定一定是Number的子类

也无法传递Number的子类型给对象的set()

p.setFirst(new Integer(p.getFirst().intValue() + 100));

总结一下:

  1. 允许调用get()获取Number的引用
  2. 不允许调用set(? extends Number)传入任何Number的引用

super

正好和Number相反,传入的是Integer以及它的父类

Pair p

使用super须知:

public static void main(String[] args) {
    Pair p = new Pair<>(3,5);
    Pair.add(p,5);
}
static void add(Pair p,Integer n) {
    p.setFirst(n);
}

这段代码是可以被正常编译的,因为限定了下界为Integer,无论传入Integer还是它的父类都是可以的

但是无法使用Integer接收get()的返回值,因为无法确定具体返回的是Integer还是它的父类,唯一可以接收的是Object

static void add(Pair p,Integer n) {
    Object first = p.getFirst();
}

所以对比extendssuper可以发现:

可读不可写

可写不可读

补充

声明泛型数组时,不能用new操作符创建数组,需强制转型

Pair[] pairs = (Pair[]) new Pair[2];

你可能感兴趣的:(java)