java-泛型

使用泛型可以在编译时而不是在运行时检测出错误,在试图使用一个不相容的对象时,编译器就会检测出这个错误。提高了可靠性和可读性。

泛型类型必须是引用类型,不能用基本类型来替代泛型类型。


一、泛型类和接口

class MyStack<E>{

    public MyStack() {
        //构造方法应该这样定义
    }
}

创建时

MyStack<String> stack1 = new MyStack<String>();

可以定义一个类或接口个作为泛型或者接口的子类型
String类实现Comparable接口

public class String implements Comparable<String>

二、泛型方法

public class Test {
    public static void main(String[] args){
        String[] str1 = {"a", "b", "c"};
        Integer[] integer1 = {1, 2, 3};

        Test.<String>print(str1);
        Test.<Integer>print(integer1);
    }

    public static <E> void print(E[] list){
        for (int i = 0; i < list.length; i++) {
            System.out.print(list[i] + " ");
        }
        System.out.println();
    }
}

方法调用中可以省略掉类型参数,写成

Test.print(str1);

可以定义泛型类型为<T extends MyType>这样的泛型类型称为受限的


三、如何理解<T extends Comparable<T>><T extends Comparable<? super T>>

首先理解一下<T extends Comparable<T>>
这里写图片描述

类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T。只有这样,T 的实例之间才能相互比较。例如,在实际调用时若使用的具体类是 Food,那么 Food 必须implements Comparable<Food>

再说<T extends Comparable<? super T>>

类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T 或 T 的任一父类。T 的实例之间,T 的实例和它的父类的实例之间,可以相互比较大小。例如,在实际调用时若使用的具体类是 Fruit (假设 Fruit 有一个父类 Food),Fruit 可以从Food 那里继承 Comparable<food> ,或者自己 implements Comparable<Fruit>

四、?非受限通配
和? extends Object是相同的, 表示任何一种对象类型

public class Test {
    public static void main(String[] args){
        List<String> strList = new ArrayList<>();
        strList.add("hello");
        strList.add("hi");

        print(strList);
    }

    public static <E> void print(List<?> list){
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
    }
}

不能使用List<Object>代替List<?>因为List<String>并不是List<Object>的实例


五、上界<? extends Class>和下界<? super Class>

get-put principle:

    Use an extends wildcard when you only get values out of a structure.
    Use a super wildcard when you only put values into a structure.
    And don't use a wildcard when you both want to get and put from/to a structure.

Exceptions are:

    You cannot put anything into a type declared with an extends wildcard except for the value null, which belongs to every reference type.

    You cannot get anything out from a type declared with an super wildcard except for a value of type Object, which is a super type of every reference 

PECS原则:
如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)

如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)

如果既要存又要取,那么就不要使用任何通配符。

这里写图片描述

原问题连接:http://stackoverflow.com/questions/1292109/explanation-of-the-get-put-principle

大概意思为

class Fruit{}
class Apple extends Fruit{}
class Banana extends Fruit{}

public class Test {
    public static void main(String[] args){
        List<? extends Fruit> list = new ArrayList<>();

        //list.add(new Apple());
        //list.add(new Banana());
        list.add(null);

        list.contains(new Apple());
        list.indexOf(new Apple());
    }
}

不能向其中添加Apple或Banana的实例

你知道那是fruit的集合,但是不知道是那种特定的。可以取出,例如list.contains()和list.indexOf(),你知道取出的将会是fruit。但是不能添加进去,编译器并不能了解这里到底需要哪种 Fruit 的子类型
但是可以add(null),因为 null 代表任何类型。

class Fruit{}
class Banana extends Fruit{}
class YellowBanana extends Banana{}

public class Test {
    public static void main(String[] args){
        List<? super Banana> list = new ArrayList<>();

        list.add(new Banana());
        list.add(new YellowBanana());
        //list.add(new Fruit());

        //Banana banana = list.get(0);
        Object banana = list.get(0);
    }
}

现在是List<? super Banana>,其中都是具有Banana的父类的列表,可以添加进Banana的实例或者Banana的子类YellowBanana。但是不能add(new Fruit()), 因为?代表Banana的父类,但是编译器不知道你要添加哪种Banana的父类,因此不能安全地添加。

当取出的时候,你不知道将会得到什么(可能不会是个Banana),所以不能,可以确定它是一个Object。因为编译器不能确定列表中的是Banana的哪个子类,所以只能返回 Object。


举个使用例子

public class Test {
    public static void main(String[] args){
        List<String> list1 = new ArrayList<>();
        List<Object> list2 = new ArrayList<>();

        list1.add("hello");
        list1.add("world");

        list2.add(1);

        add(list1, list2);
        print(list2);
    }

