为什么这个复杂的泛型示例不能使用 Java 7 编译(但可以使用 Java 8),我该如何解决它?
Why does this complex generics example not compile with Java 7, (but does with Java 8), and how can I work around it?
这是代码。 Java 7 和 8 的错误和警告都包含在注释中。
可以更改 takeCollection(...)
的签名,以便调用者可以调用它并传递 SubRoot
/Foo
/Bar
集合并编译它两者都反对 Java 7 和 8,而不必通过重载在方法签名中专门引用 SubRoot
/Foo
/Bar
类型(其中可能有很多,一些在这个项目不可见的依赖项目中)?如果可以,怎么做?
package generics;
import java.util.Collection;
// -------------------------------------------------------------------------------
// Cannot change this (start)
//-------------------------------------------------------------------------------
interface Root<F extends Foo<F,B>, B extends Bar<F,B>> {}
class Foo <F extends Foo<F,B>, B extends Bar<F,B>> implements Root<F,B> {}
class Bar <F extends Foo<F,B>, B extends Bar<F,B>> implements Root<F,B> {}
interface SubRoot extends Root<SubFoo, SubBar> {}
class SubFoo extends Foo <SubFoo, SubBar> implements SubRoot {}
class SubBar extends Bar <SubFoo, SubBar> implements SubRoot {}
//-------------------------------------------------------------------------------
// End of cannot change block.
//-------------------------------------------------------------------------------
public class GenericsTest {
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void takeOne(R one) {}
// The following method signature can be changed a bit, but it has to accept a collection
// of Roots, or ANY of its subtypes.
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>, C extends Collection<R>>
void takeCollection(C collection) {}
// ------------------------------------------
// Test/illustration code that compiles well
// ------------------------------------------
@SuppressWarnings("rawtypes") // Warnings understood
void testOneRoot1(Root root, Foo foo, Bar bar) {
takeOne(root);
// Java 1.7 and 1.8 agree about the warning, as expected.
//
// generics\GenericsTest.java:35: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types
// takeOne(root);
// ^
// required: R
// found: Root
// where R,F,B are type-variables:
// R extends Root<F,B> declared in method <R,F,B>takeOne(R)
// F extends Foo<F,B> declared in method <R,F,B>takeOne(R)
// B extends Bar<F,B> declared in method <R,F,B>takeOne(R)
takeOne(foo);
// Java 1.7 and 1.8 agree about the warning, as expected.
//
// generics\GenericsTest.java:48: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types
// takeOne(foo);
// ^
// required: R
// found: Foo
// where R,F,B are type-variables:
// R extends Root<F,B> declared in method <R,F,B>takeOne(R)
// F extends Foo<F,B> declared in method <R,F,B>takeOne(R)
// B extends Bar<F,B> declared in method <R,F,B>takeOne(R)
takeOne(bar);
// Java 1.7 and 1.8 agree about the warning, as expected.
//
// generics\GenericsTest.java:61: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types
// takeOne(bar);
// ^
// required: R
// found: Bar
// where R,F,B are type-variables:
// R extends Root<F,B> declared in method <R,F,B>takeOne(R)
// F extends Foo<F,B> declared in method <R,F,B>takeOne(R)
// B extends Bar<F,B> declared in method <R,F,B>takeOne(R)
}
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testOneRoot2(R root, F foo, B bar) {
takeOne(root); // All fine
takeOne(foo); // All fine
takeOne(bar); // All fine
}
void testOneSubRoot(SubRoot subRoot, SubFoo subFoo, SubBar subBar) {
takeOne(subRoot); // All fine
takeOne(subFoo); // All fine
takeOne(subBar); // All fine
}
void testRootCollectionRaw(@SuppressWarnings("rawtypes") Collection<? extends Root> collection) {
takeCollection(collection);
// Java 1.7 quiet (not expected). Java 1.8 produces a warning, as expected:
//
// generics\GenericsTest.java:89: warning: [unchecked] unchecked method invocation: method takeCollection in class GenericsTest is applied to given types
// takeCollection(collection);
// ^
// required: C
// found: Collection<CAP#1>
// where C,R,F,B are type-variables:
// C extends Collection<R> declared in method <R,F,B,C>takeCollection(C)
// R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C)
// F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C)
// B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C)
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Root from capture of ? extends Root
}
// --------------------------------------------
// Test/illustration code that does NOT compile
// --------------------------------------------
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testRootCollection1(Collection<? extends R> collection) {
takeCollection(collection);
// JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected):
//
// generics\GenericsTest.java:112: error: invalid inferred types for R#1,F#1,B#1; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: CAP#1
// bound(s): Root<CAP#2,CAP#3>
// where R#1,F#1,B#1,C,R#2,F#2,B#2 are type-variables:
// R#1 extends Root<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// F#1 extends Foo<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// B#1 extends Bar<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// C extends Collection<R#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// R#2 extends Root<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>)
// F#2 extends Foo<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>)
// B#2 extends Bar<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>)
// where CAP#1,CAP#2,CAP#3 are fresh type-variables:
// CAP#1 extends R#2 from capture of ? extends R#2
// CAP#2 extends Foo<CAP#2,CAP#3> from capture of ?
// CAP#3 extends Bar<CAP#2,CAP#3> from capture of ?
}
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testRootCollection2(Collection<R> collection) {
takeCollection(collection);
// JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected):
//
// generics\GenericsTest.java:136: error: invalid inferred types for R#1,F#1,B#1; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: R#2
// bound(s): Root<CAP#1,CAP#2>
// where R#1,F#1,B#1,C,R#2,F#2,B#2 are type-variables:
// R#1 extends Root<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// F#1 extends Foo<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// B#1 extends Bar<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// C extends Collection<R#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// R#2 extends Root<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>)
// F#2 extends Foo<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>)
// B#2 extends Bar<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>)
// where CAP#1,CAP#2 are fresh type-variables:
// CAP#1 extends Foo<CAP#1,CAP#2> from capture of ?
// CAP#2 extends Bar<CAP#1,CAP#2> from capture of ?
}
void testSubRootCollection1(Collection<SubRoot> collection) {
takeCollection(collection);
// JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected):
//
// generics\GenericsTest.java:158: error: invalid inferred types for R,F,B; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: SubRoot
// bound(s): Root<CAP#1,CAP#2>
// where R,F,B,C are type-variables:
// R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C)
// F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C)
// B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C)
// C extends Collection<R> declared in method <R,F,B,C>takeCollection(C)
// where CAP#1,CAP#2 are fresh type-variables:
// CAP#1 extends Foo<CAP#1,CAP#2> from capture of ?
// CAP#2 extends Bar<CAP#1,CAP#2> from capture of ?
}
void testSubRootCollection2(Collection<? extends SubRoot> collection) {
takeCollection(collection);
// JDK 1.8 quiet. JDK 1.7 yields an error:
//
// generics\GenericsTest.java:177: error: invalid inferred types for R,F,B; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: CAP#1
// bound(s): Root<CAP#2,CAP#3>
// where R,F,B,C are type-variables:
// R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C)
// F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C)
// B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C)
// C extends Collection<R> declared in method <R,F,B,C>takeCollection(C)
// where CAP#1,CAP#2,CAP#3 are fresh type-variables:
// CAP#1 extends SubRoot from capture of ? extends SubRoot
// CAP#2 extends Foo<CAP#2,CAP#3> from capture of ?
// CAP#3 extends Bar<CAP#2,CAP#3> from capture of ?
}
}
如果我将 takeCollection
的签名更改为:
,所有编译错误都会消失
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void takeCollection(Collection<? extends R> collection) {}
虽然有 unchecked/unsafe 个操作警告,但都与原始类型有关。
我会像下面这样更改签名:
void takeOne(Root<?,?> one) {}
void takeCollection(Collection<? extends Root<?,?>> collection) {}
void testOneRoot1(Root<?,?> root, Foo<?,?> foo, Bar<?,?> bar) {}
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testOneRoot2(R root, F foo, B bar) {}
void testOneSubRoot(SubRoot subRoot, SubFoo subFoo, SubBar subBar) {}
@SuppressWarnings("unchecked")
void testRootCollectionRaw(@SuppressWarnings("rawtypes") Collection<? extends Root> collection) {
takeCollection((Collection<? extends Root<?,?>>)collection);
}
void testRootCollection1(Collection<? extends Root<?,?>> collection) {}
void testRootCollection2(Collection<? extends Root<?,?>> collection) {}
您的代码应该使用此签名为 Java 7 和 Java 8 进行编译,而不会出现错误或警告。在您的代码中有几个地方可以用通配符替换泛型类型参数。例如<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void takeOne(R one) {}
几乎等同于void takeOne(Root<?,?> one) {}
,因为F
和B
的绑定方式已经由Root
的签名定义了。 testRootCollectionRaw
是一个特例,因为它以原始类型为上界。我宁愿在 testRootCollectionRaw
方法中使用未经检查的转换来解决这种情况,而不是通过调整 takeCollection
签名使其工作。
您的原始代码无法针对 Java 7 进行编译的原因是 Java 7 中的类型推断不如 Java 8 中的类型推断先进。Java 8 将在类型推断上考虑表达式的目标类型。通常,在可能的情况下用通配符替换显式类型参数是个好主意。这样做的副作用是 Java 7 处理通配符比处理泛型方法中的显式类型参数更容易。
这是代码。 Java 7 和 8 的错误和警告都包含在注释中。
可以更改 takeCollection(...)
的签名,以便调用者可以调用它并传递 SubRoot
/Foo
/Bar
集合并编译它两者都反对 Java 7 和 8,而不必通过重载在方法签名中专门引用 SubRoot
/Foo
/Bar
类型(其中可能有很多,一些在这个项目不可见的依赖项目中)?如果可以,怎么做?
package generics;
import java.util.Collection;
// -------------------------------------------------------------------------------
// Cannot change this (start)
//-------------------------------------------------------------------------------
interface Root<F extends Foo<F,B>, B extends Bar<F,B>> {}
class Foo <F extends Foo<F,B>, B extends Bar<F,B>> implements Root<F,B> {}
class Bar <F extends Foo<F,B>, B extends Bar<F,B>> implements Root<F,B> {}
interface SubRoot extends Root<SubFoo, SubBar> {}
class SubFoo extends Foo <SubFoo, SubBar> implements SubRoot {}
class SubBar extends Bar <SubFoo, SubBar> implements SubRoot {}
//-------------------------------------------------------------------------------
// End of cannot change block.
//-------------------------------------------------------------------------------
public class GenericsTest {
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void takeOne(R one) {}
// The following method signature can be changed a bit, but it has to accept a collection
// of Roots, or ANY of its subtypes.
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>, C extends Collection<R>>
void takeCollection(C collection) {}
// ------------------------------------------
// Test/illustration code that compiles well
// ------------------------------------------
@SuppressWarnings("rawtypes") // Warnings understood
void testOneRoot1(Root root, Foo foo, Bar bar) {
takeOne(root);
// Java 1.7 and 1.8 agree about the warning, as expected.
//
// generics\GenericsTest.java:35: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types
// takeOne(root);
// ^
// required: R
// found: Root
// where R,F,B are type-variables:
// R extends Root<F,B> declared in method <R,F,B>takeOne(R)
// F extends Foo<F,B> declared in method <R,F,B>takeOne(R)
// B extends Bar<F,B> declared in method <R,F,B>takeOne(R)
takeOne(foo);
// Java 1.7 and 1.8 agree about the warning, as expected.
//
// generics\GenericsTest.java:48: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types
// takeOne(foo);
// ^
// required: R
// found: Foo
// where R,F,B are type-variables:
// R extends Root<F,B> declared in method <R,F,B>takeOne(R)
// F extends Foo<F,B> declared in method <R,F,B>takeOne(R)
// B extends Bar<F,B> declared in method <R,F,B>takeOne(R)
takeOne(bar);
// Java 1.7 and 1.8 agree about the warning, as expected.
//
// generics\GenericsTest.java:61: warning: [unchecked] unchecked method invocation: method takeOne in class GenericsTest is applied to given types
// takeOne(bar);
// ^
// required: R
// found: Bar
// where R,F,B are type-variables:
// R extends Root<F,B> declared in method <R,F,B>takeOne(R)
// F extends Foo<F,B> declared in method <R,F,B>takeOne(R)
// B extends Bar<F,B> declared in method <R,F,B>takeOne(R)
}
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testOneRoot2(R root, F foo, B bar) {
takeOne(root); // All fine
takeOne(foo); // All fine
takeOne(bar); // All fine
}
void testOneSubRoot(SubRoot subRoot, SubFoo subFoo, SubBar subBar) {
takeOne(subRoot); // All fine
takeOne(subFoo); // All fine
takeOne(subBar); // All fine
}
void testRootCollectionRaw(@SuppressWarnings("rawtypes") Collection<? extends Root> collection) {
takeCollection(collection);
// Java 1.7 quiet (not expected). Java 1.8 produces a warning, as expected:
//
// generics\GenericsTest.java:89: warning: [unchecked] unchecked method invocation: method takeCollection in class GenericsTest is applied to given types
// takeCollection(collection);
// ^
// required: C
// found: Collection<CAP#1>
// where C,R,F,B are type-variables:
// C extends Collection<R> declared in method <R,F,B,C>takeCollection(C)
// R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C)
// F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C)
// B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C)
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Root from capture of ? extends Root
}
// --------------------------------------------
// Test/illustration code that does NOT compile
// --------------------------------------------
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testRootCollection1(Collection<? extends R> collection) {
takeCollection(collection);
// JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected):
//
// generics\GenericsTest.java:112: error: invalid inferred types for R#1,F#1,B#1; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: CAP#1
// bound(s): Root<CAP#2,CAP#3>
// where R#1,F#1,B#1,C,R#2,F#2,B#2 are type-variables:
// R#1 extends Root<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// F#1 extends Foo<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// B#1 extends Bar<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// C extends Collection<R#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// R#2 extends Root<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>)
// F#2 extends Foo<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>)
// B#2 extends Bar<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection1(Collection<? extends R#2>)
// where CAP#1,CAP#2,CAP#3 are fresh type-variables:
// CAP#1 extends R#2 from capture of ? extends R#2
// CAP#2 extends Foo<CAP#2,CAP#3> from capture of ?
// CAP#3 extends Bar<CAP#2,CAP#3> from capture of ?
}
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testRootCollection2(Collection<R> collection) {
takeCollection(collection);
// JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected):
//
// generics\GenericsTest.java:136: error: invalid inferred types for R#1,F#1,B#1; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: R#2
// bound(s): Root<CAP#1,CAP#2>
// where R#1,F#1,B#1,C,R#2,F#2,B#2 are type-variables:
// R#1 extends Root<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// F#1 extends Foo<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// B#1 extends Bar<F#1,B#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// C extends Collection<R#1> declared in method <R#1,F#1,B#1,C>takeCollection(C)
// R#2 extends Root<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>)
// F#2 extends Foo<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>)
// B#2 extends Bar<F#2,B#2> declared in method <R#2,F#2,B#2>testRootCollection2(Collection<R#2>)
// where CAP#1,CAP#2 are fresh type-variables:
// CAP#1 extends Foo<CAP#1,CAP#2> from capture of ?
// CAP#2 extends Bar<CAP#1,CAP#2> from capture of ?
}
void testSubRootCollection1(Collection<SubRoot> collection) {
takeCollection(collection);
// JDK 1.8 quiet (as expected). JDK 1.7 yields an error (not expected):
//
// generics\GenericsTest.java:158: error: invalid inferred types for R,F,B; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: SubRoot
// bound(s): Root<CAP#1,CAP#2>
// where R,F,B,C are type-variables:
// R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C)
// F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C)
// B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C)
// C extends Collection<R> declared in method <R,F,B,C>takeCollection(C)
// where CAP#1,CAP#2 are fresh type-variables:
// CAP#1 extends Foo<CAP#1,CAP#2> from capture of ?
// CAP#2 extends Bar<CAP#1,CAP#2> from capture of ?
}
void testSubRootCollection2(Collection<? extends SubRoot> collection) {
takeCollection(collection);
// JDK 1.8 quiet. JDK 1.7 yields an error:
//
// generics\GenericsTest.java:177: error: invalid inferred types for R,F,B; inferred type does not conform to declared bound(s)
// takeCollection(collection);
// ^
// inferred: CAP#1
// bound(s): Root<CAP#2,CAP#3>
// where R,F,B,C are type-variables:
// R extends Root<F,B> declared in method <R,F,B,C>takeCollection(C)
// F extends Foo<F,B> declared in method <R,F,B,C>takeCollection(C)
// B extends Bar<F,B> declared in method <R,F,B,C>takeCollection(C)
// C extends Collection<R> declared in method <R,F,B,C>takeCollection(C)
// where CAP#1,CAP#2,CAP#3 are fresh type-variables:
// CAP#1 extends SubRoot from capture of ? extends SubRoot
// CAP#2 extends Foo<CAP#2,CAP#3> from capture of ?
// CAP#3 extends Bar<CAP#2,CAP#3> from capture of ?
}
}
如果我将 takeCollection
的签名更改为:
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void takeCollection(Collection<? extends R> collection) {}
虽然有 unchecked/unsafe 个操作警告,但都与原始类型有关。
我会像下面这样更改签名:
void takeOne(Root<?,?> one) {}
void takeCollection(Collection<? extends Root<?,?>> collection) {}
void testOneRoot1(Root<?,?> root, Foo<?,?> foo, Bar<?,?> bar) {}
<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void testOneRoot2(R root, F foo, B bar) {}
void testOneSubRoot(SubRoot subRoot, SubFoo subFoo, SubBar subBar) {}
@SuppressWarnings("unchecked")
void testRootCollectionRaw(@SuppressWarnings("rawtypes") Collection<? extends Root> collection) {
takeCollection((Collection<? extends Root<?,?>>)collection);
}
void testRootCollection1(Collection<? extends Root<?,?>> collection) {}
void testRootCollection2(Collection<? extends Root<?,?>> collection) {}
您的代码应该使用此签名为 Java 7 和 Java 8 进行编译,而不会出现错误或警告。在您的代码中有几个地方可以用通配符替换泛型类型参数。例如<R extends Root<F,B>, F extends Foo<F,B>, B extends Bar<F,B>>
void takeOne(R one) {}
几乎等同于void takeOne(Root<?,?> one) {}
,因为F
和B
的绑定方式已经由Root
的签名定义了。 testRootCollectionRaw
是一个特例,因为它以原始类型为上界。我宁愿在 testRootCollectionRaw
方法中使用未经检查的转换来解决这种情况,而不是通过调整 takeCollection
签名使其工作。
您的原始代码无法针对 Java 7 进行编译的原因是 Java 7 中的类型推断不如 Java 8 中的类型推断先进。Java 8 将在类型推断上考虑表达式的目标类型。通常,在可能的情况下用通配符替换显式类型参数是个好主意。这样做的副作用是 Java 7 处理通配符比处理泛型方法中的显式类型参数更容易。