跳转到内容

泛型

100% developed
来自 Wikibooks,开放世界中的开放书籍

导航 类和对象 主题:v  d  e )

Java 是一种强类型语言,因此类中的字段可以这样类型化

Computer code 代码清单 4.34:Repository.java
public class Repository {

   public Integer item;

   public Integer getItem() {
      return item;
   }

   public void setItem(Integer newItem) {
      item = newItem;
   }
}

这确保了只有 Integer 对象可以放入字段中,并且在运行时不会发生 ClassCastException,只会发生编译时错误。不幸的是,它只能用于 Integer 对象。如果你想在另一个使用 String 的上下文中使用相同的类,你必须像这样泛化类型

Computer code 代码清单 4.35:Repository.java
public class Repository {

   public Object item;

   public Object getItem() {
      return item;
   }

   public void setItem(Integer newItem) {
      item = newItem;
   }

   public void setItem(String newItem) {
      item = newItem;
   }
}

但是你将在运行时再次遇到 ClassCastException,并且你不能轻松地使用你的字段。解决方案是使用 泛型

泛型类

[编辑 | 编辑源代码]

泛型类不会硬编码字段、返回值或参数的类型。该类只表示对于给定的对象实例,泛型类型应该相同。泛型类型未在类定义中指定。它是在对象实例化期间指定的。这允许泛型类型在从一个实例到另一个实例时不同。因此,我们应该这样编写我们的类

Computer code 代码清单 4.36:Repository.java
public class Repository<T> {

   public T item;

   public T getItem() {
      return item;
   }

   public void setItem(T newItem) {
      item = newItem;
   }
}

这里,泛型类型是在类名之后定义的。可以选择任何新的标识符。这里,我们选择了T,这是最常见的选择。实际类型是在对象实例化时定义的

Example 代码节 4.35:实例化。
Repository<Integer> arithmeticRepository = new Repository<Integer>();
arithmeticRepository.setItem(new Integer(1));
Integer number = arithmeticRepository.getItem();

Repository<String> textualRepository = new Repository<String>();
textualRepository.setItem("Hello!");
String message = textualRepository.getItem();

虽然每个对象实例都有自己的类型,但每个对象实例仍然是强类型的

Warning 代码节 4.36:编译错误。
Repository<Integer> arithmeticRepository = new Repository<Integer>();
arithmeticRepository.setItem("Hello!");

一个类可以定义任意数量的泛型类型。为每个泛型类型选择一个不同的标识符,并用逗号隔开它们

Computer code 代码清单 4.37:Repository.java
public class Repository<T, U> {

   public T item;

   public U anotherItem;

   public T getItem() {
      return item;
   }

   public void setItem(T newItem) {
      item = newItem;
   }

   public U getAnotherItem() {
      return anotherItem;
   }

   public void setAnotherItem(U newItem) {
      anotherItem = newItem;
   }
}

当使用泛型定义的类型(例如,Collection<T>)不使用泛型(例如,Collection)时,称为原始类型

泛型方法

[编辑 | 编辑源代码]

泛型类型可以仅为方法定义

Example 代码节 4.37:泛型方法。
public <D> D assign(Collection<D> generic, D obj) {
  generic.add(obj);
  return obj;
}

这里,在方法声明的开头选择了一个新的标识符(D)。该类型特定于方法调用,并且可以在同一对象实例中使用不同的类型

Example 代码节 4.38:泛型方法调用。
Collection<Integer> numbers = new ArrayList<Integer>();
Integer number = assign(numbers, new Integer(1));
Collection<String> texts = new ArrayList<String>();
String text = assign(texts, "Store it.");

实际类型将由方法参数的类型定义。因此,泛型类型不能仅为返回值定义,因为它不会被解析。有关解决方案,请参见 Class<T> 部分。

测试你的知识

问题 4.8:考虑以下类。

Computer code 问题 4.8:Question8.java
public class Question8<T> {
  public T item;
 
  public T getItem() {
    return item;
  }
 
  public void setItem(T newItem) {
    item = newItem;
  }

  public static void main(String[] args) {
    Question8<String> aQuestion = new Question8<String>();
    aQuestion.setItem("Open your mind.");
    aQuestion.display(aQuestion.getItem());
  }

  public void display(String parameter) {
    System.out.println("Here is the text: " + parameter);
  }

  public void display(Integer parameter) {
    System.out.println("Here is the number: " + parameter);
  }

  public void display(Object parameter) {
    System.out.println("Here is the object: " + parameter);
  }
}

控制台上将显示什么?

答案
Standard input or output 答案 4.8 的控制台
Here is the text: Open your mind.

aQuestion.getItem() 的类型为字符串。

通配符类型

[编辑 | 编辑源代码]