    public static <T> void add(List<T> list1, List<? super T> list2){
        for (int i = 0; i < list1.size(); i++) {
            list2.add(list1.get(i));
        }
    }

    public static void print(List<Object> list){
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

输出:
1
hello
world

这里<? super T>表示类型T或T的父类型。Object是String的父类型


这里写图片描述
泛型类型和通配类型之间的关系(无视水印orz)

六、擦除
泛型是使用一种称为类型擦除的方法实现的,编译器使用泛型类型信息来编译代码,但是随后消除它,因此泛型信息在运行时是不可用的,这种方法使泛型代码向后兼容使用原始类型的遗留代码

Class c1 = new ArrayList<Integer>().getClass(); 
Class c2 = new ArrayList<String>().getClass(); 
System.out.println(c1 == c2);

结果为:true

这是java泛型擦除造成的,ArrayList<Integer>()new ArrayList<String>(),在编译器中都会转换成原始类型ArrayList。泛型存在于编译时,一旦编译器确认泛型类型是安全使用的,就会将它替换成原始类型

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

类型擦除的原则
1、所有参数化容器类都被擦除成非参数化,如List<E>擦除成Lsit
2、参数化的数组被擦除成非参数化的数组,如List<E>[]擦除成List[]
3、参数类型E,没有上限的情况下,擦除成Object
4、所有约束参数,如<? extends E>擦除成E
5、多个约束的情况下,擦除成第一个,如<T extends Object & E>擦除成Object


七、擦除产生的问题

1、不管实际的具体类型是什么,泛型类是被它的所有实例所共享的。在编译时ArrayList<Integer>ArrayList<String>是两种不同的类型,但是运行时只有一个ArrayList会被加载。
所以要求所有静态成员不能够是泛型

这里写图片描述

2、重载冲突

public void method(T t){
        \\do something
    }
public void method(Object t){
        \\do something
    }

报错:Method method(Object) has the same erasure method(Object) as another method in type Test1

是因为参数T被擦除成了Object,导致这两个方法的特征签名变得一模一样

3、接口冲突

class Test2 implements Comparable<String>, Comparable<Integer>{
    //错误的
}

这里Comparable<String>, Comparable<Integer>都是泛型接口,被擦除成Comparable。
如果要实现多个泛型接口,只能实现具有不同擦除效果的接口


八、擦除对泛型的限制
由于泛型类型在运行时被擦除, 对于如何使用泛型类型有一些限制

1、if(obj instanceof T);

2、不能使用new E()
例如

这里写图片描述
运行时泛型类型是不可用的

3、不能使用new E[]

4、在静态的环境下不允许类的参数是泛型

5、异常类不能是泛型
假如定义一个泛型的异常类

class MyException<T> extends Exception{

}

捕获异常的时候
这里写图片描述

因为运行时类型信息是不出现的

解除擦除带来的问题

1.解决instanceof

先理解一下什么是Class<T>

使用泛型, Class.newInstance() 方法具有一个更加特定的返回类型:

class Class<T> {
     T newInstance();
} 

T指的是由此 Class 对象建模的类的类型。例如,Integer.class 的类型是 Class<Integer>。如果将被建模的类未知,则使用 Class<?>

创建实例:调用方法 Class.forName() 或者使用类常量X.class。Class.forName() 被定义为返回 Class。另一方面,类常量 X.class 被定义为具有类型 Class,所以 String.class 是Class 类型的。

现在来说明如何利用Class<T>解决instanceof的问题

Class<T> type; 
if ( type.isInstance(obj) ) {
   //...
}

原问题连接:http://stackoverflow.com/questions/5734720/java-generics-obj-instanceof-t

举个例子:

class A{}
class B extends A{}

public class Test<T> {
    private Class<T> type;
    public Test(Class<T> type){
        this.type = type;
    }

    public boolean compareTo(Object obj){
        return type.isInstance(obj);
    }

    public static void main(String[] args) {
        Test<A> test = new Test<A>(A.class);
        System.out.println(test.compareTo(new A()));
        System.out.println(test.compareTo(new B()));
    }
}

结果:

true
true

二、解决泛型数组

最简单的方式是通过Array.newInstance(Class<t>type,int size)的方式来创建数组。

public class Test<T> {  
    private Class<T> type;  

    public Test(Class<T> type) {  
        this.type = type;  
    }  

    @SuppressWarnings("unchecked")  
    T[] createArray(int size) {  
        return (T[]) Array.newInstance(type, size);  
    }  


    public static void main(String[] args) {  
        Test<String> test = new Test<String>(String.class);  
        String[] list = test.createArray(10);
    }  

}  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值