面试题 - Java泛型、协变、逆变、类型擦除

目录

  • 一、Java 泛型之-协变&逆变
    • 1.逆变与协变
    • 2.泛型中的通配符实现协变与逆变
    • 3.总结
  • 二、Java泛型类型擦除以及类型擦除带来的问题
    • 1.Java泛型的实现方法:类型擦除
      • 1-2. 通过两个例子证明Java类型的类型擦除
      • 例1.原始类型相等
      • 例2.通过反射添加其它类型元素
    • 2.类型擦除后保留的原始类型
      • 例3.原始类型Object
      • 例4.Object泛型
    • 3.类型擦除引起的问题及解决方法
      • 3-1.先检查,再编译以及编译的对象和引用传递问题
      • 3-2.自动类型转换
      • 3-3.类型擦除与多态的冲突和解决方法
      • 3-4.泛型类型变量不能是基本数据类型
      • 3-5.编译时集合的instanceof
      • 3-6.泛型在静态方法和静态类中的问题

一、Java 泛型之-协变&逆变

先说一下java中的协变,逆变,不可变:

假设有如下类:

class Food{} // 默认继承Object
class Fruit extends Food{}
class Meat extends Food {}

class Apple extends Fruit{}
class Beef extends Meat{}

在这里插入图片描述

1.逆变与协变

Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象。而Object的数组类型Object[],理解成是由Object构造出来的一种新的类型,可以认为是一种构造类型,记f(Object),那么可以这么来描述协变和逆变:

当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变(子类赋值给父类);
当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变(父类赋值给子类);
如果上面两种关系都不成立则叫做不可变。

(1)例如数组协变:
Food food = new Fruit();  
// or
food = new Meat(); // 即 把子类赋值给父类引用

Fruit [] arrFruit = new Fruit[3];
Food [] arrFood = arrFruit; // 数组协变,把子类数组赋值给父类数组
(2)java 的泛型是不可变的(不能协变也不能逆变):
List<Beef> beefList = new ArrayList<>();
List<Food> foodList = beefList; //错误:不可协变,即子类list不能赋值给父类list
beefList = foodList; // 错误 : 不可逆变,即父类list不能赋值给子类list

addFood(beefList);// 错误::不可协变  

public void addFood(List<Food> list){
    list.add(new Apple());
}

为什么不能把beefList 作为参数传递给 addFood(List list) 方法呢?这儿我们用反正法类证明:

  1. addFood 方法接收List list 的类型参数,那么在addFodd 方法中能给 传递进来的List add 任何Food 的子类型。那么,既然addFood(beefList) 这行代码没有问题,也就意味着能通过addFood 方法给 beefList 里面增加任何 Food 的子类型(比如:list.add(new Apple())).
  2. 通过第一步,我们给 装肉beefList 里面加了一个苹果(Apple), 那么从beefList get 数据赋值给Beef 的时候(Beef beef = beefList.get(0))就会发生类型转换异常 ,因为取出来的是一个Apple, 这样明显有问题了。
    因此:得出java中泛型是不变得,既不能协变,也不能逆变。

2.泛型中的通配符实现协变与逆变

JAVA中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时就需要通配符?。
<? extends E>指明了上界,实现了泛型的协变,比如:

List<? extends Food> foodList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>();
foodList = appleList; // ok 协变,即子类list赋值给父类list

foodList.add(new Beef()); // 错误 不能执行添加null 以外的操作,原因:反正法:beef也是food子类,但是不该加入苹果列表,否则get时类型转换异常,就有问题
foodList.add(new Food());// 错误,同上
foodList.add(new Apple()); // 错误,同上

Food food = foodList.get(0); //ok, 把子类引用赋值给父类显然是可以的

<? extends Food> 指明了上界,即表示了集合中存放的对象是 Food 或者Food 的子类,因此foodList 就表示了一个存放 Food 或者Food 的子类的集合,appleList 集合就是一个存放Food 的子类 Apple的集合, 因此可以把 appleList 赋值给 foodList,但是不能对foodList 添加除null 以外的任何对象。为什么不能添加呢?其实很简单,如果可以允许的话,那么foodList 就可以添加Food 或者Food 的子类型,那么上面代码中在foodList = appleList; 赋值后,执行foodList.add(new Beef()); 操作就会导致给 appleList 里面添加了一个Beef 对象,显然这样是不对的。因此可以得出结论。

<? super E>指明了下界,实现了泛型的逆变,比如:

List<? super Fruit> fruitList = new ArrayList<>();
List<Food> foodList = new ArrayList<>();
foodList.add(new Meat()); 

fruitList = foodList; // ok 逆变,父类列表赋值给子类列表

fruitList.add(new Apple()); // ok,只能添加 Fruit 或者 其子类
fruitList.add(new Food());// error, 只能添加 Fruit 或者 其子类

Fruit fruit = fruitList.get(0); // error,get出来的元素是Object类型
Object obj = fruitList.get(0)// ok 

<? super Fruit> 指明了下界,即表示了集合中存放的对象只能是 Fruit 或者其父类,因此fruitList 表示了一个存放Fruit 或者其父类的集合,那么就可以把一个Fruit 父类Food 的集合foodList 赋值给 fruitList(即:fruitlist = foodList),这儿可能有的人就有点疑问了?<? super Fruit> 指明了集合存放的对象是Fruit 或者其父类,那为什么往集合中添加Fruit的父类元素不行呢(fruitList.add(new Food()))?同样我们用反正法来说明,假如可以向<? super E> 集合中添加 E 的父类,那么就可以向fruitList 添加Fruit 的父类(Food or Object),在本例中,我们把foodList 赋值给了fruitList(fruitList = foodList),既然可以添加父元素,那么我们执行fruitList.add(new Food) 就相当于给 foodList集合里面添加了一个Food元素,这看起来没有什么问题,因为foodList 本来就是用来装Food 元素的,但是如果我们添加的Fruit 父类是Object 对象(或者一个Fruit 实现的和Food 无关的接口,又或者Food继承的一个其他类B,那么B也是 Fruit 的父类),那么就意味着给foodList 里面添加了和Food 无关的类,这样显然是不行的。那么为什么往fruitList 里面添加Fruit或者其子类 元素是可以的,因为fruitList 表示的就是一个存放Fruit 或者其父类的集合,所以这个集合肯定就能装Fruit 或者其子类,比如上面把foodList 赋值给fruitList 后,往fruitList 里面添加 Apple 就相当于往foodList 里面添加Apple,显然是可以的,对于get 操作时为什么读取出来的是Object ,是因为 fruitList 集合表示的存放Fruit 或者其父类的集合,而Fruit 的父类可能有很多,在本例中由于我们知道存的是Food(fruitList = foodList), 但是在实际中,谁知道在运行时到底存的是哪一个呢?因此为了安全全部定义为Object 类型。因此得出结论:<? super E> 支持逆变,能往集合中添加E 或者E子类的对象。不能添加E的任何父类对象,读取时的对象为Object 类型,

3.总结:

  1. java中泛型是不变的,既不能协变,也不能逆变。
  2. java中<? extends E>指明了上界,实现了泛型的协变
  3. java中<? super E>指明了下界,实现了泛型的逆变

二、Java泛型类型擦除以及类型擦除带来的问题

1.Java泛型的实现方法:类型擦除

大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

如在代码中定义List<Object>和List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。

1-2.通过两个例子证明Java类型的类型擦除

例1.原始类型相等

public class Test {

    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

        System.out.println(list1.getClass() == list2.getClass());
    }

}

在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型的,只能存储字符串;一个是ArrayList<Integer>泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。
例2.通过反射添加其它类型元素

public class Test {

    public static void main(String[] args) throws Exception {

        ArrayList<Integer> list = new ArrayList<Integer>();

        list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer

        list.getClass().getMethod("add", Object.class).invoke(list, "asd");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

}

在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。

2.类型擦除后保留的原始类型

在上面,两次提到了原始类型,什么是原始类型?

原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

例3.原始类型Object

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}  

Pair的原始类型为:

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

因为在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。

从上面的例2中,我们也可以明白ArrayList<Integer>被擦除类型后,原始类型也变为Object,所以通过反射我们就可以存储字符串了。

如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换。

比如: Pair这样声明的话

public class Pair<T extends Comparable> {}

那么原始类型就是Comparable。

要区分原始类型和泛型变量的类型。

在调用泛型方法时,可以指定泛型,也可以不指定泛型。

在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类

public class Test {  
    public static void main(String[] args) {  

        /**不指定泛型的时候*/  
        int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  
        Number f = Test.add(1, 1.2); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number  
        Object o = Test.add(1, "asd"); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object  

        /**指定泛型的时候*/  
        int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类  
        int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float  
        Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
    }  

    //这是一个简单的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    }  
}

其实在泛型类中,不指定泛型的时候,也差不多,只不过这个时候的泛型为Object,就比如ArrayList中,如果不指定泛型,那么这个ArrayList可以存储任意的对象。

例4.Object泛型

public static void main(String[] args) {  
    ArrayList list = new ArrayList();  
    list.add(1);  
    list.add("121");  
    list.add(new Date());  
}  

3.类型擦除引起的问题及解决方法

因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀问题,但是也引起来许多新问题,所以,SUN对这些问题做出了种种限制,避免我们发生各种错误。

3-1.先检查,再编译以及编译的对象和引用传递问题

Q: 既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

A: Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

例如:

public static  void main(String[] args) {  

    ArrayList<String> list = new ArrayList<String>();  
    list.add("123");  
    list.add(123);//编译错误  
}

在上面的程序中,使用add方法添加一个整型,在IDE中,直接会报错,说明这就是在编译之前的检查,因为如果是在编译之后检查,类型擦除后,原始类型为Object,是应该允许任意引用类型添加的。可实际上却不是这样的,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。

那么,这个类型检查是针对谁的呢?我们先看看参数化类型和原始类型的兼容。

以 ArrayList举例子,以前的写法:

ArrayList list = new ArrayList();  

现在的写法:

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

如果是与以前的代码兼容,各种引用传值之间,必然会出现如下的情况:

ArrayList<String> list1 = new ArrayList(); //第一种 情况
ArrayList list2 = new ArrayList<String>(); //第二种 情况

这样是没有错误的,不过会有个编译时警告。

不过在第一种情况,可以实现与完全使用泛型参数一样的效果,第二种则没有效果。

因为类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

举例子:

public class Test {  

    public static void main(String[] args) {  

        ArrayList<String> list1 = new ArrayList();  
        list1.add("1"); //编译通过  
        list1.add(1); //编译错误  
        String str1 = list1.get(0); //返回类型就是String  

        ArrayList list2 = new ArrayList<String>();  
        list2.add("1"); //编译通过  
        list2.add(1); //编译通过  
        Object object = list2.get(0); //返回类型就是Object  

        new ArrayList<String>().add("11"); //编译通过  
        new ArrayList<String>().add(22); //编译错误  

        String str2 = new ArrayList<String>().get(0); //返回类型就是String  
    }  

}  

通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

泛型中参数话类型为什么不考虑继承关系?

在Java中,像下面形式的引用传递是不允许的:

ArrayList<String> list1 = new ArrayList<Object>(); //编译错误  
ArrayList<Object> list2 = new ArrayList<String>(); //编译错误

我们先看第一种情况,将第一种情况拓展成下面的形式:


ArrayList<Object> list1 = new ArrayList<Object>();  
list1.add(new Object());  
list1.add(new Object());  
ArrayList<String> list2 = list1; //编译错误

