java泛型的使用

泛型的设计目的之一就是保证了类型安全,让这种运行时期的错误在编译期就能发现

java中通配符有3中,他们分别是:通配符上界,通配符下界,无界通配符。

1、无界通配符

语法:<?>

?可以代表任意类型

“任意”也就是未知类型。
不能 add但可以get取元素要用Object类型取

场景1: 方法入参的优化

实际上这也是无界通配符?最多的使用场景

//这个方法我们只能传入List<Object>
public static void printList(List<Object> list) { }  

//这个方法我们可以能传入List<Interger>,List<String>,.....
public static void printList(List<?> list) { }

场景2:集合的读与写

public static void printList(List<?> list) {
        Object o = list.get(0);
        list.add("1");//编译报错
        list.add(null);//这是可以的
}

这说明:List<?> 是只读的。不能写

场景3:对方法对重写的优化

我们都知道方法的协变可以使方法返回值可以是父类返回值的任意子类,如下

public interface Father  { Number printList() ;}

class Son  implements Father{
    @Override
    public  Integer printList() {
        return null;
    }
}

但是如果我们想要这种效果

父类方法: public List<Number> printList()
子类方法: public List<String> printList() //编译报错

这时候我们就可以使用<?>

public   interface Father  {  List<?> printList(); }

class Son  implements Father{
    @Override
    public List<Integer> printList() {
        return null;
    }
}
2、通配符上界
  • 语法:<? extend T>

注意在这里T是一个具体类型,比如List, 他可以限制泛型的上界类型为List

场景1 方法的入参

class GrandFather{}
class Father extends GrandFather {}
class Son  extends Father{}

public class Demo {
   
    public static void test(List<? extends Father> list){}

    public static void main(String[] args) {
        List<GrandFather> list1 = new ArrayList<>();
        List<Son> list2 = new ArrayList<>();
        test(list1);//编译错误
        test(list2);
    }
}

场景2、方法的返回值

class GrandFather{}
class Father extends GrandFather {}
class Son  extends Father{}

public class Demo {
    public  List<? extends Father> test(){return null; }
}

class DemoSon extends Demo{
    @Override
    public List<Son> test() {
        return null;
    }
   /* @Override 编译错误
    public List<GrandFather> test() {
        return null;
    }*/
}

场景3 读与写

class GrandFather{}
class Father extends GrandFather {}
class Son  extends Father{}

public class Demo {
    public static void test(List<? extends Father> list){
        list.add(new Father());//编译报错
        list.add(new GrandFather());//编译报错
        list.add(new Son());//编译报错
        Father father = list.get(0);//他一定是个Father
        list.add(null);
    }
}
3 通配符下界

语法:<? super T>

注意在这里T是一个具体类型,比如List, 他可以限制泛型的上界类型为List

场景1 方法的入参

class GrandFather{}
class Father extends GrandFather {}
class Son  extends Father{}

public class Demo {

    public static void test(List<? super Father> list){}

    public static void main(String[] args) {
        List<GrandFather> list1 = new ArrayList<>();
        List<Son> list2 = new ArrayList<>();
        test(list1);
        test(list2);//编译错误
    }
}

场景2、方法的返回值

class GrandFather{}
class Father extends GrandFather {}
class Son  extends Father{}

public class Demo {
    public  List<? super Father> test(){return null; }
}

class DemoSon extends Demo{
    @Override
    public List<GrandFather> test() {
        return null;
    }
   /* @Override 编译错误
    public List<Son> test() {
        return null;
    }*/
}

场景3 读与写

class GrandFather{}
class Father extends GrandFather {}
class Son  extends Father{}

public class Demo {
    public static void test(List<? super Father> list){
        list.add(new Father());
        list.add(new GrandFather());//编译报错
        list.add(new Son());
        Object o = list.get(0);
        list.add(null);
    }
}
4、泛型方法

在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。非泛型类也可以有泛型方法

public <T> T test(Class<T> c)

1、public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
2、只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
3、表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
4、与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
泛型方法的基本用法

光看上面的例子可能依然会非常迷糊,我们再通过一个例子,把我泛型方法再总结一下。

