java 泛型和泛型边界 2分钟看懂各种框架 无界 ? 下界 super 上界extents

什么是泛型?为什么要使用泛型?

 

泛型

泛型就是参数化类型

  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定
  • 泛型归根到底就是“模版”

优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。

泛型主要使用在集合中

 

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
    Log.d("泛型测试","item = " + item);
}

毫无疑问,程序的运行结果会以崩溃结束:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

 

为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

 

泛型通配符  ?

当在赋值的时候,上面一节说赋值的都是为具体类型,当赋值的类型不确定的时候,我们用通配符(?)代替了:

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}

 

 

我们知道IngeterNumber的一个子类,同时在特性章节中我们也验证过GenericGeneric实际上是相同的一种基本类型。那么问题来了,在使用Generic作为形参的方法中,能否使用Generic的实例传入呢?在逻辑上类似于GenericGeneric是否可以看成具有父子关系的泛型类型呢?

为了弄清楚这个问题,我们使用Generic这个泛型类继续看下面的例子:

public void showKeyValue1(Generic obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}

 

Generic gInteger = new Generic(123);
Generic gNumber = new Generic(456);

showKeyValue(gNumber);

// showKeyValue这个方法编译器会为我们报错:Generic 
// cannot be applied to Generic
// showKeyValue(gInteger);

 

通过提示信息我们可以看到Generic不能被看作为`Generic的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是GenericGeneric父类的引用类型。由此类型通配符应运而生。

我们可以将上面的方法改一下:

public void showKeyValue1(Generic obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}

 

 

泛型边界问题,super和extends关键字

 

 

 

 

关于泛型数组要提一下

看到了很多文章中都会提起泛型数组,经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。

也就是说下面的这个例子是不可以的:

List[] ls = new ArrayList[10];  

而使用通配符创建泛型数组是可以的,如下面这个例子:

List[] ls = new ArrayList[10]; 

这样也是可以的:

List[] ls = new ArrayList[10];

下面使用Sun的一篇文档的一个例子来说明这个问题:

List[] lsa = new List[10]; // Not really allowed.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List li = new ArrayList();    
li.add(new Integer(3));    
oa[1] = li; // Unsound, but passes run time store check    
String s = lsa[1].get(0); // Run-time error: ClassCastException.

 

这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,

但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,

上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。

而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。

 

下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。

List[] lsa = new List[10]; // OK, array of unbounded wildcard type.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List li = new ArrayList();    
li.add(new Integer(3));    
oa[1] = li; // Correct.    
Integer i = (Integer) lsa[1].get(0); // OK 

 

 1.上界不能往里存,只能往外取

  不要太疑惑,其实很好理解,因为编译器只知道容器里的是Fruit或者Fruit的子类,但不知道它具体是什么类型,所以存的时候,无法判断是否要存入的数据的类型与容器种的类型一致,所以会拒绝set操作。

  2.下界往外取只能赋值给Object变量,不影响往里存

  因为编译器只知道它是Fruit或者它的父类,这样实际上是放松了类型限制,Fruit的父类一直到Object类型的对象都可以往里存,但是取的时候,就只能当成Object对象使用了。

      3.无边界类型通配符() 等同于 上边界通配符,所以可以以Object类去获取数据,但意义不大。

      4.下边界类型通配符()下边界通配符 + 上边界通配符,所以可以以Object类去获取数据,但意义不大。

三句话总结JAVA泛型通配符

 

1. “?”不能添加元素

以“?”声明的集合,不能往此集合中添加元素,所以它只能作为生产者(亦即它只能被迭代),如下:

List names = Lists.newArrayList("yiifaa");

// 通配符声明的集合,获取的元素都是Object类型

List allNames = Lists.newArrayList("yiifee");

allNames.addAll(names);

// 只能以Object迭代元素

for(Object name: names) {

    System.out.println(name);

}

2. “? extends T”也不能添加元素

以“? extends T”声明的集合,不能往此集合中添加元素,所以它也只能作为生产者,如下:

List names = Lists.newArrayList("yiifaa");

// 声明消费者

List allNames = Lists.newArrayList("yiifee");

// 消费生产者的元素

allNames.addAll(allNames);

相对于以“?”声明的集合,“? extends T”能更轻松地迭代元素:

List names = Lists.newArrayList("yiifaa");

// 能更精确地确认元素类型

for(String name: names) {

    System.out.println(name);

3. “? super T”能添加元素

 

在通配符的表达式中,只有“? super T”能添加元素,所以它能作为消费者(消费其他通配符集合)。

 

List allNames = Lists.newArrayList("yiifaa");

List names = Lists.newArrayList("yiifee");

// 可以直接添加泛型元素

allNames.addAll(names);

// 也可以添加通配符泛型元素

List names1 = Lists.newArrayList("yiifee");

allNames.addAll(names1);

8

针对采用“? super T”通配符的集合,对其遍历时需要多一次转型,如下:

// 只能获取到Object类型

for(Object name: allNames) {

    // 这里需要一次转型

    System.out.println(name);

}

你可能感兴趣的:(java)