细说 Java 泛型及其应用

引出泛型

咱们经过以下的示例,引出为何泛型的概念。java

public class Test {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("abc");
        list.add(2);

        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // error
            System.out.println("name:" + name);
        }
    }
}
复制代码

当获取列表中的第二个元素时,会报错,java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。这是常见的类型转换错误。程序员

当咱们将元素放入到列表中,并无使用指定的类型,在取出元素时使用的是默认的 Object 类型。所以很容易出现类型转换的异常。数组

咱们想要实现的结果是,集合可以记住集合内元素各种型,且可以达到只要编译时不出现问题,运行时就不会出现 java.lang.ClassCastException 异常。泛型恰好能知足咱们的需求。安全

什么是泛型?

泛型,即参数化类型。一提到参数,最熟悉的就是定义方法时有形参,而后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,相似于方法中的变量参数,此时类型也定义成参数形式(能够称之为类型形参),而后在使用/调用时传入具体的类型(类型实参)。bash

泛型的本质是为了参数化类型,即在不建立新的类型的状况下,经过泛型指定的不一样类型来控制形参具体限制的类型。在泛型使用过程当中,操做的数据类型被指定为一个参数,这种参数类型能够用在类、接口和方法中,分别被称为泛型类、泛型接口和泛型方法。微信

泛型的特色

Java 语言中引入泛型是一个较大的功能加强。不只语言、类型系统和编译器有了较大的变化,已支持泛型,并且类库也进行了大翻修,因此许多重要的类,好比集合框架,都已经成为泛型化的了。这带来了不少好处:app

  1. 类型安全。 泛型的主要目标是提升 Java 程序的类型安全。经过知道使用泛型定义的变量的类型限制,编译器能够在一个高得多的程度上验证类型假设。
  2. 消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,而且减小了出错机会。
  3. 潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。

命名类型参数

推荐的命名约定是使用大写的单个字母名称做为类型参数。对于常见的泛型模式,推荐的名称是:框架

  • K:键,好比映射的键
  • V:值,好比 List 和 Set 的内容,或者 Map 中的值
  • E:元素
  • T:泛型
public class Generic<T> { 
    //key的类型为T 
    private T key;

    public Generic(T key) { 
    	//泛型构造方法形参key的类型也为T
        this.key = key;
    }

    public T getKey() { 
    	//泛型方法getKey的返回值类型为T
       return key;
    }
}
复制代码

如上定义了一个普通的泛型类,成员变量的类型为 T,T的类型由外部指定。泛型方法和泛型构造函数一样如此。函数

Generic<Integer> genericInteger = new Generic<Integer>(123456); //1

Generic<String> genericString = new Generic<String>("key_vlaue"); // 2

System.out.println("key is " + genericInteger.getKey());
System.out.println("key is " + genericString.getKey());
复制代码

泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。传入的实参类型需与泛型的类型参数类型相同,即为Integer/String。性能

如上所述,定义的泛型类,就必定要传入泛型类型实参么?

并非这样,在使用泛型的时候若是传入泛型实参,则会根据传入的泛型实参作相应的限制,此时泛型才会起到本应起到的限制做用。若是不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型能够为任何的类型。

Generic genericString = new Generic("111111");
Generic genericInteger = new Generic(4444);

System.out.println("key is " + genericString.getKey());
System.out.println("key is " + genericInteger.getKey());
复制代码

如上的代码片断,将会输出以下的结果:

key is 111111
key is 4444
复制代码

在不传入泛型类型实参的状况下,泛型类中使用的泛型防范或成员变量能够为 Integer 或 String 等等其余任意类型。不过须要注意的是,泛型的类型参数只能是类类型,不能是简单类型。且不能对确切的泛型类型使用 instanceof 操做。对于不一样传入的类型实参,生成的相应对象实例的类型是否是同样的呢?具体看以下的示例:

public class GenericTest {

    public static void main(String[] args) {

        Generic<Integer> name = new Box<String>("111111");
        Generic<String> age = new Box<Integer>(712);

        System.out.println("name class:" + name.getClass());  
        System.out.println("age class:" + age.getClass()); 
        System.out.println(name.getClass() == age.getClass());    // true
    }

}
复制代码

由输出结构可知,在使用泛型类时,虽然传入了不一样的泛型实参,但并无真正意义上生成不一样的类型,传入不一样泛型实参的泛型类在内存上只有一个,即仍是原来的最基本的类型(本例中为 Generic),固然在逻辑上咱们能够理解成多个不一样的泛型类型。

究其缘由,在于 Java 中的泛型这一律念提出的目的,其只是做用于代码编译阶段。在编译过程当中,对于正确检验泛型结果后,会将泛型的相关信息擦除。也就是说,成功编译事后的 class 文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

泛型类型在逻辑上看以当作是多个不一样的类型,实际上都是相同的基本类型。

