《Java核心技术》第八章 泛型程序设计 学习笔记

第八章 泛型程序设计

从Java程序设计语言1.0版发布以来,变化最大的部分就是泛型。

泛型正是我们需要的程序设计手段。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用

8.1 为什么要使用泛型程序设计

泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。

8.1.1 类型参数的好处

泛型提供了一个更好的解决方案:类型参数(typeparameters)。ArrayList类有一个类型参数用来指示元素的类型:

ArrayList<String> files = new ArrayList<>();

省略的类型可以从变量的类型推断得出。
类型参数的魅力在于:使得程序具有更好的可读性和安全性。

8.2 定义简单泛型类

一个泛型类(generic class)就是具有一个或多个类型变量的类。

public class Pair<T> {
    
    
    private T first;
    private T second;
    public Pair()
    {
    
    
        first = null;
        second = null;
    }
    public Pair(T first, T second)
    {
    
    
        this.first = first;
        this.second = second;
    }
    public T getFirst(){
    
    
        return first;
    }
    public T getSecond(){
    
    
        return second;
    }
}

Pair类引入了一个类型变量T,用尖括号(< >)括起来,并放在类名的后面。泛型类可以有多个类型变量。
换句话说,泛型类可看作普通类的工厂
下面的测试方法对泛型类进行测试

public class PairTest1 {
    
    
    public static void main(String[] args) {
    
    
        String[] words = {
    
    "ff", "fdf", "GTR"};
        Pair<String> mm = ArrayAlg.minmax(words);
        System.out.println("min = " + mm.getFirst());
        System.out.println("max = " + mm.getSecond());
    }
}

class ArrayAlg
{
    
    
    public static Pair<String> minmax(String[] a)
    {
    
    
        if (a == null || a.length == 0)
            return null;
        String min = a[0];
        String max = a[0];
        for (int i = 0; i < a.length; i++) {
    
    
            if (min.compareTo(a[i]) > 0) min = a[i];
            if (max.compareTo(a[i]) < 0) max = a[i];
        }
        return new Pair<>(min, max);
    }
}

8.3 泛型方法

前面已经介绍了如何定义一个泛型类。实际上,还可以定义一个带有类型参数的简单方法。

这个方法是在普通类中定义的,而不是在泛型类中定义的。然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。
注意,类型变量放在修饰符(这里是public static)的后面,返回类型的前面。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:

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

// 调用
String middle = ArrayAlg.<String>getMiddle("John", "Q", "DGG");

8.5 泛型代码和虚拟机

虚拟机没有泛型类型对象——所有对象都属于普通类。
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object)。
例如,Pair的原始类型如下所示:

public class Pair {
    
    
    private Object first;
    private Object  second;
    public Pair()
    {
    
    
        first = null;
        second = null;
    }
    public Pair(Object first, Object second)
    {
    
    
        this.first = first;
        this.second = second;
    }
    public Object getFirst(){
    
    
        return first;
    }
    public Object getSecond(){
    
    
        return second;
    }
}

因为T是一个无限定的变量,所以直接用Object替换。结果是一个普通的类,就好像泛型引入Java语言之前已经实现的那样。

8.6 约束与局限性

不能用基本类型实例化类型参数
因此,没有Pair,只有Pair。当然,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double值。

运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。

不能实例化类型变量
不能使用像new T(…), new T[…]或T.class这样的表达式中的类型变量。

不能构造泛型数组
就像不能实例化一个泛型实例一样,也不能实例化数组。

8.8 通配符类型

8.8.1 通配符概念

通配符类型中,允许类型参数变化。例如,通配符类型

Pair <? extends Employee>

表示任何泛型Pair类型,它的类型参数是Employee的子类,如Pair,但不是Pair。

8.8.2 通配符的超类型限定

通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超类型限定(supertype bound)
下面的方法将可以接受任何适当的Pair

public static void mimm(Manager[] a, Pair<? super Manager> result)
{
    
    
        
}

8.9 反射和泛型

反射允许你在运行时分析任意的对象。如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息,因为它们会被擦除。
在下面的小节中,可以了解利用反射可以获得泛型类的什么信息。

8.9.1 泛型Class类

现在,Class类是泛型的。例如,String.class实际上是一个Class类的对象(事实上,是唯一的对象)。

类型参数十分有用,这是因为它允许Class方法的返回类型更加具有针对性。下面Class中的方法就使用了类型参数:

newInstance方法返回一个实例,这个实例所属的类由默认的构造器获得。它的返回类型目前被声明为T,其类型与Class描述的类相同,这样就免除了类型转换。如果给定的类型确实是T的一个子类型,cast方法就会返回一个现在声明为类型T的对象,否则,抛出一个BadCastException异常。

猜你喜欢

转载自blog.csdn.net/qq_17677907/article/details/112469449