正如我们上面所看到的,泛型给人的印象是,每个不同的类型参数都会创建一个新的容器类型。我们还看到,除了正常的类型检查之外,当我们分配泛型变量时,类型参数也必须匹配。在某些情况下,这限制太严格。如果我们想放松这种额外的检查怎么办?如果我们想定义一个可以保存任何泛型集合的集合变量,无论它保存的类型参数是什么?通配符类型用字符 <?> 表示,发音为 未知任意类型。任意类型也可以用 <? extends Object> 表示。任意类型包括接口,不仅仅是类。所以现在我们可以定义一个元素类型与任何东西匹配的集合。见下文

Example 代码节 4.39:通配符类型。
Collection<?> collUnknown;

上界通配符

[编辑 | 编辑源代码]

你可以对可以使用类的类型进行指定限制。例如,<? extends ClassName> 仅允许 ClassName 类或子类的对象。例如,要创建一个只能包含“可序列化”对象的集合,请指定

Example 代码节 4.40:可序列化子对象的集合。
Collection<String> textColl = new ArrayList<String>();

Collection<? extends Serializable> serColl = textColl;

上面的代码是有效的,因为 String 类是可序列化的。使用不可序列化的类会导致编译错误。添加的项目可以作为 Serializable 对象检索。你可以调用 Serializable 接口的方法或将其转换为 String。以下集合只能包含扩展 Animal 类的对象。

Computer code 代码清单 4.38:Dog.java
class Dog extends Animal {
}
Example 代码节 4.41:子类的示例。
// Create "Animal Collection" variable
Collection<? extends Animal> animalColl = new ArrayList<Dog>();

下界通配符

[编辑 | 编辑源代码]

<? super ClassName> 指定对可以使用类的类型进行指定限制。例如,要声明一个可以比较 Dogs 的 Comparator,请使用

Example 代码节 4.42:超类。
Comparator<? super Dog> myComparator;

现在假设你定义了一个可以比较 Animals 的比较器

Example 代码节 4.43:Comparator。
class AnimalComparator implements Comparator<Animal> {
  int compare(Animal a, Animal b) {
   //...
  }
}

由于 DogsAnimals,因此你也可以使用此比较器来比较 Dogs。任何 Dog 超类的比较器也可以比较 Dog;但是,任何严格子类的比较器都不能。

Example 代码节 4.44:泛型比较器。
Comparator<Animal> myAnimalComparator = new AnimalComparator();

static int compareTwoDogs(Comparator<? super Dog> comp, Dog dog1, Dog dog2) {
  return comp.compare(dog1, dog2);
}

上面的代码是有效的,因为 Animal 类是 Dog 类的超类型。使用不是超类型的类会导致编译错误。

无界通配符

[编辑 | 编辑源代码]

与原始类型(即没有泛型)相比,无界通配符(即 <?>)的优势在于明确表示参数化类型未知,而不是任何类型。这样,所有暗示知道类型的操作都被禁止,以避免不安全的操作。考虑以下代码

Example 代码部分 4.45:不安全操作。
public void addAtBottom(Collection anyCollection) {
  anyCollection.add(new Integer(1));
}

此代码将编译,但如果集合只包含字符串,则此代码可能会破坏集合

Example 代码部分 4.46:集合损坏。
List<String> col = new ArrayList<String>();
addAtBottom(col);
col.get(0).endsWith(".");
Standard input or output 代码部分 4.46 的控制台
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer incompatible with java.lang.String
at Example.main(Example.java:17)

如果 addAtBottom(Collection) 方法使用无界通配符定义,则可以避免这种情况:addAtBottom(Collection<?>)。使用此签名,无法编译依赖于参数化类型的代码。只能调用集合的独立方法(clear()isEmpty()iterator()remove(Object o)size() 等)。例如,addAtBottom(Collection<?>) 可以包含以下代码

Example 代码部分 4.47:安全操作。
public void addAtBottom(Collection<?> anyCollection) {
   Iterator<?> iterator = anyCollection.iterator();
   while (iterator.hasNext()) {
      System.out.print(iterator.next());
   }
}

从 Java 1.5 开始,java.lang.Class 类是泛型。它是一个有趣的例子,说明如何将泛型用于容器类以外的其他用途。例如,String.class 的类型是 Class<String>,Serializable.class 的类型是 Class<Serializable>。这可用于提高反射代码的类型安全性。特别是,由于 Class 中的 newInstance() 方法现在返回 T,因此在反射地创建对象时,您可以获得更精确的类型。现在,我们可以使用 newInstance() 方法返回具有精确类型的全新对象,而无需进行强制转换。使用泛型的示例

Example 代码部分 4.48:自动强制转换。
Customer cust = Utility.createAnyObject(Customer.class);  // No casting
...
public static <T> T createAnyObject(Class<T> cls) {
    T ret = null;
    try {
        ret = cls.newInstance();
    } catch (Exception e) {
        // Exception Handling
    }
    return ret;
}

