JAVA泛型

泛型解释:
泛型的本质是参数化类型,通常用于输入参数、存储类型不确定的场景。相比于直接使用 Object 的好处是:编译期强类型检查、无需进行显式类型转换。
泛型概述:
Java 泛型(generics)是 JDK 5 中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在 JDK 5 中的新集合类框架中。对于泛型概念的引入,开发社区的观点是褒贬不一。从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误。而从不好的地方来说,为了保证与旧有版本的兼容性,Java 泛型的实现上存在着一些不够优雅的地方。当然这也是任何有历史的编程语言所需要承担的历史包袱。后续的版本更新会为早期的设计缺陷所累。
开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收 List 作为形式参数,那么如果尝试将一个 List 的对象作为实际参数传进去,却发现无法通过编译。虽然从直觉上来说,Object 是 String 的父类,这种类型转换应该是合理的。但是实际上这会产生隐含的类型转换问题,因此编译器直接就禁止这样的行为。本文试图对 Java 泛型做一个概括性的说明。

类型擦除:
Java 中的泛型是在编译器这个层次实现的,在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除 type erasure。

泛型的奇怪特性都与这个类型擦除的存在有关:

  • 泛型类并没有自己独有的 Class 类对象。比如并不存在 List.class 或是 List.class,而只有 List.class。
  • 静态变量是被泛型类的所有实例所共享的。对于声明为 MyClass 的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过 new MyClass 还是 new MyClass 创建的对象,都是共享一个静态变量。
  • 泛型的类型参数不能用在 Java 异常处理的 catch 语句中。因为异常处理是由 JVM 在运行时刻来进行的。由于类型信息被擦除,JVM 是无法区分两个异常类型 MyException 和 MyException 的。对于 JVM 来说,它们都是 MyException 类型的。也就无法执行与异常对应的 catch 语句。

上代码解释不用泛型List类型写入List报错

    public  void insertList(List<Object> list){
        for (Object obj: list) {
            System.out.println(obj);
        }
        list.add(12);
    }
    public void TestInsetListString(){
        List<String> strlist=new ArrayList<>();
        insertList(strlist);
    }
    编译提示异常:
    不兼容的类型: java.util.List<java.lang.String>无法转换为java.util.List<java.lang.Object>
通配符与上下界

在使用泛型类的时候,既可以指定一个具体的类型,如 List 就声明了具体的类型是 String;也可以用通配符? 来表示未知类型,如 List<?> 就声明了 List 中包含的元素类型是未知的。 通配符所代表的其实是一组类型,但具体的类型是未知的。List<?> 所声明的就是所有类型都是可以的。但是 List<?> 并不等同于 List。List 实际上确定了 List 中包含的是 Object 及其子类,在使用的时候都可以通过 Object 来进行引用。而 List<?> 则其中所包含的元素类型是不确定。其中可能包含的是 String,也可能是 Integer。如果它包含了 String 的话,往里面添加 Integer 类型的元素就是错误的。正因为类型未知,就不能通过 new ArrayList<?>() 的方法来创建一个新的 ArrayList 对象。因为编译器无法知道具体的类型是什么。但是对于 List<?> 中的元素确总是可以用 Object 来引用的,因为虽然类型未知,但肯定是 Object 及其子类

        public  void insertList(List<?> list){
        list.add(12);  编译异常
    }

试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。

因为对于 List<?> 中的元素只能用 Object 来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。 如 List<? extends Number> 说明 List 中可能包含的元素类型是 Number 及其子类。而 List<? super Number> 则说明 List 中包含的是 Number 及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。比如访问 List<? extends Number> 的时候,就可以使用 Number 类的 intValue 等方法。

类型系统

在 Java 中,大家比较熟悉的是通过继承机制而产生的类型体系结构。比如 String 继承自 Object。根据 Liskov 替换原则,子类是可以替换父类的。当需要 Object 类的引用的时候,如果传入一个 String 对象是没有任何问题的。但是反过来的话,即用父类的引用替换子类引用的时候,就需要进行强制类型转换。编译器并不能保证运行时刻这种转换一定是合法的。这种自动的子类替换父类的类型转换机制,对于数组也是适用的。 String[] 可以替换 Object[]。但是泛型的引入,对于这个类型系统产生了一定的影响。正如前面提到的 List 是不能替换掉 List 的。

引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。第一个指的是对于 List 和 List 这样的情况,类型参数 String 是继承自 Object 的。而第二种指的是 List 接口继承自 Collection 接口。对于这个类型系统,有如下的一些规则:

相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即 List 是 Collection 的子类型,List 可以替换 Collection。这种情况也适用于带有上下界的类型声明。
当泛型类的类型声明中使用了通配符的时候, 其子类型可以在两个维度上分别展开。如对 Collection<? extends Number> 来说,其子类型可以在 Collection 这个维度上展开,即 List<? extends Number> 和 Set<? extends Number> 等;也可以在 Number 这个层次上展开,即 Collection 和 Collection 等。如此循环下去,ArrayList 和 HashSet 等也都算是 Collection<? extends Number> 的子类型。
如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。
理解了上面的规则之后,就可以很容易的修正实例分析中给出的代码了。只需要把 List 改成 List<?> 即可。List 是 List<?> 的子类型,因此传递参数时不会发生错误。

重点:

public  void insertList(List<int> list){   int编译报错:Type argument cannot be of primitive type
        list.add(12);
    }

解释:泛型要求能包容的是对象类型,而基本类型在java里不属于对象。但是基本类型都有其包装类型,也就是对象类型。

JAVA使用:
封装分页,特殊对象处理等场景,
型的使用方式,可以在类、接口、方法中使用,分别简称之泛型类、泛型接口、泛型方法,并且通过泛型实现数据类型的任意化,即灵活、又有安全性,易于维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值