Java基础——泛型.

泛型是什么?

泛型机制使得编写的代码可以被很多不同类型的对象所重用,要比使用Object变量再进行强制类型转换具有更好的安全性和可读性。

泛型实现

在未使用泛型前,ArrayList内部维护Object数组,导致其可以存储任意类型的变量,每次取出都要进行强制类型转换

ArrayList arrayList = new ArrayList();
arrayList.add("string");
arrayList.add(new File("/"));
String name = (String) arrayList.get(0);
File file = (File) arrayList.get(1);

泛型通过类型参数指定元素的类型,jdk7后可省略后面<>中的参数表示,调用get不需类型转换

ArrayList<String> stringArrayList = new ArrayList<String>();

泛型有子类型化的规则,如下List<String>是List的子类型,而不是List<Object>的子类型

List<String> s1 = new ArrayList<>();
List s2 = s1;
//List<Object> s3 = s1;

自定义泛型

一个泛型类就是具有一个或多个类型变量的类,常用E表示集合,K和V表示键值对,T、U和S表示任意类型

泛型类

类型变量用<>括起来,放在类后面

class GenericClass<T,U> {
    
}

泛型方法

泛型方法可在普通类中定义,还可有泛型参数,泛型变量放在修饰符后面,返回类型前面

class GenericClass {
    public <T> T genericMethod(T... a) {
        return a[a.length - 1];
    }
}

调用泛型方法时,需要在方法名前的<>放入具体的类型,在可推断类型参数的时候<>可省略

GenericClass genericClass = new GenericClass();
genericClass.<String>genericMethod("tom", "john");

但在方法中传递多种数据类型时,并返回其中一个数据类型时,会推断失败

int i = genericClass.genericMethod(3.14, 10, 0);

在这里插入图片描述
报错如上,意思是需要利用它们的共同父类进行接收,即将T推断为Object

Object i = genericClass.genericMethod(3.14, 10, 0);

类型变量限定

对于泛型参数的数组,如果我们想在内部利用compareTo进行比较,但T可能代表任意类型,怎么确保T实现了Comparable接口并拥有compareTo方法呢?

public <T> int genericMethod(T[] a) {
    return a[0].compareTo(a[1]);
}

利用限定符<T extends Comparable>可以确保T拥有compareTo方法,此时只有实现了Comparable接口的类才可作为参数传递

public <T extends Comparable> int genericMethod(T[] a) {
    return a[0].compareTo(a[1]);
}

若有多个限定符需用&分割,如<T extends Comparable & Serializable>,可以限定多个接口,但只能限定一个类,且限定类需放在第一个

泛型擦除

为了保持对JDK5之前的兼容性,所有泛型类都会转化为Java基本类,并将泛型变量T转为限定类型(无限定则为Object),可通过反射还原类进行验证

对于无限定的泛型,无论是<Person>还是<String>

class GenericClass<T> {
    private T para;
}

将转为Object变量

class GenericClass {
    private Object para;
}

对于有限定的泛型

class GenericClass<T extends Comparable & Serializable> {
    private T para;
}

就用第一个限定的类型变量替换,如切换两个限定的位置则转化为Serializable,但当需要调用Comparable中的compareTo方法时,还得强制转换成Comparable,故应该将标签接口(无方法接口)放在末尾

class GenericClass {
    private Comparable para;
}

桥方法

现有泛型GenericClass及其子类subClass,子类重写了父类的set和get方法

class GenericClass<T> {
    private T para;

    public T getPara() {
        return para;
    }

    public void setPara(T para) {
        this.para = para;
    }
}

class SubClass extends GenericClass<Integer> {
    @Override
    public void setPara(Integer para) {

    }

    @Override
    public Integer getPara() {
        return super.getPara();
    }
}

当出现泛型擦除时,GenericClass类中的T转化为Object,导致其setPara(Object para)和SubClass中的setPara(Integer para) 参数变得不一样,导致重写无效,利用反射还原类,可看到SubClass继承了setPara(Object para)

class com.example.demo0.SubClass extend com.example.demo0.GenericClass {
  com.example.demo0.SubClass();
  public java.lang.Integer getPara();
  public volatile java.lang.Object getPara();
  public void setPara(java.lang.Integer);
  public volatile void setPara(java.lang.Object);
}

对于以下的多态调用,父类的setPara(Object para)和子类的setPara(Integer para)不同,相等于子类没有重写,即没有多态,genericClass.setPara(1)会调用父类的setPara(Object para)

SubClass subClass = new SubClass();
GenericClass<Integer> genericClass = subClass;
genericClass.setPara(1);

但编译器为了维持多态的性质,会将setPara(Object para)修改为桥方法,即在其内部调用setPara(Integer para),即如下

public void setPara(Object para){
	 setPara((Integer)para);
}

同理,在SubClass存在Integer getPara()和Object getPara(),在编写代码的时候不能根据返回值类型重载代码,但在虚拟机中用参数类型和返回值类型确定一个方法,故这样是可以的

Tips:在覆盖方法时,子类方法可指定更为严格的返回值类型,其原理也是利用了桥方法

泛型和继承

对于如下继承结构

class Person {

}

class Man extends Person {

}

class GenericClass<T> {

}

GenericClass<Person>和GenericClass<Man>无任何关系,即不能将GenericClass<Man>赋值给GenericClass<Person>,下面会介绍通配符解决这个问题

