Sam*_*per 6 java generics collections type-parameter bounded-wildcard
Collections.fill方法具有以下标头:
public static <T> void fill(List<? super T> list, T obj)
Run Code Online (Sandbox Code Playgroud)
为什么需要通配符?下面的标题似乎也同样有效:
public static <T> void fill(List<T> list, T obj)
Run Code Online (Sandbox Code Playgroud)
我看不出为什么需要通配符;如下代码适用于第二个标头和第一个标头:
List<Number> nums = new ArrayList<>();
Integer i = 43;
fill(nums, i); //fill method written using second header
Run Code Online (Sandbox Code Playgroud)
我的问题是:第一个标头可以工作但第二个标头不能工作的具体调用是什么?fill
如果没有这样的调用,为什么要包含通配符?在这种情况下,通配符不会使方法更简洁,也不会增加可读性(在我看来)。
这是一个非常好的问题,简单的答案已经被猜到了:
\n\n\n对于当前版本的 ,
\nfill(List<? super T> list, T obj)
\n如果签名更改为 ,则不存在会被拒绝的输入fill(List<T> list, T obj)
,因此没有任何好处,开发人员可能会遵循 PECS 原则
上面的说法源于这样的原理:如果存在这样的类型,X
那么\nX
是\n的超类型,T
那么由于类型逆变,\nList<X>
是\n的超类型。\n因为我们总能找到这样的(在最坏的情况下,它是class) - 编译器可以根据给定的任一形式推断出合适的参数类型List<? super T>
X
Object
List<X>
fill
。
因此,知道这一事实后,我们可以干扰编译器并使用“类型见证”自行推断类型,因此代码会中断:
\nList<Object> target = new ArrayList<>();\n//Compiles OK as we can represent List<Object> as List<? super Integer> and it fits\nCollections.<Integer>fill(target, 1);\n\n//Compilation error as List<Object> is invariant to List<Integer> and not a valid substitute\nCollections.<Integer>fillNew(target, 1);\n
Run Code Online (Sandbox Code Playgroud)\n当然,这纯粹是理论上的,任何头脑正常的人都不会在那里使用类型参数。
\n然而
\n在回答“在这里使用通配符有什么好处? ”这个问题时,我们只考虑了等式的一方面 - 我们,该方法和我们的经验的消费者,但没有考虑库开发人员。
\n因此,这个问题有点类似于为什么Collections.enumeration(final Collection<T> c)
要这样声明,而不是enumeration(Collection<T> c)
对final
最终用户来说似乎是多余的。
我们可以在这里推测真正的意图,但我可以给出一些主观原因:
\nList<? super T>
(以及final
for enumeration
)立即消除了代码的歧义,并且具体而言<? super T>
- 它有助于表明仅需要有关\ntype参数的部分知识,并且不能list
用于生成 T 的值,而仅消耗它们。\n引用:\n\n在仅需要有关类型参数的部分知识的情况下,通配符非常有用。\n JLS 4.5.1。参数化类型的类型参数
\n
现在让我们尝试提出一些假设的“改进”来看看我的意思(我将其形式fill
称为List<T>
)fillNew
:
#1 决定是让方法返回值obj
(用于填充列表):
public static <T> void fill(List<? super T> list, T obj)\n//becomes \xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\npublic static <T> T fill(List<? super T> list, T obj)\n
Run Code Online (Sandbox Code Playgroud)\n更新后的方法对于签名来说效果很好fill
,但是对于fillNew
- 推断的返回类型现在并不那么明显:
List<Number> target = new ArrayList<>();\nLong val = fill(target, 1L); //<<Here Long is the most specific type that fits both arguments\n//Compilation error\nLong val = fillNew(target, 1L); //<<Here Number is, so it cannot be assigned back\n\n//More exotic case:\nInteger val = fill(asList(true), 0); //val is Integer as expected\nComparable<?> val = fillNew(asList(true), 0); //val is now Comparable<?> as the most specific type \n
Run Code Online (Sandbox Code Playgroud)\n#2 添加重载版本的决定在以下fill
情况下性能提高 10 倍:T
Comparable<T>
/* Extremely performant 10x version */\npublic static <T extends Comparable<T>> void fill(List<? super T> list, T value)\n/* Normal version */\npublic static void fill(List<? super T> list, T value)\n\nList<Number> target = new ArrayList<>();\nfill(target, 1); //<<< Here the more performant version is used as T inferred to Integer and it implements Comparable<Integer>\nfillNew(target, 1); //<< Still uses the slow version just because T is inferred to Number which is not Comparable\n \n
Run Code Online (Sandbox Code Playgroud)\n综上所述 -fill
我认为对于各方(开发人员和库设计者)来说,当前的签名更加灵活/更具描述性
归档时间: |
|
查看次数: |
384 次 |
最近记录: |