没有泛型的相同代码

Example 代码部分 4.49:以前版本。
Customer cust = (Customer) Utility.createAnyObject(Customer.class);  // Casting is needed
...
public static Object createAnyObject(Class cls) {
    Object ret = null;
    try {
        ret = cls.newInstance();
    } catch (Exception e) {
        // Exception Handling
    }
    return ret;
}

Java 长期以来一直因需要在将元素从“容器/集合”类中取出时显式类型转换而受到批评。没有办法强制执行“集合”类只包含一种类型的对象(例如,在编译时禁止将 Integer 对象添加到应该只包含 StringCollection 中)。从 Java 1.5 开始,这成为可能。在 Java 演变的前几年,Java 没有真正的竞争对手。微软 C# 的出现改变了这一局面。Java 泛型更适合与 C# 竞争。其他语言中也存在类似于 Java 泛型的构造,有关详细信息,请参阅 泛型编程。泛型在 Java 语言语法中是在 1.5 版本中添加的。这意味着使用泛型的代码将无法在 Java 1.4 及更低版本中编译。泛型的使用是可选的。为了与预泛型代码向后兼容,可以使用没有泛型类型规范(<T>)的泛型类。在这种情况下,当您从泛型对象检索对象引用时,您将必须手动将其从类型 Object 强制转换为正确的类型。

C++ 程序员的注意事项

[编辑 | 编辑源代码]

Java 泛型类似于 C++ 模板,因为它们都是出于相同的原因添加的。Java 泛型和 C++ 模板的语法也类似。但是,也存在一些差异。C++ 模板可以看作是一种宏,因为会为每个引用的泛型类型生成新的代码副本。所有模板的额外代码都在编译时生成。相反,Java 泛型是内置在语言中的。相同的代码用于每个泛型类型。例如

Example 代码部分 4.50:Java 泛型。
Collection<String>  collString  = new ArrayList<String>();
Collection<Integer> collInteger = new ArrayList<Integer>();

这两个对象在运行时显示为相同类型(都是 ArrayList)。泛型类型信息在编译期间被擦除(类型擦除)。例如

Example 代码部分 4.51:类型擦除。
public <T> void method(T argument) {
  T variable;
  
}

通过擦除转换为

Example 代码部分 4.52:转换。
public void method(Object argument) {
  Object variable;
  
}
测试你的知识

问题 4.9:考虑以下类。

Computer code 问题 4.9:Question9.java
import java.util.ArrayList;
import java.util.Collection;

public class Question9 {
  public static void main(String[] args) {
    Collection<String> collection1 = new ArrayList<String>();
    Collection<? extends Object> collection2 = new ArrayList<String>();
    Collection<? extends String> collection3 = new ArrayList<String>();
    Collection<? extends String> collection4 = new ArrayList<Object>();
    Collection<? super Object> collection5 = new ArrayList<String>();
    Collection<? super Object> collection6 = new ArrayList<Object>();
    Collection<?> collection7 = new ArrayList<String>();
    Collection<? extends Object> collection8 = new ArrayList<?>();
    Collection<? extends Object> collection9 = new ArrayList<Object>();
    Collection<? extends Integer> collection10 = new ArrayList<String>();
    Collection<String> collection11 = new ArrayList<? extends String>();
    Collection collection12 = new ArrayList<String>();
  }
}

哪些行会生成编译错误?

答案
Computer code 答案 4.9:Answer9.java
import java.util.ArrayList;
import java.util.Collection;

public class Answer9 {
  public static void main(String[] args) {
    Collection<String> collection1 = new ArrayList<String>();
    Collection<? extends Object> collection2 = new ArrayList<String>();
    Collection<? extends String> collection3 = new ArrayList<String>();
    Collection<? extends String> collection4 = new ArrayList<Object>();
    Collection<? super Object> collection5 = new ArrayList<String>();
    Collection<? super Object> collection6 = new ArrayList<Object>();
    Collection<?> collection7 = new ArrayList<String>();
    Collection<? extends Object> collection8 = new ArrayList<?>();
    Collection<? extends Object> collection9 = new ArrayList<Object>();
    Collection<? extends Integer> collection10 = new ArrayList<String>();
    Collection<String> collection11 = new ArrayList<? extends String>();
    Collection collection12 = new ArrayList<String>();
  }
}
  • 第 9 行:Object 不扩展 String
  • 第 10 行:String 不是 Object 的超类。
  • 第 13 行:无法实例化 ArrayList<?>
  • 第 15 行:Integer 不扩展 String
  • 第 16 行:无法实例化 ArrayList<? extends String>



华夏公益教科书