简体   繁体   English

toUpperCase() 如何在 Java 8 中实现 apply()?

[英]How can toUpperCase() implement apply() in Java 8?

I'm learning Java 8 with Lambda and Streams and method reference.我正在学习 Java 8 和 Lambda 以及 Streams 和方法参考。 And I have a question regarding the example below.我对下面的例子有疑问。

Optional<String> s = Optional.of("test");
System.out.println(s.map(String::toUpperCase).get());

I don't understand how is it possible to use String::toUpperCase as an input for this map() method.我不明白如何使用String::toUpperCase作为此map()方法的输入。 This is the method implementation:这是方法实现:

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

So it requires a Function interface, and it has this apply() method: R apply(T t);所以它需要一个 Function 接口,它有这个 apply() 方法: R apply(T t); This method has an input argument.此方法有一个输入参数。

And toUpperCase() method doesn't have any argument:toUpperCase()方法没有任何参数:

public String toUpperCase() {
    return toUpperCase(Locale.getDefault());
}

I know that if the abstract method has one argument then the implemented method should have one argument of the same type.我知道如果抽象方法有一个参数,那么实现的方法应该有一个相同类型的参数。 How can toUpperCase() method implement apply(T t) method from Function interface? toUpperCase()方法如何从 Function 接口实现apply(T t)方法?

I try to recreate the same conditions:我尝试重新创建相同的条件:

I create a functional interface:我创建了一个功能界面:

public interface Interf {
    String m1(String value);
}

Then I create a class with the method reference for m1():然后我使用 m1() 的方法参考创建一个 class:

public class Impl {
    public String value;
    
    public String toUpp() {
        return value.toUpperCase();
    }
}

And here is a class for test:这是一个 class 用于测试:

public class Test {
    public static void main(String[] args) {
        Interf i = String::toUpperCase;
        System.out.println(i.m1("hey"));

        Interf i1 = Impl::toUpp;
        System.out.println(i.m1("hello"));
    }
}

There is no issue at this statement: Interf i = String::toUpperCase;此语句没有问题: Interf i = String::toUpperCase; but there is a compilation error on this line: Interf i1 = Impl::toUpp;但是这一行有一个编译错误: Interf i1 = Impl::toUpp; . . It says:它说:

Non-static method cannot be referenced from a static context

But toUpperCase() is also a non-static method.但是toUpperCase()也是一种非静态方法。 And even if I make the toUpp() static, it is still not working, it is working only if I add a String argument as an input argument for toUpp() .即使我将toUpp()设置为 static,它仍然无法正常工作,只有当我添加一个 String 参数作为toUpp()的输入参数时它才会工作。 But then why is it working for String::toUpperCase ?但是为什么它适用于String::toUpperCase呢?

How can toUpperCase() method implement apply(T t) method from Function interface? toUpperCase() 方法如何从 Function 接口实现 apply(T t) 方法?

The method reference String::toUpperCase has unbound receiver.方法引用String::toUpperCase有未绑定的接收者。 Java 8: Difference between method reference Bound Receiver and UnBound Receiver Then the argument T t would be the receiver Java 8: 方法参考 Bound Receiver 和 UnBound Receiver 之间的区别 那么参数T t将是接收者

In your example在你的例子中

     public <U, T> Optional<U> map(Function<? super T, ? extends U> mapper) {
...
            return Optional.ofNullable(mapper.apply(value));
        }

by calling map(String::toUpperCase)通过调用map(String::toUpperCase)

If value , let's say equal to "Example String" , would be the receiver so mapper.apply("Example String");如果value等于"Example String" ,那么mapper.apply("Example String");将是接收者would be equivalent to "Example String".toUpperCase();将等同于"Example String".toUpperCase();

TL;DR长话短说

Parameters that a Method reference is expected to consume according to the contract imposed by a Function interface it implements are NOT necessarily the same as parameters of the method used in the Method reference.根据由它实现的Function接口强加的合同,方法引用预期使用的参数不一定与方法引用中使用的方法的参数相同。

