【发布时间】:2009-04-02 21:00:11
【问题描述】:
谁能指出在 Java 中使用泛型的好例子?我的意思是自己编写泛型类的示例?
大多数解释是“你可以像这样定义一个泛型类。现在看看 Java 集合 API 并忘记所有这些 - 只需使用它并快乐。”
我想要的更像是“你可以像这样定义一个泛型类,现在考虑一下你可能需要它的这种情况,现在让我们看看我们将如何编写它。”
谢谢。
【问题讨论】:
谁能指出在 Java 中使用泛型的好例子?我的意思是自己编写泛型类的示例?
大多数解释是“你可以像这样定义一个泛型类。现在看看 Java 集合 API 并忘记所有这些 - 只需使用它并快乐。”
我想要的更像是“你可以像这样定义一个泛型类,现在考虑一下你可能需要它的这种情况,现在让我们看看我们将如何编写它。”
谢谢。
【问题讨论】:
Java 中的泛型有助于parametric polymorphism。通过类型参数,您可以将参数传递给类型。就像String foo(String s) 这样的方法模拟一些行为,不仅针对特定字符串,而且针对任何字符串s,所以像List<T> 这样的类型模拟一些行为,不仅针对特定类型,而是为任何类型。 List<T> 表示对于任何类型的T,都存在List 的Ts。所以List 实际上是一个类型构造函数。它将一个类型作为参数并构造另一个类型作为结果。
这里有几个我每天使用的泛型类型的例子。首先,一个非常有用的通用接口:
public interface F<A, B> {
public B f(A a);
}
这个接口表示对于任何两种类型,A 和B,都有一个函数(称为f)接受A 并返回B。 当你实现这个接口,A 和B 可以是你想要的任何类型,只要你提供一个函数f 接受前者并返回后者。这是接口的示例实现:
F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
在泛型之前,多态性是通过子类化 使用extends 关键字来实现的。使用泛型,我们实际上可以取消子类化并使用参数多态性。例如,考虑一个用于计算任何类型的哈希码的参数化(通用)类。我们将使用这样的通用类,而不是覆盖 Object.hashCode():
public final class Hash<A> {
private final F<A, Integer> hashFunction;
public Hash(final F<A, Integer> f) {
this.hashFunction = f;
}
public int hash(A a) {
return hashFunction.f(a);
}
}
这比使用继承灵活得多,因为我们可以保持使用组合和参数多态的主题,而无需锁定脆弱的层次结构。
尽管如此,Java 的泛型并不完美。例如,您可以抽象类型,但不能抽象类型构造函数。也就是说,你可以说“对于任何类型 T”,但不能说“对于任何带有类型参数 A 的类型 T”。
I wrote an article about these limits of Java generics, here.
The source for a lot of useful generic types can be found here.。如果您还没有,请查看标准 Java 库的源代码。
【讨论】:
Java Generics FAQ 中部分回答了您的答案,它提供了一些很好的示例,说明您何时希望您的课程成为通用课程。另请查看该常见问题解答中的Designing Generic Methods 部分。
【讨论】:
我使用泛型来处理 DAO 中的一些基本 CRUD 操作。对于各种领域对象,大部分代码都会重复,所以我尽可能将其提取到一个通用抽象类中。
【讨论】:
不确定这是否正是您要寻找的,但就示例而言...您创建的任何类型的数据结构都可以(并且可能应该)以通用形式完成。我曾经这样开始过:
public class TrieMap<C extends Comparable<C>, K extends ComponentComparable<C>, V>
extends AbstractMap<K,V> implements SortedMap<K,V> {
...
}
当然,这取决于泛型的另一种用法,这个接口用于其元素可相互比较的序列:
public abstract interface ComponentComparable<C extends Comparable<C>>
extends Comparable<ComponentComparable<C>>, Iterable<C> {
...
}
我还创建了一整套节点类,例如
public interface Node<T extends Node<T>> { ... }
public interface TreeNode<T extends TreeNode<T>> extends Iterable<T>, Node<T> { ... }
public interface ListNode<T extends ListNode<T>> extends Node<T> { ... }
public interface BinaryTreeNode<T extends BinaryTreeNode<T>> extends Node<T> { ... }
public interface TernaryTreeNode<T extends TernaryTreeNode<T>>
extends BinaryTreeNode<T> { ... }
可以想象,它可以方便地创建图形、树、链表等,以及一个 Algorithms 类,该类具有在这些节点上操作的各种(通用!)方法。
或者,如果您厌倦了数据结构,其他一些想法包括可以转换为类型的事物的接口:
public abstract interface Convertable<T> {
public abstract T convert();
}
或者一个用于字符串解析器:
public interface Parser<O> {
public abstract O parse(String str);
}
或者一个用于它们自己的解析器的类:
public interface Parseable<P extends Parseable<P>> extends Parser<P> {}
我在创建系统时使用它来解析命令行选项:
public abstract class Option<T> { ... }
public class CLOption<P extends Parseable<P>> extends Option<P> { ... }
public class StringOption extends Option<String> { ... }
等等。等等等等。
【讨论】:
public class Pair<A,B> {
private A a;
private B b;
public Pair(A a, B b) { setFirst(a); setSecond(b); }
public A getFirst() { return a; }
public B getSecond() { return b; }
public void setFirst(A a) { this.a=a; }
public void setSecond(B b) { this.b=b; }
}
【讨论】:
使用泛型的静态实用程序函数示例。它需要两个映射 A -> B 和 B -> C 并从中构造一个 A -> C(哈希)映射。
public static <A, B, C> Map<A, C> transitise(Map<A, B> aToB, Map<B, C> bToC) {
HashMap<A, C> map = new HashMap<A, C>(aToB.size() * 2);
for (Map.Entry<A, B> abEntry : aToB.entrySet()) {
map.put(abEntry.getKey(), bToC.get(abEntry.getValue()));
}
return map;
}
使用示例:
public static void main(String[] args) {
HashMap<String, Integer> nameToNumber = new HashMap<String, Integer>();
nameToNumber.put("Anna", 12345);
nameToNumber.put("James", 444);
HashMap<Integer, Point> numberToPosition = new HashMap<Integer, Point>();
numberToPosition.put(12345, new Point(3, 3));
numberToPosition.put(444, new Point(1, 55));
for (Map.Entry<String, Point> nTP :
transitise(nameToNumber, numberToPosition).entrySet())
{
System.out.println(nTP.getKey() + " -> " +
nTP.getValue().x + "/" + nTP.getValue().y);
}
}
结果:
James -> 1/55
Anna -> 3/3
无论如何,我要说明的是,使用泛型,您可以制作一些非常有用的,呃,泛型实用函数。
【讨论】:
几个有用的练习:
我不能马上给你代码示例,因为我正在处理它们,但也许你可以尝试自己做,如果你遇到困难,再回来提出更多问题。
【讨论】: