为什么类型参数比方法参数更强大 [英] Why is a type parameter stronger then a method parameter

查看:42
本文介绍了为什么类型参数比方法参数更强大的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么是

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 (特别是其存根功能)可能已经基本上完成了您使用类型安全的通用生成器"要实现的目标.也许您可以改用它?

完整的解释

我将通过 <withwithX的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),所以我们必须减少,使用新的约束:

  1. TypeInference::getLong Supplier<String>
  2. 兼容
  3. ...减少到Long String
  4. 兼容
  5. ...减少为 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 method withX() 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 on R: Serializable
    • For withX, the introduction of the additional type parameter F forces the compiler to resolve R first, without considering the constraint F extends Function<T,R>. R resolves to the (much more specific) String which then means that inference of F 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 to withX 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 the F 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 use T 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 your withX 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 the value 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 and withX. 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 for Supplier, 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 with Supplier<R>
    • "Not a long" is compatible with R

    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 (giving Serializable), 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 with Supplier<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 of m) is a type parameter of m.

    Hence, the initial constraint set for applicability inference, C, is:

    • "Also not a long" is compatible with R

    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 (giving String), 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 with F
      • Input variables: F
      • Output variables: none

    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.

    1. 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 on R, so V := {F, R}.

    2. We pick a subset of V according to the rule:

      let { α1, ..., αn } be a non-empty subset of uninstantiated variables in V such that i) for all i (1 ≤ i ≤ n), if αi depends on the resolution of a variable β, then either β has an instantiation or there is some j 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}.

    3. Using the third bound (String <: R) we instantiate R = String and incorporate this into our bound set. R is now resolved, and the second bound effectively becomes F <: Supplier<String>.

    4. 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:

    1. TypeInference::getLong is compatible with Supplier<String>
    2. ...reduces to Long is compatible with String
    3. ...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 than Supplier)

    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 replace Long with Number and String with Integer). 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 with Consumer<R>) will reduce to R <: Number instead of Number <: R as it does for Supplier<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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