java 泛型

泛型
术语意思:是适用于许多的类型,即使代码可以应用于多种类型。
目的:希望类或者方法能够具备最广泛的表达能力,并且将错误检测移入到编译 期间。
核心概念:告诉编译器想使用什么类型,然后编译器帮你处理好一切细节。
举个例子:

void test(){
        List<User> listTest = new ArrayList<User>();
        listTest.add(new User());//正确
        listTest.add(new Dog());//错误  在编译期间就提示出来 所以细节由编译器处理
    }

定义带类型参数的类

public class Test<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
    public static void main(String[] args) {
        Test<String> t =new Test<String>();
        t.setT("test");//只能放String类型  不然就报错 
        System.out.println(t.getT());
    }
}

定义带类型参数的方法
泛型方法可以使得该方法能够独立于类而产生变化。一个基本原则导论:在能够使用泛型方法的时候尽量使用泛型方法,因为他可以让事情更加清楚明白。
要定义泛型方法,只需要将泛型参数列表置于方法返回值之前就好,例子:

public class Test  {
    public <T> void test(T x){
        System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
        Test t =new Test();
        t.test("输出 java.lang.String");
        t.test(1);//输出 java.lang.Integer
        t.test(1.1);//输出 java.lang.Double
        t.test(1.1f);//输出 java.lang.Float
        t.test(t);//输出 com.hlj.test.Test
        t.test('c');//输出 java.lang.Character
        t.test(true);//输出 java.lang.Boolean
    }
}

类型参数推断

Test<String> t =new Test<String>();

这样写重复了泛型参数列表,即写了两次,可以编写一个工具类,利用类型参数推断的机制,简化代码。

public class New {

    public static <K,V> Map<K,V> map(){
        return new HashMap<K, V>();
    }
    public static <K> Set<K> set(){
        return new HashSet<K>();
    }
    public static <K> List<K> list(){
        return new ArrayList<K>();
    }

    public static void main(String[] args) {
        List<String> list = New.list();//不用写两次<String>了
    }
}

注意:
类型推断只对赋值操作有效,其他时候不管用。如果将一个泛型方法的返回值作为参数传递给另一个方法,编译器是不会执行类型推断的。因为调用泛型后,其返回值被赋给一个Object类型的变量。

static void f(List<String> list){}
    public static void main(String[] args) {
        List<String> list = New.list();//不用写两次<String>了
        f(New.list());//编译失败 
}

编译失败的原因就是New.list()返回的是List 和List不相符,而前面的赋值操作可以是因为前面写了List.
可以写成这样New.list()就不会报错了,这个叫做显示的类型说明。

擦除
Java泛型是通过使用擦除来完成的,这意味着当你使用泛型的时候,任何具体的参数类型都会被擦除,占位符T(一般是T 随便写)将会用Object代替(如果有边界的话就是用边界代替,待会说)。
如之前的代码

public class Test<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}

擦除后

public class Test{
    private Object t;
    public Object getT() {
        return t;
    }
    public void setT(Object t) {
        this.t = t;
    }
}

由上面的可以得知,例如
List和List在运行时是相同的类型即 List.getClass()==List.getClass()。

擦除的原因
因为泛型是在JDK5才引入的,所以擦除的核心动机就是为了向后兼容,也就是说让使用泛型的客户端可以调用没有使用泛型的类库。既要保证当前代码的合法性又要保持之前的含义向后兼容,擦除是唯一可行的办法,让泛型成为伪泛型。
擦除的边界
首先看个例子:

public class Test<T>  {
    private T t;
    public Test(T t){
        this.t = t;
    }
    public void f(){
        t.f();//这里会报错
    }

public void setT(T t) {
        this.t = t;
}

    public static void main(String[] args) {
        Test2 t2 = new Test2();
        Test<Test2> t =new Test<Test2>(t2);
    }
}
class Test2{
    public void f(){};
}