实际上,在第4行代码的时候,就会有编译错误。那么,我们先假设它编译没错。那么当我们使用list2引用用get()方法取值的时候,返回的都是String类型的对象(上面提到了,类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。(这也是泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷)。

再看第二种情况,将第二种情况拓展成下面的形式:

ArrayList<String> list1 = new ArrayList<String>();  
list1.add(new String());  
list1.add(new String());
ArrayList<Object> list2 = list1; //编译错误

没错,这样的情况比第一种情况好的多,最起码,在我们用list2取值的时候不会出现ClassCastException,因为是从String转换为Object。可是,这样做有什么意义呢,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以java不允许这么干。再说,你如果又用list2往里面add()新的对象,那么到时候取得时候,我怎么知道我取出来的到底是String类型的,还是Object类型的呢?

所以,要格外注意,泛型中的引用传递的问题。

3-2.自动类型转换

因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。

既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?

看下ArrayList.get()方法:

public E get(int index) {  

    RangeCheck(index);  

    return (E) elementData[index];  

}

可以看到,在return之前,会根据泛型变量进行强转。假设泛型类型变量为Date,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(Date)elementData[index]。所以我们不用自己进行强转。当存取一个泛型域时也会自动插入强制类型转换。假设Pair类的value域是public的,那么表达式:

Date date = pair.value;

也会自动地在结果字节码中插入强制类型转换。

3-3.类型擦除与多态的冲突和解决方法

现在有这样一个泛型类:

class Pair<T> {  

    private T value;  

    public T getValue() {  
        return value;  
    }  

    public void setValue(T value) {  
        this.value = value;  
    }  
}

然后我们想要一个子类继承它。

class DateInter extends Pair<Date> {  

    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  

    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}

在这个子类中,我们设定父类的泛型类型为Pair<Date>,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:将父类的泛型类型限定为Date,那么父类里面的两个方法的参数都为Date类型。

public Date getValue() {  
    return value;  
}  

public void setValue(Date value) {  
    this.value = value;  
}

所以,我们在子类中重写这两个方法一点问题也没有,实际上,从他们的@Override标签中也可以看到,一点问题也没有,实际上是这样的吗?

分析:实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子:

class Pair {  
    private Object value;  

    public Object getValue() {  
        return value;  
    }  

    public void setValue(Object  value) {  
        this.value = value;  
    }  
}  

再看子类的两个重写的方法的类型:

@Override  
public void setValue(Date value) {  
    super.setValue(value);  
}  
@Override  
public Date getValue() {  
    return super.getValue();  
}

先来分析setValue方法,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
  
我们在一个main方法测试一下:

public static void main(String[] args) throws ClassNotFoundException {  
        DateInter dateInter = new DateInter();  
        dateInter.setValue(new Date());                  
        dateInter.setValue(new Object()); //编译错误  
}

如果是重载,那么子类中两个setValue方法,一个是参数Object类型,一个是Date类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,却是是重写了,而不是重载了。

为什么会这样呢?

原因是这样的,我们传入父类的泛型类型是Date,Pair<Date>,我们的本意是将泛型类变为如下:

class Pair {  
    private Date value;  
    public Date getValue() {  
        return value;  
    }  
    public void setValue(Date value) {  
        this.value = value;  
    }  
}

然后再子类中重写参数类型为Date的那两个方法,实现继承中的多态。

可是由于种种原因,虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date类型参数的方法啊。

于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法

首先,我们用javap -c className的方式反编译下DateInter子类的字节码,结果如下:

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  com.tao.test.DateInter();  
    Code:  
       0: aload_0  
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>":()V  
       4: return  

  public void setValue(java.util.Date);  //我们重写的setValue方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V  
       5: return  

  public java.util.Date getValue();    //我们重写的getValue方法  
    Code:  
       0: aload_0  
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;  
       4: checkcast     #26                 // class java/util/Date  
       7: areturn  

  public java.lang.Object getValue();     //编译时由编译器生成的巧方法  
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;  
       4: areturn  

  public void setValue(java.lang.Object);   //编译时由编译器生成的巧方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: checkcast     #26                 // class java/util/Date  
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V  
       8: return  
}

从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。

所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。

不过,要提到一点,这里面的setValue和getValue这两个桥方法的意义又有不同。

setValue方法是为了解决类型擦除与多态之间的冲突。

而getValue却有普遍的意义,怎么说呢,如果这是一个普通的继承关系:

那么父类的setValue方法如下:

public Object getValue() {  
    return super.getValue();  
}

而子类重写的方法是:

public Date getValue() {  
    return super.getValue();  
}

其实这在普通的类继承中也是普遍存在的重写,这就是协变(子类赋值给父类)。

关于协变:看文章开头

并且,还有一点也许会有疑问,子类中的巧方法Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。

3-4.泛型类型变量不能是基本数据类型

不能用类型参数替换基本类型。就比如,没有ArrayList<double>,只有ArrayList<Double>。因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储double值,只能引用Double的值。

3-5.编译时集合的instanceof
ArrayList<String> arrayList = new ArrayList<String>();

因为类型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了。

那么,编译时进行类型查询的时候使用下面的方法是错误的

if( arrayList instanceof ArrayList<String>)
3-6.泛型在静态方法和静态类中的问题

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

举例说明:

public class Test2<T> {    
    public static T one;   //编译错误    
    public static  T show(T one){ //编译错误    
        return null;    
    }    
}

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

但是要注意区分下面的一种情况:

public class Test2<T> {    

    public static <T >T show(T one){ //这是正确的    
        return null;    
    }    
}

因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T。

写一篇好博客不容易,参考来源

  1. http://blog.csdn.net/wisgood/article/details/11762427
  2. https://blog.csdn.net/Just_keep/article/details/79482365
  3. https://www.cnblogs.com/wuqinglong/p/9456193.html
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值