This answer is a journey from this common misconception towards understanding all syntactical flavors of Method references.这个答案是从这种常见的误解到理解方法引用的所有语法风格的旅程。

Let's take tiny baby steps to dispel misunderstanding, starting from the definition of the Method reference.让我们采取一些小步骤来消除误解,从方法引用的定义开始。

What is Method reference什么是方法引用

Here is the definition of what Method references are according to the Java Language Specification 15.13.以下是根据 Java 语言规范15.13 定义的方法引用。 Method Reference Expressions 方法引用表达式

A method reference expression is used to refer to the invocation of a method without actually performing the invocation .方法引用表达式用于在实际执行调用的情况下引用方法的调用 Certain forms of method reference expression also allow class instance creation (§15.9) or array creation (§15.10) to be treated as if it were a method invocation.某些 forms 方法引用表达式还允许将 class 实例创建(§15.9)或数组创建(§15.10)视为方法调用。

So, a Method reference is a mean of referring the method-invocation with invoking a method.因此,方法引用是通过调用方法来引用方法调用的一种方式。 Or in other words, it's a way of describing a certain behavior by delegating to the existing functionality.或者换句话说,它是一种通过委托现有功能来描述特定行为的方式。

Let's take a small detour and have a look at the sibling of a Method reference, a Lambda expression.让我们绕道而行,看看方法引用的同级,即 Lambda 表达式。

Lambda expressions are also a way to describe behavior (without executing it), and both lambdas and Method references should conform to a Functional interface. Lambda 表达式也是一种描述行为的方式(无需执行),lambda 和方法引用都应符合 Functional 接口。 Let's declare some lambdas.让我们声明一些 lambda。

Consider, we have a domain class Foo and utility class FooUtils .考虑一下,我们有一个域 class Foo和实用程序 class FooUtils

public class FooUtils {
    
    public static Foo doSomethingWithFoo(Foo foo) {
        // do something
        return new Foo();
    }
}

And we need to define a function of type UnaryOperator<Foo> , let's start with writing a lambda expression:而我们需要定义一个 function 类型的UnaryOperator<Foo> ,让我们从写一个 lambda 表达式开始:

UnaryOperator<Foo> fooChanger = foo -> FooUtils.doSomethingWithFoo(foo);

Lambda receives an instance of Foo as an argument and feeds it into the existing utility method. Lambda 接收一个Foo实例作为参数并将其提供给现有的实用程序方法。 Quite simple, right?很简单,对吧? Nothing special happens inside the lambda's body, and since have defined the type as UnaryOperator<Foo> the lambda should expect Foo . lambda 体内没有发生任何特殊情况,并且由于已将类型定义为UnaryOperator<Foo> lambda 应该期望Foo Everything is absolutely predictable, isn't it?一切都是绝对可以预见的,不是吗? Now the question is: can we alternate that?现在的问题是:我们可以替代吗?

Sure, we can!我们当然可以!

UnaryOperator<Foo> fooChanger = FooUtils::doSomethingWithFoo;

That's where a Method reference comes to the rescue.这就是 Method 参考来拯救的地方。 It provides a shortened syntax by:它通过以下方式提供缩短的语法:

1. Dropping the lambda's arguments (they are still there, we're simply not displaying them because we know what they are). 1.删除 lambda 的 arguments (它们仍然存在,我们只是不显示它们,因为我们知道它们是什么)。