public class Generic<T>{    
    private T key;
    public Generic(T key) {
        this.key = key;
    }
    public T getKey(){
    return key;
}

我想说的其实是这个,虽然在getKey()方法中使用了泛型,但是这并不是一个泛型方法。
这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
所以在这个方法中才可以继续使用 T 这个泛型。

class GenerateTest<T>{
    public void show_1(T t){
        System.out.println(t.toString());
    }

public <E> void show_3(E t){
    System.out.println(t.toString());
}

在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。

当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体类型。这称为类型参数判断,因此,我们可以像普通方法一样调用show_2();,而且就好像show_2()被无限次的重载过。

5、静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

public class StaticGenerator<T> {
    ....
    ....
    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){
    }
}
6、泛型在静态方法和静态类中的问题

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

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

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

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

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

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

7、显示的类型推断

在类型方法中,可以显示的指明类型,要显示地指明类型,必需在点操作符与方法名之间插入尖括号,然后把类型置于其内如果定义在该方法的内部,必需在点操作符之间使用this关键字,如果是static的方法,必需再点操作符之间加上类名。如下:

public static <K, V> Map<K, V> map() { 
        return new HashMap<K, V>(); 
} 
 
static void f(Map<Persom, List<? extends Persom>> pet) { 
        System.out.println(pet); 
} 
 
public static void main(String[] args) { 
        Map<Persom, List<? extends Persom>> pet = New.map(); 
        f(pet); 
        f(New map());//错误
        f(New.<Persom, List<? extends Persom>> map());// 显示的类型说明-- 静态方法在点(.)操作符前用类名,非静态this 
}

泛型方法总结

泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

8、泛型类与继承

类型注释:类型变量要大写,且比较短,在java库中,E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T表示任意类型

泛型类继承很简单,一句话就是,所有的泛型参数在使用时都能被指定为特定的类型,要么开发者指定要么编译器可以推断出来

1、最正常的继承,子类的泛型参数和父类的参数是一致的

class Father<T> {
     
}
class Son<T> extends Father<T> { }// 最正常的继承,子类的泛型参数和父类的参数是一致的 

2、子类增加了一个泛型参数,父类的泛型参数不能遗漏,所以仍然要定义

class Father<T> {
     
}
class Son2<E, F> extends Father<F> {
     
}

3、继承时指定父类的泛型参数,子类就不用再写泛型参数

class Father<T> {
     
}
class Son extends Father<String> {
     
}

注意:这时候要 必须指定父类的泛型参数,否则报错

4、父类指定了类型,子类又增加了,这时子类的只是新增加的泛型参数,跟父类没有关系

class Father<T> {
     
}
class Son<T> extends Father<String> {
     
}
9、泛型类的方法继承

现在有这样一个泛型类:

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类型:“

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; 
    } 
} 

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

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

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

DateInter dateInter=new DateInter(); 
dateInter.setValue(new Date());                 
dateInter.setValue(new Object());//编译错误 

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

为什么会这样呢?

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

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代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。

10、泛型数组

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

作为一个数组,必须牢记它的元素类型,也就是所有的元素对象都必须一个样(协变),而泛型类型恰恰做不到这一点。

class Cls <? extends Number> {...}中数据类型可以是Integer,Double等等多种类型,同一个容器内(比如List)可以存放他们所有,因为容器是以对象为单位的。但是数组就不行,因为数组本身就是一个数据类型,它的所有元素都是协变的,如果其中一个元素是Integer类型,那么其他元素也必须是Integer类型,这时候想存放Double类型,不出错才怪。

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

List<String>[] ls = new ArrayList<String>[10];//编译错误

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

List<?>[] ls = new ArrayList<?>[10];  //可以
List<String>[] as = new ArrayList[10];//可以

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

List<String>[] lsa = new List<String>[10]; // Not really allowed.   
Object o = lsa;   
Object[] oa = (Object[]) o;   
List<Integer> li = new ArrayList<Integer>();   
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<Integer> li = new ArrayList<Integer>();   
li.add(new Integer(3));   
oa[1] = li; // Correct.   
Integer i = (Integer) lsa[1].get(0); // OK
11、泛型接口

一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口,如下

interface Payable<T>{}
class Employye implements Payable<Employye>{}
public class Hourly extends Employye implements Payable<Hourly>{}//不能编译

Hourly不能编译,因为擦除会将Payable和Payable简化为相同的接口,这样,上面的代码就意味着在重复两次的实现相同的接口。但十分有趣的是下面两种情况可以实现

情况一

interface Payable{}
class Employye implements Payable{}
public class Hourly extends Employye implements Payable{}//能编译

情况二

interface Payable<T>{}
class Employye implements Payable<Employye>{}
public class Hourly extends Employye implements Payable<Employye>{}//能编译
12、异常中的泛型

1、不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展Throwable都不合法。例如:下面的定义将不会通过编译:

public class Problem<T> extends Exception{......} //不会通过编译

为什么不能扩展Throwable,因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉,那么,假设上面的编译可行,那么,在看下面的定义:

try{ 
}catch(Problem<Integer> e1){ 
 ...  
}catch(Problem<Number> e2){ 
... 
}

类型信息被擦除后,那么两个地方的catch都变为原始类型Object,那么也就是说,这两个地方的catch变的一模一样,就相当于下面的这样

try{ 
}catch(Problem<Object> e1){ 
...
}catch(Problem<Object> e2){ 
... 

这个当然就是不行的。就好比,catch两个一模一样的普通异常,不能通过编译一样:

try{ 
}catch(Exception e1){ 
...  
}catch(Exception  e2){//编译错误 
...   

2、不能在catch子句中使用泛型变量

public static <T extends Throwable> void doWork(Class<T> t){ 
    try{ 
        ... 
    }catch(T e){ //编译错误 
        ... 
    } 
} 

因为泛型信息在编译的时候已经变味原始类型,也就是说上面的T会变为原始类型Throwable,那么如果可以再catch子句中使用泛型变量,那么,下面的定义呢:

public static <T extends Throwable> void doWork(Class<T> t){ 
    try{ 
        ... 
    }catch(T e){ //编译错误 
        ... 
    }catch(IndexOutOfBounds e){ 
} 

根据异常捕获的原则,一定是子类在前面,父类在后面,那么上面就违背了这个原则。即使你在使用该静态方法的使用T是ArrayIndexOutofBounds,在编译之后还是会变成Throwable,ArrayIndexOutofBounds是IndexOutofBounds的子类,违背了异常捕获的原则。所以java为了避免这样的情况,禁止在catch子句中使用泛型变量。

但是在异常声明中可以使用类型变量。下面方法是合法的。

public static<T extends Throwable> void doWork(T t) throws T{ 
    try{ 
        ... 
    }catch(Throwable realCause){ 
        t.initCause(realCause); 
        throw t;  
} 

上面的这样使用是没问题的。

13、泛型相关面试题

泛型类型变量不能是基本数据类型

1、Java中的泛型是什么 ? 使用泛型的好处是什么?

泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。

泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。

2、Java的泛型是如何工作的 ? 什么是类型擦除 ?

泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。

编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。

3、什么是泛型中的限定通配符和非限定通配符 ?

限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。

4、List<? extends T>和List <? super T>之间有什么区别 ?

这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。在本段出现的连接中可以找到更多信息。

5、如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?

编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样:

public V put(K key, V value) {
return cache.put(key, value);
}

6、Java中如何使用泛型编写带有参数的类?

这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。

7、编写一段泛型程序来实现LRU缓存?

对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。

8、你可以把List传递给一个接受List参数的方法吗?

对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List应当可以用在需要List的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List可以存储任何类型的对象包括String, Integer等等,而List却只能用来存储Strings。

9、Array中可以用泛型吗?

这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。

10、如何阻止Java中的类型未检查的警告?

如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告,例如List rawList = new ArrayList()
注意: Hello.java使用了未检查或称为不安全的操作;
这种警告可以使用@SuppressWarnings(“unchecked”)注解来屏蔽。

11、Java中List和原始类型List之间的区别?

原始类型和带参数类型之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。这道题的考察点在于对泛型中原始类型的正确理解。它们之间的第二点区别是,你可以把任何带参数的泛型类型传递给接受原始类型List的方法,但却不能把List传递给接受List的方法,因为会产生编译错误。

12、Java中List<?>和List之间的区别是什么?

这道题跟上一道题看起来很像,实质上却完全不同。List<?> 是一个未知类型的List,而List其实是任意类型的List。你可以把List, List赋值给List<?>,却不能把List赋值给List。

13、List和原始类型List之间的区别.

该题类似于“原始类型和带参数类型之间有什么区别”。带参数类型是类型安全的,而且其类型安全是由编译器保证的,但原始类型List却不是类型安全的。你不能把String之外的任何其它类型的Object存入String类型的List中,而你可以把任何类型的对象存入原始List中。使用泛型的带参数类型你不需要进行类型转换,但是对于原始类型,你则需要进行显式的类型转换。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值