Test的类型参数是Test2,并将Test2实例对象传递给了Test,并在Test中调用了Test2的方法,但是编译的时候不正确,因为擦除的原因,T被Object代替了,而Object却没有相应的方法,所以会报错。
*补充:
既然T被Object代替了,那为什么不能接受其他类型呢?因为编译器会事先检查(反射)能不能将其转化为Test2类型,如何可以则执行转换。否则将抛出ClassCastException异常。
而检查是针对对象的引用,即Test t 中的t,所以Test t =new Test(t2);这样写跟没写泛型一样。new Test只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用t来调用它的方法,比如说调用setT()方法。所以t引用能完成泛型类型的检查。*

解决刚刚报错的问题,Object没有相应的方法,那我们就想办法让JVM不完全擦除,只擦除到某个边界即可,而这个边界就是Test2,即用Test2带替T,写法为<T extends Test2>。
刚刚的例子改进

public class Test<T extends Test2>  {
    private T t;
    public Test(T t){
        this.t = t;
    }
    public void f(){
        t.f();//不报错啦 原因是T被Test2代替啦
    }

    public void setT(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        Test2 t2 = new Test2();
        Test<Test2> t =new Test<Test2>(t2);
    }
}
class Test2{
    public void f(){};
}

在定义类时候,类型参数改为就可以了。若有一个类Test3
继承了Test2,那么调用的时候也可以Test<Test3> t =new Test<Test3>();型参数是Test2的子类就可以了。
也可以是

Test<Test2> t =new Test<Test2>();
        t.setT(new Test3());

擦除的补偿
擦除丢失了代码的某些执行操作,比如调用方法,生成实例对象等等。也就是说任何在运行时需要知道确切类型信息的操作都不能实行。如:

T t = new T(); //编译出错

解决方法就是传递一个class对象给他,例子:

public class Test<T>  {

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

    public void f(Object obj) throws InstantiationException, IllegalAccessException{
        if(c.isInstance(obj)){
            System.out.println("我判断了C的确切类型");
        }else{
            System.out.println("我判断了C的确切类型,虽然不正确");
        }
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Test<Test2> test =new Test<Test2>(Test2.class);
        test.f(new Test2());
    }
}
class Test2{void f(){};}

<? extends T> 与 <? super T>
<? extends T>的?是T的某一种子类的意思,记住是一种,单一的一种,问题来了,由于连哪一种都不确定,带来了不确定性,所以是不可能通过 
add()来加入元素。
例子:的?是T的某一种子类的意思,记住是一种,单一的一种,问题来了,由于连哪一种都不确定,带来了不确定性,所以是不可能通过 
add()来加入元素。
例子:
public static void main(String[] args)  {
        List<? extends Fruit> list = new ArrayList<Apple>();
        list.add(new Apple());//编译出错
        list.add(new Fruit());//编译出错
        list.add(null);//正确
    }
class Fruit{}
class Apple extends Fruit{}

List类型现在是<? Extends Fruit>,可以读作“具有任何从Fruit继承的类型的列表”,但是并不意味着这个list可以持有任何类型的fruit。
但是如果调用了他的返回方法,比如get()就是安全的,比如:

List<? extends Fruit> list = Arrays.asList(new Apple());
    Fruit fruit =  list.get(0);

因为List中的类型都是fruit的子类,所以允许这么做。

虽然不能调用add(new Apple())方法,但是可以调用contains(new Apple),
indexOf(new Apple)等带有参数的方法,为什么呢?

list.contains(new Apple());//不报错
    list.indexOf(new Apple());//不报错

查看文档可以发现,add()方法接收的是一个具有泛型参数类型的参数,但是其他的方法接收的却是object类型的参数,由于指定了类型参数为

void test(List<? super Apple> list){
        list.add(new Apple());//正确
        list.add(new SmallApple());//正确
        list.add(new Fruit());//编译错误
}
class Fruit{}
class Apple extends Fruit{}
class SmallApple extends Apple{}

编译错误的原因:
首先理解<? Super T> 指的是new出来的实例list)或者赋值或者接收参数的时候,类型参数的边界是T的基类,

List<? super Apple> list = Arrays.asList(new Fruit());

并不是指可以持有T的基类。
由于Apple是下界,所以只能存放apple或者apple的子类,
Super不可用于的返回类型限定,能用于参数类型限定

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值