Java 泛型

欢马劈雪     最近更新时间:2020-08-25 03:33:36

641

JDK 5(2004 年发布)中引入的泛型标志着 Java 语言的一次巨大进步。如果使用过 C++ 模板,会发现 Java 语言中的泛型与其很相似,但并非完全相同。如果未使用过 C++ 模板,不要担心:本节将概括介绍 Java 语言中的泛型。

什么是泛型?

JDK 5.0(2004 年发布)向 Java 语言中引入了 泛型类型泛型 )和关联的语法。基本上讲,一些当时熟悉的 JDK 类被替换为了等效的泛型。泛型是一种编译器机制,您可通过该机制获取通用的代码并 参数化(或 模板化 )剩余部分,从而以一种一般化方式创建(和使用)一些类型的实体(比如类或接口和方法)。这种编程方法被称为 泛型编程

泛型实战

要了解泛型有何作用,可考虑一个在 JDK 中存在已久的类示例:java.util.ArrayList,它是一个由数组支持的 ObjectList

清单 3 展示了如何实例化 java.util.ArrayList

清单 3. 实例化 ArrayList
ArrayList arrayList = new ArrayList();
arrayList.add("A String");
arrayList.add(new Integer(10));
arrayList.add("Another String");
// So far, so good.

可以看到,ArrayList 具有不同的形式:它包含两个 String 类型和一个 Integer 类型。在 JDK 5 之前,Java 语言对此行为没有任何约束,这导致了许多编码错误。举例而言,在清单 23 中,目前为止看起来一切正常。但要访问 ArrayList 中的元素怎么办,清单 4 尝试采用哪种方法?

清单 4. 尝试访问 ArrayList 中的元素
ArrayList arrayList = new ArrayList();
arrayList.add("A String");
arrayList.add(new Integer(10));
arrayList.add("Another String");
// So far, so good.
*processArrayList(arrayList);
*// In some later part of the code...
private void processArrayList(ArrayList theList) {
  for (int aa = 0; aa < theList.size(); aa++) {
    // At some point, this will fail...
    String s = (String)theList.get(aa);
  }
}

如果以前不知道 ArrayList 中包含的内容,就必须检查要访问的元素,看看您是否能处理其类型,否则可能遇到 ClassCastException

借助泛型,可指定将哪些类型的内容放入 ArrayList。清单 5 展示了如何执行该操作,以及如果尝试添加错误类型的对象(第 3 行)会发生什么情况。

清单 5. 第二次尝试使用泛型
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("A String");
arrayList.add(new Integer(10));// compiler error!
arrayList.add("Another String");
// So far, so good.
*processArrayList(arrayList);
*// In some later part of the code...
private void processArrayList(ArrayList<String> theList) {
  for (int aa = 0; aa < theList.size(); aa++) {
    // No cast necessary...
    String s = theList.get(aa);
  }
}

迭代泛型

泛型使用处理一些实体(比如 List)的特殊语法增强了 Java 语言,您通常可能希望逐个元素地处理这些实体。举例而言,如果想迭代 ArrayList,可以清单 5 中的代码重写为:

private void processArrayList(ArrayList<String> theList) {
  for (String s : theList) {
    String s = theList.get(aa);
  }
}

此语法适用于任何 Iterable (也就是实现了 Iterable 接口)的对象类型。

参数化的类

参数化的类对于集合非常有用,所以使用集合时可考虑使用这种类。考虑(真实的)List 接口,它表示一个有序的对象集合。在最常见的用例中,您向 List 中添加项,然后按索引或通过迭代 List 来访问这些项。

如果考虑参数化一个类,可考虑是否满足以下条件:

  • 一个核心类位于某种包装器的中心。也就是位于类中心的”东西”可能应用很广泛,并且其特性(例如属性)是相同的。
  • 相同的行为:无论类中心的”事务”是什么,您都会执行完全相同的操作。

根据这两个条件,显然集合满足要求:

  • 这个”事务”就是组成集合的类。
  • 操作(比如 addremovesizeclear )完全相同,无论集合由哪些对象组成都是如此。

一个参数化的 List

在泛型语法中,创建 List 的代码类似于:

List<E> listReference = new concreteListClass<E>();

E(代表元素)是我之前提到的”事务”。concreteListClass 是您正在实例化的 JDK 的类。该 JDK 包含多个 List<E> 实现,但您使用 ArrayList<E>。您可能看到的泛型类的另一种形式为 Class<T>,其中 T 代表类型。在 Java 代码中看到 E 时,它通常是指一个某种类型的集合。看到 T 时,它表示一个参数化的类。

所以,要创建一个由 java.lang.Integer 组成的 ArrayList,可以这么做:

List<Integer> listOfIntegers = new ArrayList<Integer>();

SimpleList`:一个参数化的类

现在假设您想创建自己的参数化类 SimpleList,该类包含 3 个方法:

  • add() 将一个元素添加到 SimpleList 的末尾。
  • size() 返回 SimpleList 中当前的元素数量。
  • clear() 完全清除 SimpleList 的内容。

清单 6 给出了参数化 SimpleList 的语法:

清单 6. 参数化 SimpleList
package com.nowjava.intro;
import java.util.ArrayList;
import java.util.List;
public class SimpleList<E> {
  private List<E> backingStore;
  public SimpleList() {
    backingStore = new ArrayList<E>();
  }
  public E add(E e) {
    if (backingStore.add(e))
    return e;
    else
    return null;
  }
  public int size() {
    return backingStore.size();
  }
  public void clear() {
    backingStore.clear();
  }
}

可使用任何 Object 子类来参数化 SimpleList。要创建并使用一个由 java.math.BigDecimal 对象组成的 SimpleList,可以这样做:

package com.nowjava.intro;
import java.math.BigDecimal;
import java.util.logging.Logger;
import org.junit.Test;
public class SimpleListTest {
  @Test
  public void testAdd() {
    Logger log = Logger.getLogger(SimpleListTest.class.getName());

    SimpleList<BigDecimal> sl = new SimpleList<>();
    sl.add(BigDecimal.ONE);
    log.info("SimpleList size is :" + sl.size());
    sl.add(BigDecimal.ZERO);
    log.info("SimpleList size is :" + sl.size());
    sl.clear();
    log.info("SimpleList size is :" + sl.size());
  }
}

而且会得到此输出:

Sep 20, 2015 10:24:33 AM com.nowjava.intro.SimpleListTest testAdd
INFO:SimpleList size is:1 Sep 20, 2015 10:24:33 AM com.nowjava.intro.SimpleListTest testAdd
INFO:SimpleList size is:2 Sep 20,
2015 10:24:33 AM com.nowjava.intro.SimpleListTest testAdd
INFO:SimpleList size is:0

参数化方法

有时,您可能不想参数化整个类,但只需要一两个方法。在本例中,创建一个 泛型方法 。考虑清单 27 中的示例,其中方法 formatArray 用于创建数组内容的字符串表示形式。

清单 7. 参数化 SimpleList
public class MyClass {
// Other possible stuff... ignore...
  public <E> String formatArray(E[] arrayToFormat) {
    StringBuilder sb = new StringBuilder();

    int index = 0;
    for (E element : arrayToFormat) {
      sb.append("Element ");
      sb.append(index++);
      sb.append(" => ");
      sb.append(element);
      sb.append('\n');
    }

    return sb.toString();
  }
// More possible stuff... ignore...
}

没有参数化 MyClass,您只将一个方法泛化,这个方法是您想要来创建适用于任何元素类型的一致字符串表示。

在实践中,您会发现自己使用参数化的类和接口的频率远高于方法,但现在您知道,如果需要,这个功能是可用的。

enum 类型

在 JDK 5 中,Java 语言新添了一种名为 enum 的数据类型。不要与 java.util.Enumeration 混淆,enum 表示一组与某个特定概念相关的常量对象,每个对象表示该集合中一个不同的常量值。将 enum 引入 Java 语言之前,必须按如下方式为一个概念(比如性别)定义一组常量值:

public class Person {
  public static final String MALE = "male";
  public static final String FEMALE = "female";
}

引用该常量值所需的代码可以像这样编写:

public void myMethod() {
  //...
  String genderMale = Person.MALE;
  //...
}

使用 enum 定义常量

使用 enum 类型让常量的定义更加正式,而且更强大。这是 Genderenum 定义:

展开阅读全文