泛型
导航 类和对象 主题: ) |
Java 是一种强类型语言,因此类中的字段可以这样类型化
代码清单 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
的上下文中使用相同的类,你必须像这样泛化类型
代码清单 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
,并且你不能轻松地使用你的字段。解决方案是使用 泛型。
泛型类不会硬编码字段、返回值或参数的类型。该类只表示对于给定的对象实例,泛型类型应该相同。泛型类型未在类定义中指定。它是在对象实例化期间指定的。这允许泛型类型在从一个实例到另一个实例时不同。因此,我们应该这样编写我们的类
代码清单 4.36:Repository.java
public class Repository<T> {
public T item;
public T getItem() {
return item;
}
public void setItem(T newItem) {
item = newItem;
}
}
|
这里,泛型类型是在类名之后定义的。可以选择任何新的标识符。这里,我们选择了T,这是最常见的选择。实际类型是在对象实例化时定义的
代码节 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();
|
虽然每个对象实例都有自己的类型,但每个对象实例仍然是强类型的
代码节 4.36:编译错误。
Repository<Integer> arithmeticRepository = new Repository<Integer>();
arithmeticRepository.setItem("Hello!");
|
一个类可以定义任意数量的泛型类型。为每个泛型类型选择一个不同的标识符,并用逗号隔开它们
代码清单 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
)时,称为原始类型。
泛型类型可以仅为方法定义
代码节 4.37:泛型方法。
public <D> D assign(Collection<D> generic, D obj) {
generic.add(obj);
return obj;
}
|
这里,在方法声明的开头选择了一个新的标识符(D)。该类型特定于方法调用,并且可以在同一对象实例中使用不同的类型
代码节 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:考虑以下类。
问题 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);
}
}
|
控制台上将显示什么?
答案 4.8 的控制台
Here is the text: Open your mind. |
aQuestion.getItem()
的类型为字符串。
正如我们上面所看到的,泛型给人的印象是,每个不同的类型参数都会创建一个新的容器类型。我们还看到,除了正常的类型检查之外,当我们分配泛型变量时,类型参数也必须匹配。在某些情况下,这限制太严格。如果我们想放松这种额外的检查怎么办?如果我们想定义一个可以保存任何泛型集合的集合变量,无论它保存的类型参数是什么?通配符类型用字符 <?> 表示,发音为 未知 或 任意类型。任意类型也可以用 <? extends Object>
表示。任意类型包括接口,不仅仅是类。所以现在我们可以定义一个元素类型与任何东西匹配的集合。见下文
代码节 4.39:通配符类型。
Collection<?> collUnknown;
|
你可以对可以使用类的类型进行指定限制。例如,<? extends ClassName>
仅允许 ClassName
类或子类的对象。例如,要创建一个只能包含“可序列化”对象的集合,请指定
代码节 4.40:可序列化子对象的集合。
Collection<String> textColl = new ArrayList<String>();
Collection<? extends Serializable> serColl = textColl;
|
上面的代码是有效的,因为 String
类是可序列化的。使用不可序列化的类会导致编译错误。添加的项目可以作为 Serializable
对象检索。你可以调用 Serializable
接口的方法或将其转换为 String
。以下集合只能包含扩展 Animal
类的对象。
代码清单 4.38:Dog.java
class Dog extends Animal {
}
|
代码节 4.41:子类的示例。
// Create "Animal Collection" variable
Collection<? extends Animal> animalColl = new ArrayList<Dog>();
|
<? super ClassName>
指定对可以使用类的类型进行指定限制。例如,要声明一个可以比较 Dogs 的 Comparator,请使用
代码节 4.42:超类。
Comparator<? super Dog> myComparator;
|
现在假设你定义了一个可以比较 Animals 的比较器
代码节 4.43:Comparator。
class AnimalComparator implements Comparator<Animal> {
int compare(Animal a, Animal b) {
//...
}
}
|
由于 Dogs
是 Animals
,因此你也可以使用此比较器来比较 Dogs。任何 Dog 超类的比较器也可以比较 Dog;但是,任何严格子类的比较器都不能。
代码节 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
类的超类型。使用不是超类型的类会导致编译错误。
与原始类型(即没有泛型)相比,无界通配符(即 <?>
)的优势在于明确表示参数化类型未知,而不是任何类型。这样,所有暗示知道类型的操作都被禁止,以避免不安全的操作。考虑以下代码
代码部分 4.45:不安全操作。
public void addAtBottom(Collection anyCollection) {
anyCollection.add(new Integer(1));
}
|
此代码将编译,但如果集合只包含字符串,则此代码可能会破坏集合
代码部分 4.46:集合损坏。
List<String> col = new ArrayList<String>();
addAtBottom(col);
col.get(0).endsWith(".");
|
代码部分 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<?>)
可以包含以下代码
代码部分 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()
方法返回具有精确类型的全新对象,而无需进行强制转换。使用泛型的示例
代码部分 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;
}
|
没有泛型的相同代码
代码部分 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
对象添加到应该只包含 String
的 Collection
中)。从 Java 1.5 开始,这成为可能。在 Java 演变的前几年,Java 没有真正的竞争对手。微软 C# 的出现改变了这一局面。Java 泛型更适合与 C# 竞争。其他语言中也存在类似于 Java 泛型的构造,有关详细信息,请参阅 泛型编程。泛型在 Java 语言语法中是在 1.5 版本中添加的。这意味着使用泛型的代码将无法在 Java 1.4 及更低版本中编译。泛型的使用是可选的。为了与预泛型代码向后兼容,可以使用没有泛型类型规范(<T>
)的泛型类。在这种情况下,当您从泛型对象检索对象引用时,您将必须手动将其从类型 Object 强制转换为正确的类型。
Java 泛型类似于 C++ 模板,因为它们都是出于相同的原因添加的。Java 泛型和 C++ 模板的语法也类似。但是,也存在一些差异。C++ 模板可以看作是一种宏,因为会为每个引用的泛型类型生成新的代码副本。所有模板的额外代码都在编译时生成。相反,Java 泛型是内置在语言中的。相同的代码用于每个泛型类型。例如
代码部分 4.50:Java 泛型。
Collection<String> collString = new ArrayList<String>();
Collection<Integer> collInteger = new ArrayList<Integer>();
|
这两个对象在运行时显示为相同类型(都是 ArrayList
)。泛型类型信息在编译期间被擦除(类型擦除)。例如
代码部分 4.51:类型擦除。
public <T> void method(T argument) {
T variable;
…
}
|
通过擦除转换为
代码部分 4.52:转换。
public void method(Object argument) {
Object variable;
…
}
|
问题 4.9:考虑以下类。
问题 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>();
}
}
|
哪些行会生成编译错误?
答案 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>
。