Java 中的泛型
Generics in Java
我正在尝试学习 Java 中的泛型编程概念,但我坚持了一个 question.Consider 函数 max 的这些实现:
1)
public static <T extends Comparable<? super T>> T max(Collection<? extends T> collection){
//code
}
2)
public static <T extends Comparable<T>> T max(Collection<T> collection){
//code
}
我很想知道,它们之间有什么区别?
当然,我知道,声明 Collection<? extends T>
允许将 T 的子类型作为 Collection 传递,但是在这种情况下,在静态方法中它有什么用?
不使用边界结果必须相同,不是吗?
那这个 <T extends Comparable<? super T>>
呢?
如果你传递T的子类型,反正没问题,因为T实现了Comparable,Comparable的实现会在子类型中维护,所以你可以使用2)选项。
我只是想知道是否有一些情况,当不可能使用 2) 选项,但可以使用 1)
这适用于第一个版本,但不适用于第二个版本:
class A implements Comparable<A> {...};
class B extends A {...};
List<B> list = new ArrayList<>();
B maxItem = max(list);
在这两种情况下,类型参数 T
都设置为 B
,因为 list
是 Collection<B>
。然而,B
没有实现 Comparable<B>
,因此它不能满足第二个声明中对 T extends Comparable<T>
的约束。
不过,它适用于第一个声明,因为 A
是 B
的超类。
这解释了 Comparable<? super T>
... Collection<? extends T>
在这里并不是真正必要的——我认为 max 方法没有任何理由成为 return 类型除了集合元素类型 T,所以 Collection<T>
就可以了。
要完全理解它,您缺少两个版本。这是全部四个:
public static <T extends Comparable<? super T>> T max1(Collection<? extends T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<T>> T max2(Collection<T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<T>> T max3(Collection<? extends T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<? super T>> T max4(Collection<T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
为了看看它们是如何工作的,我会偷 :
class A implements Comparable<A> {
@Override public int compareTo(A that) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
class B extends A {
}
现在让我们看看四种 max 方法中哪一种有效:
List<B> list = new ArrayList<>();
B maxItem1 = max1(list); // T = B (but could be A if result is A, see next line)
A maxItem1a = Test.<A>max1(list); // T = A
B maxItem1b = Test.<B>max1(list); // T = B
B maxItem2 = max2(list); // compile error
A maxItem3 = max3(list); // T = A (forced by Comparable<T>)
B maxItem4 = max4(list); // T = B (forced by Collection<T>)
如您所见,对于 max3()
和 max4()
,由于缺少通配符,T
被强制为特定类型。
max2()
根本不行,因为没有T
可以同时满足Comparable<T>
和Collection<T>
。
至于max1()
,T
可以解析为A
或B
,但如果解析为A
,则必须赋值成为 A
类型的变量。如果解析为 B
,分配可以是 A
或 B
。
如果不手动指定T
类型,编译器会选择B
.
请注意,由于您的 max 方法可能只会迭代集合一次,您甚至可以将参数类型更改为 Iterable
,一切仍将正常工作。
这将允许您找到非集合迭代的最大值,例如 java.nio.file.Path
以在文件夹 中查找最后一个文件(按字典顺序).
让我们把它分解成几个部分:
<T extends Comparable<? super T>>
这定义了 T
的泛型边界——它可以表示任何 Comparable
类型——因为 T
是 Comparable
或者因为某些父 class 是。由于该父 class(称之为 P
)可能实现 Comparable<P>
而不是 Comparable<T>
,因此有必要在此处使用 super
。听起来你已经知道了。
T
这是方法的 return 类型,通常可以由调用上下文指定(例如,当您将结果分配给变量时)。我们会回到这个。
Collection<? extends T>
这允许您传入 T
的 Collection
或 T
的任何子类型(称之为 S
)。如果您刚刚指定 Collection<T>
,您将 仅 能够传递 Collection<T>
个对象而不是 Collection<S>
个对象。
将它们放在一起可以让调用者执行如下操作:
// notice that max() returns a C, but is assignable to a B
B b = max(new ArrayList<C>());
其中 A
、B
和 C
是:
A implements Comparable<A>
B extends A
C extends B
事实证明,由于您的方法 return 直接是 T
,因此 Collection<? extends T>
并不重要,因为您始终可以分配子类型(C
) 到父类型 (B
)。但是,如果我们将方法签名稍微调整为 return 一个使用 T
作为泛型的类型,这将不起作用:
public static <T extends Comparable<? super T>> Collection<T>
collect(Collection<T> collection)
注意我们现在 returning a Collection<T>
- 现在 ? extends
变得很重要 - 这不会编译:
Collection<B> b = collect(new ArrayList<C>());
而如果您将参数更改为 Collection<? extends T>
,则会放宽界限; return 类型不必与参数类型相同。您可以传入 Collection<C>
但因为调用者使 T
表示 B
(因为这是 return 类型所期望的类型) C
现在 扩展 T
,而不是是 T
.
简而言之,对于您的方法,我认为您不需要 ? extends T
,但在某些情况下您需要。更一般地说,如果你打算在你的泛型中支持子类型,那么明确地这样做是个好主意,即使这不是绝对必要的。
我正在尝试学习 Java 中的泛型编程概念,但我坚持了一个 question.Consider 函数 max 的这些实现:
1)
public static <T extends Comparable<? super T>> T max(Collection<? extends T> collection){
//code
}
2)
public static <T extends Comparable<T>> T max(Collection<T> collection){
//code
}
我很想知道,它们之间有什么区别?
当然,我知道,声明 Collection<? extends T>
允许将 T 的子类型作为 Collection 传递,但是在这种情况下,在静态方法中它有什么用?
不使用边界结果必须相同,不是吗?
那这个 <T extends Comparable<? super T>>
呢?
如果你传递T的子类型,反正没问题,因为T实现了Comparable,Comparable的实现会在子类型中维护,所以你可以使用2)选项。
我只是想知道是否有一些情况,当不可能使用 2) 选项,但可以使用 1)
这适用于第一个版本,但不适用于第二个版本:
class A implements Comparable<A> {...};
class B extends A {...};
List<B> list = new ArrayList<>();
B maxItem = max(list);
在这两种情况下,类型参数 T
都设置为 B
,因为 list
是 Collection<B>
。然而,B
没有实现 Comparable<B>
,因此它不能满足第二个声明中对 T extends Comparable<T>
的约束。
不过,它适用于第一个声明,因为 A
是 B
的超类。
这解释了 Comparable<? super T>
... Collection<? extends T>
在这里并不是真正必要的——我认为 max 方法没有任何理由成为 return 类型除了集合元素类型 T,所以 Collection<T>
就可以了。
要完全理解它,您缺少两个版本。这是全部四个:
public static <T extends Comparable<? super T>> T max1(Collection<? extends T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<T>> T max2(Collection<T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<T>> T max3(Collection<? extends T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<? super T>> T max4(Collection<T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
为了看看它们是如何工作的,我会偷
class A implements Comparable<A> {
@Override public int compareTo(A that) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
class B extends A {
}
现在让我们看看四种 max 方法中哪一种有效:
List<B> list = new ArrayList<>();
B maxItem1 = max1(list); // T = B (but could be A if result is A, see next line)
A maxItem1a = Test.<A>max1(list); // T = A
B maxItem1b = Test.<B>max1(list); // T = B
B maxItem2 = max2(list); // compile error
A maxItem3 = max3(list); // T = A (forced by Comparable<T>)
B maxItem4 = max4(list); // T = B (forced by Collection<T>)
如您所见,对于 max3()
和 max4()
,由于缺少通配符,T
被强制为特定类型。
max2()
根本不行,因为没有T
可以同时满足Comparable<T>
和Collection<T>
。
至于max1()
,T
可以解析为A
或B
,但如果解析为A
,则必须赋值成为 A
类型的变量。如果解析为 B
,分配可以是 A
或 B
。
如果不手动指定T
类型,编译器会选择B
.
请注意,由于您的 max 方法可能只会迭代集合一次,您甚至可以将参数类型更改为 Iterable
,一切仍将正常工作。
这将允许您找到非集合迭代的最大值,例如 java.nio.file.Path
以在文件夹 中查找最后一个文件(按字典顺序).
让我们把它分解成几个部分:
<T extends Comparable<? super T>>
这定义了 T
的泛型边界——它可以表示任何 Comparable
类型——因为 T
是 Comparable
或者因为某些父 class 是。由于该父 class(称之为 P
)可能实现 Comparable<P>
而不是 Comparable<T>
,因此有必要在此处使用 super
。听起来你已经知道了。
T
这是方法的 return 类型,通常可以由调用上下文指定(例如,当您将结果分配给变量时)。我们会回到这个。
Collection<? extends T>
这允许您传入 T
的 Collection
或 T
的任何子类型(称之为 S
)。如果您刚刚指定 Collection<T>
,您将 仅 能够传递 Collection<T>
个对象而不是 Collection<S>
个对象。
将它们放在一起可以让调用者执行如下操作:
// notice that max() returns a C, but is assignable to a B
B b = max(new ArrayList<C>());
其中 A
、B
和 C
是:
A implements Comparable<A>
B extends A
C extends B
事实证明,由于您的方法 return 直接是 T
,因此 Collection<? extends T>
并不重要,因为您始终可以分配子类型(C
) 到父类型 (B
)。但是,如果我们将方法签名稍微调整为 return 一个使用 T
作为泛型的类型,这将不起作用:
public static <T extends Comparable<? super T>> Collection<T>
collect(Collection<T> collection)
注意我们现在 returning a Collection<T>
- 现在 ? extends
变得很重要 - 这不会编译:
Collection<B> b = collect(new ArrayList<C>());
而如果您将参数更改为 Collection<? extends T>
,则会放宽界限; return 类型不必与参数类型相同。您可以传入 Collection<C>
但因为调用者使 T
表示 B
(因为这是 return 类型所期望的类型) C
现在 扩展 T
,而不是是 T
.
简而言之,对于您的方法,我认为您不需要 ? extends T
,但在某些情况下您需要。更一般地说,如果你打算在你的泛型中支持子类型,那么明确地这样做是个好主意,即使这不是绝对必要的。