通配符

Ingeter 是 Number 的一个子类,同时 Generic<Ingeter>Generic<Number> 其实是相同的一种基本类型。那么问题来了,在使用 Generic<Number> 做为形参的方法中,可否使用Generic<Ingeter> 的实例传入呢?在逻辑上相似于 Generic<Number>Generic<Ingeter> 是否能够当作具备父子关系的泛型类型呢?下面咱们经过定义一个方法来验证。

public void show(Generic<Number> obj) {
    System.out.println("key value is " + obj.getKey());
}
复制代码

进行以下的调用:

Generic<Integer> genericInteger = new Generic<Integer>(123);

show(genericInteger);  //error Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
复制代码

经过提示信息咱们能够看到 Generic<Integer> 不能被看做为 Generic<Number> 的子类。由此能够看出:同一种泛型能够对应多个版本(由于参数类型是不肯定的),不一样版本的泛型类实例是不兼容的。

咱们不能所以定义一个 show(Generic<Integer> obj)来处理,所以咱们须要一个在逻辑上能够表示同时是Generic和Generic父类的引用类型。由此类型通配符应运而生。

T、K、V、E 等泛型字母为有类型,类型参数赋予具体的值。除了有类型,还能够用通配符来表述类型, 未知类型,类型参数赋予不肯定值,任意类型只能用在声明类型、方法参数上,不能用在定义泛型类上。将方法改写成以下:

public void show(Generic<?> obj) {
    System.out.println("key value is " + obj.getKey());
}
复制代码

此处 ? 是类型实参,而不是类型形参。即和 Number、String、Integer 同样都是实际的类型,能够把 当作全部类型的父类,是一种真实的类型。能够解决当具体类型不肯定的时候,这个通配符就是 ?;当操做类型时,不须要使用类型的具体功能时,只使用 Object 类中的功能。那么能够用 ? 通配符来表未知类型。

泛型上下边界

在使用泛型的时候,咱们还能够为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。为泛型添加上边界,即传入的类型实参必须是指定类型的子类型。

public void show(Generic<? extends Number> obj) {
    System.out.println("key value is " + obj.getKey());
}
复制代码

咱们在泛型方法的入参限定参数类型为 Number 的子类。

Generic<String> genericString = new Generic<String>("11111");
Generic<Integer> genericInteger = new Generic<Integer>(2222);


showKeyValue1(genericString); // error
showKeyValue1(genericInteger);

复制代码

当咱们的入参为 String 类型时,编译报错,由于 String 类型并非 Number 类型的子类。

类型通配符上限经过形如 Generic<? extends Number> 形式定义;相对应的,类型通配符下限为Generic<? super Number>形式,其含义与类型通配符上限正好相反,在此不做过多阐述。

泛型数组

在 java 中是不能建立一个确切的泛型类型的数组的,即:

List<String>[] ls = new ArrayList<String>[10];  
复制代码

如上会编译报错,而使用通配符建立泛型数组是能够的:

List<?>[] ls = new ArrayList<?>[10]; 

//List<String>[] ls = new ArrayList[10];
复制代码

JDK1.7 对泛型的简化,因此另外一种声明也是能够的。

因为JVM泛型的擦除机制,在运行时 JVM 是不知道泛型信息的。泛型数组实际的运行时对象数组只能是原始类型( T[]为Object[],Pair[]为Pair[] ),而实际的运行时数组对象多是T类型( 虽然运行时会擦除成原始类型 )。成功建立泛型数组的惟一方式就是建立一个被擦出类型的新数组,而后对其转型。

public class GenericArray<T> {
    private Object[] array;  //维护Object[]类型数组
    @SupperessWarning("unchecked")
    public GenericArray(int v) {
        array = new Object[v];
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { 
    	return (T)array[index]; 
    } //数组对象出口强转
    public T[] rep() { return (T[])array; } //运行时不管怎样都是Object[]类型 
    public static void main (String[] args){
        GenericArray<Integer> ga = new GenericArray<Integer>(10);
        // Integer[] ia = ga.rep(); //依旧ClassCastException
        Object[] oa = ga.rep(); //只能返回对象数组类型为Object[]
        ga.put(0, 11);
        System.out.println(ga.get(0)); // 11
    }
}
复制代码

在运行时,数组对象的出口作转型输出,入口方法在编译期已实现类型安全,因此出口方法能够放心强制类型转换,保证成功。

小结

本文主要讲了 Java 泛型的相关概念和应用。泛型使编译器能够在编译期间对类型进行检查以提升类型安全,减小运行时因为对象类型不匹配引起的异常。由泛型的诞生介绍相关的概念,在保证代码质量的状况下,如何使用泛型去简化开发。

订阅最新文章,欢迎关注个人公众号

微信公众号

参考

java 泛型详解

相关文章
相关标签/搜索