GenericClass<Person> personGenericClass = new GenericClass<>();
GenericClass<Man> manGenericClass = new GenericClass<>();
//personGenericClass = manGenericClass;

泛型类可以扩展或实现其他泛型类,对于如下继承结构

class Person<T> {

}

class Man<T> extends Person<T> {

}

可让Person<T>指向Man<T>实现多态

Person<String> person = new Person<>();
Man<String> man = new Man<>();
person = man;

通配符限定

在如下代码中

class Person {

}

class Man extends Person {

}

class GenericClass<T> {
    private T para;

    public T getPara() {
        return para;
    }

    public void setPara(T para) {
        this.para = para;
    }
}

子类型限定

不能将GenericClass<Man>赋值给GenericClass<Person>,利用通配符可以允许类型参数变化,如GenericClass<? extends Person>表示可指向泛型为Person及其子类的变量

GenericClass<? extends Person> g1 = new GenericClass<Person>();
GenericClass<? extends Person> g2 = new GenericClass<Man>();

对于使用<? extends Person>通配符后,set方法无法调用,因为无法确定<? extends Person>的具体子类型是什么(不能把父类赋给子类),而对于get方法则可将返回值(父类或其子类)赋给父类引用

? extends Person getPara()
void setPara(? extends Person)

超类型限定

GenericClass<? super Man>表示可以指向泛型为Man及其父类的变量

GenericClass<? super Man> g3 = new GenericClass<Man>();
GenericClass<? super Man> g4 = new GenericClass<Person>();

对于使用<? super Man>通配符后,get方法返回值只能赋值给公共父类Object,而无法确定具体的类型(但得到Object并没什么用)。而对于set方法,不能确定Man的父类,但可以像普通方法一样传递Man及其子类

? super Man getPara()
void setPara(? super Man)

无限定

GenericClass<?>表示可以指向某一类型的泛型变量

GenericClass<?> g5 = new GenericClass<Man>();
GenericClass<?> g6 = new GenericClass<Person>();
GenericClass<?> g7 = new GenericClass<String>();
GenericClass<?> g8 = new GenericClass<Time>();

其get方法只能赋给Object,set方法则无法调用(也不能用Object调用),但可传递null

?  getPara()
void setPara(?)
<?>一般用于接收泛型参数,不可以对其进行创建实例并操作
List<?> objects = new ArrayList<>();
//objects.add("a");

PECS

PECS表示producer-extends和consumer-super。即泛型表示生成者用<? extends T>,消费者用<? super T>,如下pushAll对栈添加元素,popAll从栈取出元素

class Stack<T> {
    private Object[] elements = new Object[16];
    private int size = 0;

    public boolean isEmpty() {
        return size == 0;
    }

    public void push(T e) {
        elements[size++] = e;
    }

    public T pop() {
        Object result = elements[--size];
        elements[size] = null;
        return (T) result;
    }

    public void pushAll(Collection<? extends T> collection) {
        for (T e : collection) {
            push(e);
        }
    }

    public void popAll(Collection<? super T> collection) {
        while (!isEmpty()) {
            collection.add(pop());
        }
    }
}

等价性

类型变量限定和通配符限定具有一定的等价性,如下(如果类型变量T只在方法中出现一次,即可用通配符替代)

public static <E> void swap1(List<E> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}
public static void swap2(List<?> list, int i, int j) {
    //list.set(i, list.set(j, list.get(i)));
    swap1(list, i, j);
}

但对于swap2,注释的那一行无法编译,因为无法对<?>设值,但可利用类型变量捕捉通配符对应的类型并进行中转

类型令牌和类型安全的异构容器

当一个类的Class用在方法中用于传达编译时和运行时的信息时,就被称为类型令牌

class Container {
    private Map<Class<?>, Object> map = new HashMap<>();

    public <T> void put(Class<T> type, T instance) {
        map.put(type, instance);
    }

    public <T> T get(Class<T> type) {
        return type.cast(map.get(type));
    }
}

如上,所有键都是不同类型的映射称为类型安全的异构容器,使用cast方法进行转换,以确保存进去的value为key所对应的实例对象

泛型约束

  • 不能使用基本数据类型作为类型参数,即不能<int>,原因是泛型擦拭后变成Object,而Object是对象,不能转为int
  • 只存在一个擦除后的泛型类型,下面代码为true,因为getClass()都返回GenericClass.class
GenericClass<String> stringGenericClass = new GenericClass<>();
GenericClass<Double> doubleGenericClass = new GenericClass<>();
if (stringGenericClass.getClass() == doubleGenericClass.getClass()) {
    System.out.println("true");
}
  • 不能创建类型变量的数组,即不能new GenericClass<String>[10]
  • 将泛型用于可变参数是不安全的,因为可变参数实际是一个Object数组,如下将stringList[0]由List<String>改为了List<Integer>,再取出就会报转换异常,若能保证不对数组设值及引用转义,可用@SafeVarargs取消警告
public static void test(List<String>... stringList) {
    List<Integer> integerList = new ArrayList<>();
    integerList.add(1);
    Object[] objects = stringList;
    objects[0] = integerList;
    String s = stringList[0].get(0);
}
  • 不能实例化类型变量和数组,如new T()、new T[]、T.class,因为会转为Object
  • static和T不能同时使用,即不能定义static T para
  • 不能抛出或捕获泛型类实例,也不能让泛型类扩展Throwable
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值