Java泛型的基本定义和阐述

版权声明:本文为博主原创文章,如需转载请标明出去。 https://blog.csdn.net/sujin_/article/details/81046713

泛型程序设计分为3个能力级别。基本级别是,仅仅使用泛型类型,比如典型的ArrayList这样的集合,不需要考虑它们的工作方式和原因。我们大多数的程序员都停留在这一级别上,直到出现了问题。当不同的泛型类混合在一起时,或是在与对类型参数一无所知的遗留的代码进行衔接时,可能会看到含糊不清的错误消息。如果是这样的话,我们就需要学习Java泛型来系统地解决这些问题,而不是胡乱猜测。

定义简单的泛型类

Java 泛型是 JDK 5 中引入的一个新特性。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

一个简单的泛型类就是具有一个或多个类型变量的类。对于这样的类,我们只关注泛型,而不会为数据存储的细节烦恼。

package pk;

public class Pair<T> {
    
    private T first;
    private T second;

    public T getFirst() {
        return first;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public T getSecond() {
        return second;
    }
    public void setSecond(T second) {
        this.second = second;
    }
}

Pair类引入了一个类型变量T,用尖括号括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以在Pair类,其中第一个域和第二个域使用不同的类型:

public class Pair<T,U> {...}

类定义的类型变量可以指定方法返回类型以及域和局部变量的类型。例如:

 private T first;

注:在我们使用Java时大家有没有发现,使用变量E表示的是集合元素类型,K和V则表示关键字与值的类型,T(U和S)表示任意类型。其实,我们可以把泛型类看作是普通类的工厂。

泛型方法

泛型方法可以定义在普通类中,也可以定义在泛型类中。当调用一个泛型方法时,在方法名前的尖括号放入具体的类型:

package pk;

public class ArrayAlg {

    public static <T> T getMiddle(T... a){
        return a[a.length / 2];
    }

    String middle = ArrayAlg.getMiddle("John","Q","Public");
}

在大多数情况下,对于泛型方法的类型引用是没有问题的。不过偶尔,编译器会提示错误,比如说:

double middles = ArrayAlg.getMiddle(3.14,666,0);

我们可以通过错误,查看2个类的超类:Number和Comparable接口,其本身也就是泛型类型。在这种情况下,我们可以把所有参数类型写为double值。

类型变量的限定

有时候,类或方法需要对泛型变量加以约束。下面有一个经典的例子。我们要计算数组中最小的元素:

public static<T extends Comparable> T getMin(T[] a) {
        if(a == null && a.length == 0){
            return null;
        }
        T smallest = a[0];
        for(int i=1;i<a.length;i++){
            if(smallest.compareTo(a[i])>0) {
                smallest = a[i];
            }
        }
        return smallest;
    }

通过对类型变量T设置了限定,才能使用compareTo,在这里大家有没有发现为什么是使用extends而不是implements呢?毕竟Comparable是一个接口。

<T extends Comparable>

T是绑定类型的子类型。T绑定的类型可以是类,也可以是接口。至于为什么要选择extends的原因是因为它更接近子类的概念,一个类型变量或通配符可以有多个限定,例如:

<T extends Comparable & Serializable>

限定类型用&分隔,而逗号用来分隔类型变量。在Java继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表的第一个。

泛型代码和虚拟机

虚拟机中没有泛型类型对象,所有对象都属于普通类。

类型擦除

类型擦除可以理解为将泛型代码转换为普通代码。简单来说,类型参数只存在于编译期,在运行时,Java 的虚拟机 ( JVM ) 并不知道泛型的存在。无论什么时候创建一个泛型类型,都会自动提供一个相对应的原始类型。原始类型的名字就是删去类型参数的泛型类型名。擦除类型变量替换成限定类型(无限定类型用Object类型表示)。Pair<T>的原始类型如下:

package pk;

public class Pair {
    private Object first;
    private Object second;

    public Pair(Object first, Object second) {
        this.first = first;
        this.second = second;
    }

    public Object getFirst() {
        return first;
    }

    public void setFirst(Object first) {
        this.first = first;
    }

    public Object getSecond() {
        return second;
    }

    public void setSecond(Object second) {
        this.second = second;
    }
}

因为T是无限定的变量,用Object替换。如果指定了上限如 <T extends String> 则类型参数就被替换成类型上限。如果大家想深入了解类型擦除,可以看看这篇博客,我觉得写的挺不错的。

翻译泛型表达式

其实就是表达式前面自动加上强制类型转换而已。当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。例如,下面的代码示例:

Pair<Emploee> buddies = ...;
Emploee emploee = buddies.getFirst();

擦除getFirst的返回类型后将返回Object类型,编译器自动插入Emploee的强制类型转换。也就是说编译器把这个方法调用翻译成两条虚拟机命令:

  • 对原始方法Pair.getFirst方法的调用
  • 将返回的Object类型转换成Emploee类型

翻译泛型方法

类型擦除后,可能产生方法冲突的问题,但是Java编译器会帮你解决的。桥方法:在一个方法覆盖另外一个方法时就可以指定一个更严格的返回类型。例如:

package pk;

public class Emploee implements Cloneable {

//    @Override
//    protected Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }

    public  Emploee clone() throws CloneNotSupportedException {
        //...
        return null;
    }

}

现在Emploee类有两个克隆的方法:

Emploee.clone();
Object.clone();

合成的桥方法调用了新定义的方法。总而言之,我们需要注意Java泛型的转换:

  • 虚拟机中没有泛型,只有普通的类和方法。
  • 在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。
  • 在继承泛型类型的时候,桥方法被合成来保持多态。
  • 为保持类型的安全性,必要时插入强制类型转换。

调用遗留代码

就是允许泛型代码和遗留代码之间能够互相进行操作而已。

-------------------------------明天与意外你永远不知道谁先到,我们要做的就是好好的生活下去。-------------------------------

猜你喜欢

转载自blog.csdn.net/sujin_/article/details/81046713