为什么类型参数比方法参数更强大 [英] Why is a type parameter stronger then a method parameter
问题描述
为什么是
public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {...}
然后更严格
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {...}
这是为什么在编译时未检查lambda返回类型的后续操作.
我发现使用withX()
之类的方法
.withX(MyInterface::getLength, "I am not a Long")
产生所需的编译时错误:
BuilderExample.MyInterface类型的getLength()类型很长,与描述符的返回类型String不兼容
使用方法with()
时没有.
完整示例:
import java.util.function.Function;
public class SO58376589 {
public static class Builder<T> {
public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {
return this;
}
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
return this;
}
}
static interface MyInterface {
public Long getLength();
}
public static void main(String[] args) {
Builder<MyInterface> b = new Builder<MyInterface>();
Function<MyInterface, Long> getter = MyInterface::getLength;
b.with(getter, 2L);
b.with(MyInterface::getLength, 2L);
b.withX(getter, 2L);
b.withX(MyInterface::getLength, 2L);
b.with(getter, "No NUMBER"); // error
b.with(MyInterface::getLength, "No NUMBER"); // NO ERROR !!
b.withX(getter, "No NUMBER"); // error
b.withX(MyInterface::getLength, "No NUMBER"); // error !!!
}
}
javac SO58376589.java
SO58376589.java:32: error: method with in class Builder<T> cannot be applied to given types;
b.with(getter, "No NUMBER"); // error
^
required: Function<MyInterface,R>,R
found: Function<MyInterface,Long>,String
reason: inference variable R has incompatible bounds
equality constraints: Long
lower bounds: String
where R,T are type-variables:
R extends Object declared in method <R>with(Function<T,R>,R)
T extends Object declared in class Builder
SO58376589.java:34: error: method withX in class Builder<T> cannot be applied to given types;
b.withX(getter, "No NUMBER"); // error
^
required: F,R
found: Function<MyInterface,Long>,String
reason: inference variable R has incompatible bounds
equality constraints: Long
lower bounds: String
where F,R,T are type-variables:
F extends Function<MyInterface,R> declared in method <R,F>withX(F,R)
R extends Object declared in method <R,F>withX(F,R)
T extends Object declared in class Builder
SO58376589.java:35: error: incompatible types: cannot infer type-variable(s) R,F
b.withX(MyInterface::getLength, "No NUMBER"); // error
^
(argument mismatch; bad return type in method reference
Long cannot be converted to String)
where R,F,T are type-variables:
R extends Object declared in method <R,F>withX(F,R)
F extends Function<T,R> declared in method <R,F>withX(F,R)
T extends Object declared in class Builder
3 errors
扩展示例
以下示例显示了归结到供应商的方法和类型参数的不同行为.此外,它还显示了类型参数与使用者行为的区别.它表明,无论方法参数是消费者还是供应商,这都没有影响.
import java.util.function.Consumer;
import java.util.function.Supplier;
interface TypeInference {
Number getNumber();
void setNumber(Number n);
@FunctionalInterface
interface Method<R> {
TypeInference be(R r);
}
//Supplier:
<R> R letBe(Supplier<R> supplier, R value);
<R, F extends Supplier<R>> R letBeX(F supplier, R value);
<R> Method<R> let(Supplier<R> supplier); // return (x) -> this;
//Consumer:
<R> R lettBe(Consumer<R> supplier, R value);
<R, F extends Consumer<R>> R lettBeX(F supplier, R value);
<R> Method<R> lett(Consumer<R> consumer);
public static void main(TypeInference t) {
t.letBe(t::getNumber, (Number) 2); // Compiles :-)
t.lettBe(t::setNumber, (Number) 2); // Compiles :-)
t.letBe(t::getNumber, 2); // Compiles :-)
t.lettBe(t::setNumber, 2); // Compiles :-)
t.letBe(t::getNumber, "NaN"); // !!!! Compiles :-(
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, (Number) 2); // Compiles :-)
t.lettBeX(t::setNumber, (Number) 2); // Compiles :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
t.letBeX(t::getNumber, "NaN"); // Does not compile :-)
t.lettBeX(t::setNumber, "NaN"); // Does not compile :-)
t.let(t::getNumber).be(2); // Compiles :-)
t.lett(t::setNumber).be(2); // Compiles :-)
t.let(t::getNumber).be("NaN"); // Does not compile :-)
t.lett(t::setNumber).be("NaN"); // Does not compile :-)
}
}
这是一个非常有趣的问题.恐怕答案很复杂.
tl; dr
解决差异需要对Java的withX的行为似乎是您想要的,我仍认为这是当前规范的附带副作用,而不是积极的设计决策.
这很重要,因为它提示了一个问题我应该在应用程序设计中依靠这种行为吗?我认为您不应该这样做,因为您不能保证该语言的未来版本将继续以这种方式运行.
虽然语言设计人员在更新自己的规范/设计/编译器时会尽力不破坏现有的应用程序是事实,但问题是您要依赖的行为是当前编译器失败的行为(即不是现有的应用).语言更新始终将未编译的代码转换为编译的代码.例如,可能保证不能在Java 7中编译以下代码,而可以在Java 8中 进行编译:
static Runnable x = () -> System.out.println();
您的用例没有什么不同.
我对使用您的withX
方法持谨慎态度的另一个原因是F
参数本身.通常,存在方法上的通用类型参数(未出现在返回类型中)以将签名的多个部分的类型绑定在一起.就是说:
我不在乎T
是什么,但是要确保无论我在哪里使用T
都是同一类型.
那么,从逻辑上讲,我们希望每个类型参数在方法签名中至少出现两次,否则什么也没做".您的withX
中的F
仅在签名中出现一次,这向我建议使用不与该语言功能的 intent 内联的类型参数.
替代实现
以稍微预期的行为"方式实现此目的的一种方法是将您的with
方法分成2个链:
public class Builder<T> {
public final class With<R> {
private final Function<T,R> method;
private With(Function<T,R> method) {
this.method = method;
}
public Builder<T> of(R value) {
// TODO: Body of your old 'with' method goes here
return Builder.this;
}
}
public <R> With<R> with(Function<T,R> method) {
return new With<>(method);
}
}
然后可以按以下方式使用它:
b.with(MyInterface::getLong).of(1L); // Compiles
b.with(MyInterface::getLong).of("Not a long"); // Compiler error
这不像您的withX
那样包含无关的类型参数.通过将方法分为两个签名,它还可以从类型安全的角度更好地表达您要执行的操作的意图:
- 第一个方法根据方法引用设置一个类(
With
),该类定义. - 第二种方法(
of
)约束value
的类型,使其与您先前设置的内容兼容.
该语言的未来版本能够编译此代码的唯一方法是实现完全的鸭子式输入,这似乎不太可能.
最后一点,使这件事无关紧要: 我认为 Mockito (特别是其存根功能)可能已经基本上完成了您使用类型安全的通用生成器"要实现的目标.也许您可以改用它?
完整的解释
我将通过 <with
和withX
的em>类型推断过程 .这很长,所以慢慢来.尽管时间很长,但我仍然遗漏了很多细节.您可能希望参考规范以获取更多详细信息(单击链接),以使自己确信我是对的(我很可能犯了一个错误).
另外,为了简化一点,我将使用一个更简单的代码示例.主要区别在于它将Function
替换为Supplier
,因此正在使用的类型和参数更少.这是完整的代码段,可再现您描述的行为:
public class TypeInference {
static long getLong() { return 1L; }
static <R> void with(Supplier<R> supplier, R value) {}
static <R, F extends Supplier<R>> void withX(F supplier, R value) {}
public static void main(String[] args) {
with(TypeInference::getLong, "Not a long"); // Compiles
withX(TypeInference::getLong, "Also not a long"); // Does not compile
}
}
让我们通过 类型推断 过程:
with
我们有:
with(TypeInference::getLong, "Not a long");
初始绑定集 B 0 是:
-
R <: Object
所有参数表达式均为 与适用性有关 .
因此,为 适用性推断 , C ,是:
-
TypeInference::getLong
与Supplier<R>
兼容
-
"Not a long"
与R
兼容
此 减少 绑定到以下项的集合 B 2 :
-
R <: Object
(来自 B 0 ) -
Long <: R
(从第一个约束开始) -
String <: R
(来自第二个约束)
因为它不包含绑定的' false '和(我假设) 调用类型推断 .
具有新的 input 和 output 变量的新约束集 C 为:
-
TypeInference::getLong
与Supplier<R>
兼容- 输入变量:无
- 输出变量:
R
这不包含 input 和 output 变量之间的相互依赖性,因此可以是 分辨率 成功,编译器松了一口气!
withX
我们有:
withX(TypeInference::getLong, "Also not a long");
初始绑定集 B 0 是:
-
R <: Object
-
F <: Supplier<R>
仅第二个参数表达式为 适用性推断 , C ,是:
-
"Also not a long"
与R
兼容
此 减少 绑定到以下项的集合 B 2 :
-
R <: Object
(来自 B 0 ) -
F <: Supplier<R>
(来自 B 0 ) -
String <: R
(来自约束)
再次,因为它不包含绑定的' false '和 调用类型推断 再次...
这一次,具有相关的 input 和 output 变量的新约束集 C 为:
-
TypeInference::getLong
与F
兼容- 输入变量:
F
- 输出变量:无
- 输入变量:
同样,我们在 input 和 output 变量之间没有相互依赖性.但是这一次,有一个输入变量(F
),所以我们必须减少.因此,我们从绑定集 B 2 开始.
-
我们按如下确定子集
V
:给出一组要解析的推理变量,使
V
为该集合与该集合中至少一个变量的分辨率所依赖的所有变量的并集.根据 B 2 中的第二个边界,
F
的分辨率取决于R
,所以V := {F, R}
. -
我们根据规则选择
V
的子集:让
{ α1, ..., αn }
是V
中未实例化变量的非空子集,这样i)对于所有i (1 ≤ i ≤ n)
,如果αi
取决于变量β
的分辨率,则要么β
具有实例化或存在某些j
这样的β = αj
; ii)没有具有该属性的{ α1, ..., αn }
的非空正确子集.满足此属性的
V
的唯一子集是{R}
. -
使用第三个界限(
String <: R
)实例化R = String
并将其合并到我们的界限集中.R
现在已解决,第二个边界有效地变为F <: Supplier<String>
. -
使用(修订的)第二个边界,我们实例化
F = Supplier<String>
.F
现在已解决.
现在F
已解决,我们可以继续进行减少,使用新的约束:
-
TypeInference::getLong
与Supplier<String>
兼容
- ...减少到
Long
与String
兼容
- ...减少为 false
...,我们得到一个编译器错误!
扩展示例"的其他注释
问题中的扩展示例着眼于一些有趣的案例,而上述工作并未直接涵盖这些案例:
- 值类型是方法返回类型(
Integer <: Number
)的子类型 - 其中功能接口与推断类型相反(即
Consumer
而不是Supplier
)
特别是,给定的调用中有3个可能暗示了与解释中所述的不同"编译器行为:
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
这3个中的第二个将经历与上述withX
完全相同的推理过程(只需将Long
替换为Number
,将String
替换为Integer
).这说明了在类设计中不应该依赖这种失败的类型推断行为的另一个原因,因为在此处无法进行编译可能不是期望的行为.
对于其他2个(以及您实际上希望进行的涉及Consumer
的其他任何调用),如果您通过上述方法之一(例如, with
代表第一个,withX
代表第三个).您只需注意一个小变化即可:
- 第一个参数(
t::setNumber
与Consumer<R>
兼容)的约束将Why is lambda return type not checked at compile time. I found using the methodwithX()
like.withX(MyInterface::getLength, "I am not a Long")
produces the wanted compile time error:
The type of getLength() from the type BuilderExample.MyInterface is long, this is incompatible with the descriptor's return type: String
while using the method
with()
does not.full example:
import java.util.function.Function; public class SO58376589 { public static class Builder<T> { public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) { return this; } public <R> Builder<T> with(Function<T, R> getter, R returnValue) { return this; } } static interface MyInterface { public Long getLength(); } public static void main(String[] args) { Builder<MyInterface> b = new Builder<MyInterface>(); Function<MyInterface, Long> getter = MyInterface::getLength; b.with(getter, 2L); b.with(MyInterface::getLength, 2L); b.withX(getter, 2L); b.withX(MyInterface::getLength, 2L); b.with(getter, "No NUMBER"); // error b.with(MyInterface::getLength, "No NUMBER"); // NO ERROR !! b.withX(getter, "No NUMBER"); // error b.withX(MyInterface::getLength, "No NUMBER"); // error !!! } }
javac SO58376589.java
SO58376589.java:32: error: method with in class Builder<T> cannot be applied to given types; b.with(getter, "No NUMBER"); // error ^ required: Function<MyInterface,R>,R found: Function<MyInterface,Long>,String reason: inference variable R has incompatible bounds equality constraints: Long lower bounds: String where R,T are type-variables: R extends Object declared in method <R>with(Function<T,R>,R) T extends Object declared in class Builder SO58376589.java:34: error: method withX in class Builder<T> cannot be applied to given types; b.withX(getter, "No NUMBER"); // error ^ required: F,R found: Function<MyInterface,Long>,String reason: inference variable R has incompatible bounds equality constraints: Long lower bounds: String where F,R,T are type-variables: F extends Function<MyInterface,R> declared in method <R,F>withX(F,R) R extends Object declared in method <R,F>withX(F,R) T extends Object declared in class Builder SO58376589.java:35: error: incompatible types: cannot infer type-variable(s) R,F b.withX(MyInterface::getLength, "No NUMBER"); // error ^ (argument mismatch; bad return type in method reference Long cannot be converted to String) where R,F,T are type-variables: R extends Object declared in method <R,F>withX(F,R) F extends Function<T,R> declared in method <R,F>withX(F,R) T extends Object declared in class Builder 3 errors
Extended Example
The following example shows the different behaviour of method and type parameter boiled down to a Supplier. In addition it shows the difference to a Consumer behaviour for a type parameter. And it shows it does not make a difference wether it is a Consumer or Supplier for a method parameter.
import java.util.function.Consumer; import java.util.function.Supplier; interface TypeInference { Number getNumber(); void setNumber(Number n); @FunctionalInterface interface Method<R> { TypeInference be(R r); } //Supplier: <R> R letBe(Supplier<R> supplier, R value); <R, F extends Supplier<R>> R letBeX(F supplier, R value); <R> Method<R> let(Supplier<R> supplier); // return (x) -> this; //Consumer: <R> R lettBe(Consumer<R> supplier, R value); <R, F extends Consumer<R>> R lettBeX(F supplier, R value); <R> Method<R> lett(Consumer<R> consumer); public static void main(TypeInference t) { t.letBe(t::getNumber, (Number) 2); // Compiles :-) t.lettBe(t::setNumber, (Number) 2); // Compiles :-) t.letBe(t::getNumber, 2); // Compiles :-) t.lettBe(t::setNumber, 2); // Compiles :-) t.letBe(t::getNumber, "NaN"); // !!!! Compiles :-( t.lettBe(t::setNumber, "NaN"); // Does not compile :-) t.letBeX(t::getNumber, (Number) 2); // Compiles :-) t.lettBeX(t::setNumber, (Number) 2); // Compiles :-) t.letBeX(t::getNumber, 2); // !!! Does not compile :-( t.lettBeX(t::setNumber, 2); // Compiles :-) t.letBeX(t::getNumber, "NaN"); // Does not compile :-) t.lettBeX(t::setNumber, "NaN"); // Does not compile :-) t.let(t::getNumber).be(2); // Compiles :-) t.lett(t::setNumber).be(2); // Compiles :-) t.let(t::getNumber).be("NaN"); // Does not compile :-) t.lett(t::setNumber).be("NaN"); // Does not compile :-) } }
解决方案This is a really interesting question. The answer, I'm afraid, is complicated.
tl;dr
Working out the difference involves some quite in-depth reading of Java's type inference specification, but basically boils down to this:
- All other things equal, the compiler infers the most specific type it can.
- However, if it can find a substitution for a type parameter that satisfies all the requirements, then compilation will succeed, however vague the substitution turns out to be.
- For
with
there is a (admittedly vague) substitution that satisfies all the requirements onR
:Serializable
- For
withX
, the introduction of the additional type parameterF
forces the compiler to resolveR
first, without considering the constraintF extends Function<T,R>
.R
resolves to the (much more specific)String
which then means that inference ofF
fails.
This last bullet point is the most important, but also the most hand-wavy. I can't think of a better concise way of phrasing it, so if you want more details, I suggest you read the full explanation below.
Is this intended behaviour?
I'm gonna go out on a limb here, and say no.
I'm not suggesting there's a bug in the spec, more that (in the case of
withX
) the language designers have put their hands up and said "there are some situations where type inference gets too hard, so we'll just fail". Even though the compiler's behaviour with respect towithX
seems to be what you want, I would consider that to be an incidental side-effect of the current spec, rather than a positively intended design decision.This matters, because it informs the question Should I rely on this behaviour in my application design? I would argue that you shouldn't, because you can't guarantee that future versions of the language will continue to behave this way.
While it's true that language designers try very hard not to break existing applications when they update their spec/design/compiler, the problem is that the behaviour you want to rely on is one where the compiler currently fails (i.e. not an existing application). Langauge updates turn non-compiling code into compiling code all the time. For example, the following code could be guaranteed not to compile in Java 7, but would compile in Java 8:
static Runnable x = () -> System.out.println();
Your use-case is no different.
Another reason I'd be cautious about using your
withX
method is theF
parameter itself. Generally, a generic type parameter on a method (that doesn't appear in the return type) exists to bind the types of multiple parts of the signature together. It's saying:I don't care what
T
is, but want to be sure that wherever I useT
it's the same type.Logically, then, we would expect each type parameter to appear at least twice in a method signature, otherwise "it's not doing anything".
F
in yourwithX
only appears once in the signature, which suggests to me a use of a type parameter not inline with the intent of this feature of the language.An alternative implementation
One way to implement this in a slightly more "intended behaviour" way would be to split your
with
method up into a chain of 2:public class Builder<T> { public final class With<R> { private final Function<T,R> method; private With(Function<T,R> method) { this.method = method; } public Builder<T> of(R value) { // TODO: Body of your old 'with' method goes here return Builder.this; } } public <R> With<R> with(Function<T,R> method) { return new With<>(method); } }
This can then be used as follows:
b.with(MyInterface::getLong).of(1L); // Compiles b.with(MyInterface::getLong).of("Not a long"); // Compiler error
This doesn't include an extraneous type parameter like your
withX
does. By breaking down the method into two signatures, it also better expresses the intent of what you're trying to do, from a type-safety point of view:- The first method sets up a class (
With
) that defines the type based on the method reference. - The scond method (
of
) constrains the type of thevalue
to be compatible with what you previously set up.
The only way a future version of the language would be able to compile this is if the implemented full duck-typing, which seems unlikely.
One final note to make this whole thing irrelevant: I think Mockito (and in particular its stubbing functionality) might basically already do what you're trying to achieve with your "type safe generic builder". Maybe you could just use that instead?
The full(ish) explanation
I'm going to work through the type inference procedure for both
with
andwithX
. This is quite long, so take it slowly. Despite being long, I've still left quite a lot of details out. You may wish to refer to the spec for more details (follow the links) to convince yourself that I'm right (I may well have made a mistake).Also, to simplify things a little, I'm going to use a more minimal code sample. The main difference is that it swaps out
Function
forSupplier
, so there are less types and parameters in play. Here's a full snippet that reproduces the behaviour you described:public class TypeInference { static long getLong() { return 1L; } static <R> void with(Supplier<R> supplier, R value) {} static <R, F extends Supplier<R>> void withX(F supplier, R value) {} public static void main(String[] args) { with(TypeInference::getLong, "Not a long"); // Compiles withX(TypeInference::getLong, "Also not a long"); // Does not compile } }
Let's work through the type applicability inference and type inference procedure for each method invocation in turn:
with
We have:
with(TypeInference::getLong, "Not a long");
The initial bound set, B0, is:
R <: Object
All parameter expressions are pertinent to applicability.
Hence, the initial constraint set for applicability inference, C, is:
TypeInference::getLong
is compatible withSupplier<R>
"Not a long"
is compatible withR
This reduces to bound set B2 of:
R <: Object
(from B0)Long <: R
(from the first constraint)String <: R
(from the second constraint)
Since this does not contain the bound 'false', and (I assume) resolution of
R
succeeds (givingSerializable
), then the invocation is applicable.So, we move on to invocation type inference.
The new constraint set, C, with associated input and output variables, is:
TypeInference::getLong
is compatible withSupplier<R>
- Input variables: none
- Output variables:
R
This contains no interdependencies between input and output variables, so can be reduced in a single step, and the final bound set, B4, is the same as B2. Hence, resolution succeeds as before, and the compiler breathes a sigh of relief!
withX
We have:
withX(TypeInference::getLong, "Also not a long");
The initial bound set, B0, is:
R <: Object
F <: Supplier<R>
Only the second parameter expression is pertinent to applicability. The first one (
TypeInference::getLong
) is not, because it meets the following condition:If
m
is a generic method and the method invocation does not provide explicit type arguments, an explicitly typed lambda expression or an exact method reference expression for which the corresponding target type (as derived from the signature ofm
) is a type parameter ofm
.Hence, the initial constraint set for applicability inference, C, is:
"Also not a long"
is compatible withR
This reduces to bound set B2 of:
R <: Object
(from B0)F <: Supplier<R>
(from B0)String <: R
(from the constraint)
Again, since this does not contain the bound 'false', and resolution of
R
succeeds (givingString
), then the invocation is applicable.Invocation type inference once more...
This time, the new constraint set, C, with associated input and output variables, is:
TypeInference::getLong
is compatible withF
- Input variables:
F
- Output variables: none
- Input variables:
Again, we have no interdependencies between input and output variables. However this time, there is an input variable (
F
), so we must resolve this before attempting reduction. So, we start with our bound set B2.We determine a subset
V
as follows:Given a set of inference variables to resolve, let
V
be the union of this set and all variables upon which the resolution of at least one variable in this set depends.By the second bound in B2, the resolution of
F
depends onR
, soV := {F, R}
.We pick a subset of
V
according to the rule:let
{ α1, ..., αn }
be a non-empty subset of uninstantiated variables inV
such that i) for alli (1 ≤ i ≤ n)
, ifαi
depends on the resolution of a variableβ
, then eitherβ
has an instantiation or there is somej
such thatβ = αj
; and ii) there exists no non-empty proper subset of{ α1, ..., αn }
with this property.The only subset of
V
that satisfies this property is{R}
.Using the third bound (
String <: R
) we instantiateR = String
and incorporate this into our bound set.R
is now resolved, and the second bound effectively becomesF <: Supplier<String>
.Using the (revised) second bound, we instantiate
F = Supplier<String>
.F
is now resolved.
Now that
F
is resolved, we can proceed with reduction, using the new constraint:TypeInference::getLong
is compatible withSupplier<String>
- ...reduces to
Long
is compatible withString
- ...which reduces to false
... and we get a compiler error!
Additional notes on the 'Extended Example'
The Extended Example in the question looks at a few interesting cases that aren't directly covered by the workings above:
- Where the value type is a subtype of the method return type (
Integer <: Number
) - Where the functional interface is contravariant in the inferred type (i.e.
Consumer
rather thanSupplier
)
In particular, 3 of the given invocations stand out as potentially suggesting 'different' compiler behaviour to that described in the explanations:
t.lettBe(t::setNumber, "NaN"); // Does not compile :-) t.letBeX(t::getNumber, 2); // !!! Does not compile :-( t.lettBeX(t::setNumber, 2); // Compiles :-)
The second of these 3 will go through exactly the same inference process as
withX
above (just replaceLong
withNumber
andString
withInteger
). This illustrates yet another reason why you shouldn't rely on this failed type inference behaviour for your class design, as the failure to compile here is likely not a desirable behaviour.For the other 2 (and indeed any of the other invocations involving a
Consumer
you wish to work through), the behaviour should be apparent if you work through the type inference procedure laid out for one of the methods above (i.e.with
for the first,withX
for the third). There's just one small change you need to take note of:- The constraint on the first parameter (
t::setNumber
is compatible withConsumer<R>
) will reduce toR <: Number
instead ofNumber <: R
as it does forSupplier<R>
. This is described in the linked documentation on reduction.
I leave it as an excercise for the reader to carfully work through one of the above procedures, armed with this piece of additional knowledge, to demonstrate to themselves exactly why a particular invocation does or doesn't compile.
这篇关于为什么类型参数比方法参数更强大的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!