2. Removing the parentheses after the method name. 2.去掉方法名后面的括号 Again the method declaration is known (and let's assume that there are no ambiguities), and we are not performing any prior transformation with arguments, and we are not using any additional arguments apart from those that should come according to the contract of the Functional interface.同样,方法声明是已知的(假设没有歧义),并且我们没有对 arguments 执行任何先前的转换,并且我们没有使用任何额外的 arguments 除了那些应该根据 Functional 接口的约定而来的. Only in this case everything is predictable and can a method reference.只有在这种情况下,一切都是可以预见的,才能有方法参考。

Key takeaways:要点:

  • you may think of method references as if they are shortened lambdas .您可能会认为方法引用是缩短的 lambda
  • the arguments of the method reference are the same the equivalent lambda receives because they are implementations to the same interface.方法引用的 arguments 与接收到的等效 lambda 相同,因为它们是同一接口的实现。 These parameters are still there implicitly, just dropped for the purpose of conciseness.这些参数仍然隐含地存在,只是为了简洁起见而被删除。 And more importantly, parameters that a method reference consumes should not be confused with parameters expected method it refers to.更重要的是,方法引用使用的参数不应与它引用的预期方法的参数相混淆。 In other word the first parameters an input of the method reference (and they are compliant with the contract defined by the interface) and the latter related to what happens inside the reference, and have no connection to the first ones.换句话说,第一个参数是方法引用的输入(并且它们符合接口定义的契约),而后者与引用内部发生的事情有关,与第一个参数无关。

More examples更多例子

Let's examine a few more examples.让我们再看几个例子。 Let's say we have a simple object representing a coin with a single property isHeads describing which side the coin is showing (ie heads or tails ).假设我们有一个简单的 object 表示一枚硬币,它有一个属性isHeads描述硬币显示的是哪一面(即正面反面)。

public static class Coin {
    public static final Random RAND = new Random();
    
    private final boolean isHeads;

    public Coin() {
        this.isHeads = RAND.nextBoolean();
    }

    private Coin(boolean isHeads) {
        this.isHeads = isHeads;
    }

    public Coin reverse() {
        return new Coin(!isHeads);
    }

    public boolean isHeads() {
        return isHeads;
    }
}

Let's generate a coin.让我们生成一个硬币。 For that we can use implement of Supplier which very generous, supplier doesn't receive arguments, it produces a value.为此,我们可以使用非常慷慨的供应商工具, Supplier商不会收到 arguments,它会产生一个值。 Let's definable both a lambda and a reference让我们同时定义一个 lambda 和一个参考

Supplier<Coin> coinProducer = () -> new Coin(); // no argument required according to the contract of Supplier
Supplier<Coin> coinProducer1 = Coin::new;       // Supplier expressed as a reference to no-args constructor

Both receive no argument (as per contract of the Supplier), both refer to the no-args constructor.两者都没有参数(根据供应商的合同),都引用无参数构造函数。

Now let's consider the predicates determining if the coin shows heads implemented via a lambda and a method reference:现在让我们考虑通过 lambda 和方法参考来确定硬币是否显示正面的谓词:

Predicate<Coin> isHeads = coin -> coin.isHeads();
Predicate<Coin> isHeads1 = Coin::isHeads;

Again, both the lambda and the method reference are compliant with the Predicate's contract and both receive an instance of Coin as an argument (it can't be otherwise, simply concise syntax of the method reference doesn't show that).同样,lambda 和方法引用都符合 Predicate 的约定,并且都接收一个Coin实例作为参数(不可能是其他情况,方法引用的简洁语法并没有表明这一点)。

So far, so good?到目前为止,一切都很好? Let's move further and try another way to obtain a Coin , let's define a Function :让我们更进一步,尝试另一种获取Coin的方法,让我们定义一个Function

Function<Boolean, Coin> booleanToCoin = value -> new Coin(value);
Function<Boolean, Coin> booleanToCoin1 = Coin::new;

Now both the lambda and the reference are consuming a boolean value and making use of the parameterized constructor.现在 lambda 和引用都在使用boolean值并使用参数化构造函数。 Did not notice that method reference describing Supplier<Coin> and Function<Boolean, Coin> looks identical.没有注意到描述Supplier<Coin>Function<Boolean, Coin>的方法参考看起来相同。

Reminder: both Lambda expressions and Method references have no type by itself .提醒: Lambda 表达式和方法引用本身都没有类型 They are so-called poly-expressions, which means their type should be inferred by the compiler based on the context in which they appear.它们是所谓的多表达式,这意味着它们的类型应该由编译器根据它们出现的上下文来推断。 Both the lambda and the reference should conform to a Functional interface, that their boss, interface says who they are and what they are doing. lambda 和引用都应该符合功能接口,即他们的老板接口说明他们是谁以及他们在做什么。

In all examples described earlier, arguments of consumed by a method reference appeared to be the same as the ones expected by the referenced method, but it's not mandatory for them to be the identical.在前面描述的所有示例中,方法引用消耗的 arguments 似乎与引用方法所期望的相同,但并不强制要求它们相同。 It's time to examine a couple or examples where it not the case to dispel the illusions.是时候检查一些事实并非如此的例子来消除幻想了。

Let's consider a UnaryOperator reversing a coin:让我们考虑一个UnaryOperator反转硬币:

UnaryOperator<Coin> coinFlipper = coin -> coin.reverse(); // UnaryOperator requires one argument
UnaryOperator<Coin> coinFlipper1 = Coin::reverse;         // UnaryOperator still requires one argument expressed as a reference to no arg method

All implementations of the UnaryOperator receive a Coin instance as an argument, and another coin is being produced as a result of the invocation of reverse() . UnaryOperator的所有实现都接收一个Coin实例作为参数,并且由于调用reverse()正在生成另一个硬币。 The fact that reverse is parameterless is not an issue, because we concerned about what it produces, and not what it consumes. reverse 是无参数的这一事实不是问题,因为我们关心的是它产生什么,而不是它消耗什么。

Let's try to define a tougher method reference.让我们尝试定义一个更严格的方法参考。 To begin with, introduce in the Coin class a new instance method called xor() , which is immensely useful for XOR-ing two coins:首先,在Coin class 中引入一个名为xor()的新实例方法,这对于对两个硬币进行异或运算非常有用:

public Coin xor(Coin other) {
    return new Coin(isHeads ^ other.isHeads);
}

Now when two object come into play we have more possibilities, let's start with the simplest case one by defining a UnariOperator :现在,当两个 object 发挥作用时,我们有更多的可能性,让我们从最简单的情况开始,定义一个UnariOperator

final Coin baseCoin = new Coin();
UnaryOperator<Coin> xorAgainstBase = coin -> baseCoin.xor(coin);
UnaryOperator<Coin> xorAgainstBase1 = baseCoin::xor;

In the above example an instance of Coin defined outside the function is used to perform the transformation via the instance-method.在上面的示例中,在 function 之外定义的Coin实例用于通过实例方法执行转换。

A little bit more compilicated case would be a BinaryOperator for XOR-ing a couple of coins might look like this:更复杂一点的情况是BinaryOperator用于对几个硬币进行异或运算可能如下所示:

BinaryOperator<Coin> xor = (coin1, coin2) -> coin1.xor(coin2);
BinaryOperator<Coin> xor1 = Coin::xor;

Now we have two arguments coming as an input and a Coin instance should be produce as an output as per BinaryOperator s contract.现在我们有两个 arguments 作为输入,并且根据BinaryOperator的合约,应该将Coin实例生成为 output。

The interesting thing is the first argument serves as an instance on which the method xor() would be invoked, and the second is passed to the method ( note that xor() expect only one argument).有趣的是,第一个参数用作调用方法xor()的实例,第二个参数传递给该方法(请注意xor()只需要一个参数)。

You might ask what would happen if there would be another method for XOR-ing coins.你可能会问,如果有另一种异或硬币的方法会发生什么。 A static method expecting two arguments:一个static方法需要两个 arguments:

public static Coin xor(Coin coin1, Coin coin2) {
    return new Coin(coin1.isHeads ^ coin2.isHeads);
}

Then the compiler would fail to resolve the method reference, because here we have more the one potentially applicable method a note of them can be considered to be more specific than the other since the types of arguments are the same.然后编译器将无法解析方法引用,因为这里我们有更多一个可能适用的方法,其中一个注释可以被认为比另一个更具体,因为 arguments 的类型是相同的。 That would cause a compilation error.那会导致编译错误。 But if we would have either of them (not both together), reference Coin::xor would work fine would be fine.但是,如果我们有其中一个(不是两个都在一起),那么引用Coin::xor就可以了。

Types Method references类型方法参考

Basically, the examples that we have walked through covered all the types of method references.基本上,我们走过的例子涵盖了所有类型的方法引用。 Let's enumerate them.让我们列举一下。

The official tutorial provided by Oracle re are four kinds of method references: Oracle提供的官方教程四种方法参考:

  1. Reference to a Static method参考一个Static的方法
Class::staticMethod

Example Coin::xor which refers to the static method xor(Coin coin1, Coin coin2) .示例Coin::xor指的是 static 方法xor(Coin coin1, Coin coin2)

Examples with standard JDK-classes:标准 JDK 类的示例:

BinaryOperator<Integer> sum = Integer::sum; // (i1, i2) -> Integer.sum(i1, i2)

BiFunction<CharSequence, Iterable<CharSequence>, String> iterableToString 
    = String::join; // (delimiter, strings) -> String.join(delimiter, strings)
  1. Reference to an instance method of a particular object引用特定的实例方法 object
instance::instanceMethod

The example illustrating this case would the usage of the instance method xor(Coin other) with a coin defined outside the function, which is internaly used to invoke xor() on it passing the function-argument into the method.说明这种情况的示例将使用实例方法xor(Coin other)和在 function 之外定义的硬币,它在内部用于调用xor()并将函数参数传递给方法。

final Coin baseCoin = new Coin();
UnaryOperator<Coin> xorAgainstBase1 = baseCoin::xor; // same as coin -> baseCoin.xor(coin)

Examples with standard JDK-classes:标准 JDK 类的示例:

Set<Foo> fooSet = // initialing the Set
Predicate<Foo> isPresentInFooSet = fooSet::contains;
  1. Reference to an Instance Method of an Arbitrary Object of a Particular Type引用特定类型的任意 Object 的实例方法
ContainingType::methodName

In this case method refernce operates on an instance that comes as an argument (we would have reference to it only it we would use a lambda), therefore containing type , which can be tha actual type or one the super types, is used to refer to this instance.在这种情况下,方法 reference 对作为参数出现的实例进行操作(我们将只引用它,我们将使用 lambda),因此包含 type ,它可以是实际类型或超类型之一,用于引用到这个例子。

An example would a Predicate checking if the coin shows heads Coin::isHeads .一个示例是Predicate检查硬币是否显示正面Coin::isHeads

Examples with standard JDK-classes:标准 JDK 类的示例:

Function<List<String>, Stream<String>> toStream = Collection::stream;
        
List<List<String>> lists = List.of(List.of("a", "b", "c"), List.of("x", "y", "z"));
        
List<String> strings1 = lists.stream()
    .flatMap(toStream)
    .toList();

// A slightly more complicate example taken from the linked tutorial
// Equivalent lambda: (a, b) -> a.compareToIgnoreCase(b)

String[] stringArray = { "Barbara", "James", "Mary" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
  1. Reference to a Constructor引用构造函数
Class::staticMethod

We have cove this case already with the following examples:我们已经通过以下示例介绍了这种情况:

  • Supplier<Coin> refering to no args-constracor implemented as Coin::new ; Supplier<Coin>指的是没有 args-constracor 实现为Coin::new
  • Function<Boolean, Coin> which makes use of the single-arg constructor by passing incoming boolean value also expressed as Coin::new`. Function<Boolean, Coin> which makes use of the single-arg constructor by passing incoming value also expressed as Coin::new`。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM