《Java 核心技术卷1 第10版》学习笔记 ------ 泛型【基础】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gulang03/article/details/88176807

泛型从Java SE 5.0 中开始出现,是 Java 程序设计语言从 1.0 版本发布以来,变化最大的部分。

使用泛型机制编写的程序代码要比那些杂乱地使用 Object 变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。特别的泛型对集合类尤其有用,例如,ArrayList 就是一个无处不在的集合类。

至少在表面上看来, 泛型很像 C++ 中的模板(template)。

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

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

8.1.1 类型参数的好处

在 Java 中增加范型类之前, 泛型程序设计是用继承实现的。ArrayList 类只维护一个 Object 引用的数组:

public class ArrayList    // before generic classes
{
    private Object[] elementData;
    
    ...
    public get Object get(int i){...}
    public get Object add(Object o){...}
}

这种方法有两个问题。

// 当获取一个值时必须进行强制类型转换。
ArrayList files = new ArrayList();
...
String filename = (String) files.get(0);

// 此外,这里没有错误检査。可以向数组列表中添加任何类的对象。
files,add(new File(". .."));

对于这个调用,编译和运行都不会出错。然而在其他地方,如果将 get 的结果强制类型转换为 String 类型, 就会产生一个错误。

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

// 泛型类使用类型参数,指明存储的数据类型
// 这使得代码具有更好的可读性。人们一看就知道这个数组列表中包含的是 String 对象。
ArrayList<String> files = new ArrayList<String>():

// Java SE 7 之后可以这样写, 略去后面的泛型说明
ArrayList<String> files = new ArrayList();

使用泛型指定明确类型之后,编译器可以更好的检测,从而降低出现类型转换异常的可能,故更加安全。

8.1.2 谁想成为泛型程序员

应用程序员【做实际应用开发的程序员,非专业造轮子】往往更喜欢使用现成的泛型代码,而不喜欢去设计开发自己的泛型代码。

实现一个泛型类并没有那么容易。对于类型参数,使用这段代码的程序员可能想要内置( plugin) 所有的类。他们希望在没有过多的限制以及混乱的错误消息的状态下, 做所有的事情。因此, 一个泛型程序员的任务就是预测出所用类的未来可能有的所有用途。【这个难度是有点大的】

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

8.2 定义简单泛型类

一个泛型类( generic class) 就是具有一个或多个类型变量的类。以一个 Pair 类为例:

// 类型变量 T 用“<>”,写在类名后面
public class Pair<T>
{
    private T first;
    private T second;

    public Pair() { first = null ; second = null ; }
    public PairfT first, T second) { 
        this.first = first; 
        this.second = second; 
    }

    public T getFirstO { return first; }
    public T getSecondO { return second; }

    public void setFirst(T newValue) { first = newValue; }
    public void setSecond(T newValue) { second = newValue; }
}

泛型类可以有多个类型变量。例如, 可以定义 Pair 类,其中 第一个域 和 第二个域 使用不同的类型:

public class Pair<T, U> { . . . }
// 用具体的类型替换类型变量就可以实例化泛型类型, 例如:
Pair<String>
// 可以将结果想象成带有构造器的普通类:
Pair<String>
Pair<String>(String, String)
// 和方法:
String getFirstO
String getSecond()
void setFirst(String)
void setSecond(String)

换句话说,泛型类可看作普通类的工厂。

8.3 泛型方法

实际上,还可以直接定义一个带有类型参数的简单方法。

class ArrayAlg
{
    // 定义在普通类中的泛型方法
    // 注意:类型变量修饰符放在方法修饰符后面,返回类型前面。
    // 当然这里的返回类型就是指定的泛型类型,参数也是.
    public static <T> T getMiddle(T... a)
    {
        return a[a.length / 2];
    }
}

调用泛型方法:

// 当调用一个泛型方法时,在方法名前的尖括号中放人具体的类型
String middle = ArrayAlg.<String>getMiddle("John", "Q.", "Public");

// 其实编译器可以根据实际传入参数,确定泛型类型,所以可以简写
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");

8.4 类型变量的限定(extends)

有时,类或方法需要对类型变量加以约束。未加限定的类型变量 T 相当于 Object,加上限定之后其表示范围就变小了。

// 这里限定了类型变量 T 只能是实现了 Comparable 接口的类
public static <T extends Comparable> T min(T[] a){...}

读者或许会感到奇怪—在此为什么使用关键字 extends 而不是 implements ? 毕竟,Comparable 是一个接口。下面的记法

<T extends BoundingType>
表示 T 应该是绑定类型的子类型 (subtype)T 和绑定类型可以是类, 也可以是接口。选择关键字 extends 的原因是更接近子类的概念, 并且 Java 的设计者也不打算在语言中再添加一个新的关键字(如 sub)。

一个类型变量或通配符可以有多个限定,例如:

<T extends Comparable & Serializable, U extends Response>

限定类型用 “&” 分隔,而逗号“,”用来分隔多个类型变量。

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

猜你喜欢

转载自blog.csdn.net/gulang03/article/details/88176807