简体   繁体   English

使功能接口实例自动调用其方法的正确语法

[英]Correct syntax for making a functional interface instance automatically call its method

I've been watching Douglas Schmidt classes on Parallel Java.我一直在看平行 Java 上的 Douglas Schmidt 课程。 He introduces Lambda x method referencing syntax discussion, highlighting how the last one is preferable, as it makes clearer what the code is actually doing, not what the programmer is trying to do with the code, even more than forEach approach.他介绍了 Lambda x 方法引用语法讨论,强调最后一种方法更可取,因为它更清楚代码实际在做什么,而不是程序员试图用代码做什么,甚至比forEach方法更清楚。

String[] names = {"first", "Second", "third"};
Arrays.sort(names, (n1,n2) -> n1.compareToIgnoreCase(n2));
Arrays.sort(names, String::compareToIgnoreCase); //preferable

For example, that approach mitigates the chances of programmer making mistakes inside lambda function: passing the wrong argument, inverting arguments order, adding collateral effects, etc.例如,这种方法减少了程序员在 lambda function 中出错的机会:传递错误的参数,反转 arguments 顺序,添加附带效果等。

Then he introduces Functional interfaces, an interface that contains only an abstract method, implementing its own interface runTest with an abstract method factorial() :然后他介绍了Functional接口,一个只包含一个抽象方法的接口,用抽象方法factorial()实现了自己的接口runTest

private static <T> void runTest(Function<T,T> factorial, T n) {
        System.out.println(n+ " factorial = " + factorial.apply(n));
    }
    
    private static class ParallelStreamFactorial{
        static BigInteger factorial(BigInteger n) {
            return LongStream
                    .rangeClosed(1, n.longValue())
                    .parallel()
                    .mapToObj(BigInteger::valueOf)
                    .reduce(BigInteger.ONE, BigInteger::multiply);
        }
    }

Calling it with the following syntax:使用以下语法调用它:

import java.math.BigInteger;
import java.util.function.Function;
import java.util.stream.LongStream;

public static void main(String[] args) {
        BigInteger n = BigInteger.valueOf(3);
        runTest(ParallelStreamFactorial::factorial, n);     
    }

The code works and prints代码有效并打印

3 factorial = 6

As I'm studying lambdas, I tried to interchange method reference syntax for lambda syntax, and managed to using:在学习 lambdas 时,我尝试将方法参考语法交换为 lambda 语法,并设法使用:

public static void main(String[] args) {
        BigInteger n = BigInteger.valueOf(3);
        runTest((number)->ParallelStreamFactorial.factorial(number), n);
    }

Which also worked.这也有效。

Then he proceeds to explain built-in interfaces, such as Predicate<T>{boolean test(T t);} , and that's where I got stuck.然后他继续解释内置接口,例如Predicate<T>{boolean test(T t);} ,这就是我卡住的地方。

I managed to implement a Predicate<Integer> that tests if the integer is bigger than 0 using the three syntaxes:我设法实现了一个Predicate<Integer> ,它使用以下三种语法测试 integer 是否大于 0:

  1. Instantiating an object myPredicate from a class that implements Predicate<Integer>从实现Predicate<Integer>的 class 实例化 object myPredicate
  2. Instantiating an object lambdaPredicate from a lambda从 lambda 实例化 object lambdaPredicate
  3. Instantiating an object methodReferencePredicate from a method reference:从方法引用实例化 object methodReferencePredicate
import java.util.function.Function;
import java.util.function.Predicate;

public class MyPredicates {
    
    public static void main(String[] args) {
        
        Predicate<Integer> constructorPredicate = new  myPredicate();
        System.out.println(constructorPredicate.test(4));
        
        Predicate<Integer> lambdaPredicate = (number)-> number > 0;
        System.out.println(lambdaPredicate.test(4));
        
        Predicate<Integer> methodReferencePredicate = myMethodReference::myTest;
        System.out.println(methodReferencePredicate.test(4));

    }
    
    private static class myPredicate implements Predicate<Integer>{
        public boolean test(Integer t) {
            return t>0;
        }
    }
    
    private static class myMethodReference{
        public static boolean myTest(Integer t) {
            return t>0;
        }
    }
}

And then calling their .test() methods.然后调用他们的.test()方法。 They're all three working and printing true .他们都是三个工作和打印true

However I would like to "instantiate and call" everything in a single line, as he did in his example.但是,我想在一行中“实例化并调用”所有内容,就像他在他的示例中所做的那样。 It seems like his code is inferring the type of the argument passed (I may be wrong) but it's definitely running automatically.似乎他的代码在推断传递的参数的类型(我可能错了),但它肯定是自动运行的。

I tried different things:我尝试了不同的东西:

Predicate<Integer>(myMethodReference::myTest, 4);
Predicate(myMethodReference::myTest, 4);
Predicate<Integer>((number) -> myMethodReference.myTest(number), 4);
Predicate((number) -> myMethodReference.myTest(number), 4);

But none of them work.但它们都不起作用。

They throw:他们抛出:

Syntax error, insert ";" to complete LocalVariableDeclarationStatement

and

The method Predicate(myMethodReference::myTest, int) is undefined for the type MyPredicates

Errors.错误。 I also don't even know the name of what he's doing in that single line to properly search better on internet for references.我什至不知道他在那一行中所做的事情的名称,以便在互联网上更好地搜索参考。

What's the correct syntax for that, whether by method reference or lambdas?什么是正确的语法,无论是通过方法引用还是 lambdas?

You've made things far too complicated.你把事情弄得复杂了。

There is no point in lambdas if you want to 'execute them immediately'.如果您想“立即执行”,那么 lambdas毫无意义

Here is how you run your my test code 'immediately':这是您“立即”运行我的测试代码的方式:

System.out.println(number > 4);

Why mess with lambdas?为什么要搞乱 lambdas? They just make matters confusing here.他们只是在这里使事情变得混乱。

The very point of a lambda is two-fold: lambda 的要点有两个:

  1. A way to transmit code itself to other contexts.一种将代码本身传输到其他上下文的方法。
  2. Control flow abstraction.控制流抽象。

In java in particular, option 2 is an evil - it makes code ugly, harder to reason about, introduces pointless distractions, and in general should be avoided... unless you're employing it to avoid an even greater evil.特别是在 java 中,选项 2 是一种邪恶——它使代码变得丑陋,更难以推理,引入了毫无意义的干扰,通常应该避免......除非你使用它来避免更大的邪恶。 That happens plenty - for example, a reasonable 'stream chain' is generally better even though its control flow abstraction.这种情况很多——例如,一个合理的“流链”通常更好,即使它的控制流抽象。 I'd say this:我会这样说:

int total = list.stream()
  .filter(x -> x.length() < 5)
  .mapToInt(Integer::valueOf)
  .sum();

is the lesser evil compared to:与以下相比,是较小的邪恶:

int total = 0;
for (var x : list) {
  if (x.length() < 5) continue;
  total += Integer.parseInt(x);
}

but it is a pretty close call.但这是一个非常接近的电话。

Why is it 'evil'?为什么它是“邪恶的”? Because lambdas in java are non transparent in 3 important ways, and this non-transparency is a good thing in the first case, but a bad thing in the second.因为 java 中的 lambda 在三个重要方面是不透明的,这种不透明在第一种情况下是好事,但在第二种情况下是坏事 Specifically, lambdas are not transparent in these ways:具体来说,lambda 在这些方面是不透明的:

  1. Lambdas cannot change or even read local variables from outer scope unless they are (effectively) final. Lambda 不能更改甚至从外部 scope 读取局部变量,除非它们(有效)是最终的。
  2. Lambdas cannot throw checked exceptions even if the outer scope would handle them (because they catch them or the method you're in declared throws ThatException ).即使外部 scope 会处理它们,Lambda 也不能抛出已检查的异常(因为它们会捕获它们或者您声明的方法会throws ThatException )。
  3. Lambdas cannot do control flow. Lambdas 不能做控制流。 You can't break , continue , or return from within a lambda to outside of it.您不能从 lambda 内部breakcontinuereturn到外部。

These 3 things are all useful and important things to be doing when you're dealing with basic control flow.当您处理基本控制流时,这三件事都是有用且重要的事情。 Therefore, lambdas should be avoided as you create a bunch of problems and inflexibility by using them... unless you've avoided more complexity and inflexibility of course.因此,应该避免使用 lambda,因为您使用它们会产生一堆问题和不灵活……除非您当然避免了更多的复杂性和不灵活。 It's programming: Nothing is ever easy.它是编程:没有什么是容易的。

The notion of bundling up code is therefore much more useful, because those non-transparencies turn into upside:因此,捆绑代码的概念更加有用,因为那些不透明的东西变成了好处:

  1. If you take the lambda code and export it to someplace that runs that code much later and in another thread, what does it even mean to modify a local variable at that point?如果您使用 lambda 代码并将其导出到稍后在另一个线程中运行该代码的某个地方,那么此时修改局部变量意味着什么? The local variable is long gone (local vars are ordinarily declared on stack and disappear when the method that made them ends. That method has ended; your lambda survived this, and is now running in another context).局部变量早已不复存在(局部变量通常在堆栈上声明,并在生成它们的方法结束时消失。该方法已结束;您的 lambda 幸免于难,现在正在另一个上下文中运行)。 Do we now start marking local vars as volatile to avoid thead issues?我们现在是否开始将本地变量标记为volatile以避免出现问题? Oof.钱币。

  2. The fact that the outer code deals with a checked exception is irrelevant: The lexical scope that was available when you declared the lambda is no longer there, we've long ago moved past it.外部代码处理已检查异常的事实是无关紧要的:当您声明 lambda 时可用的词汇 scope 不再存在,我们很久以前就移过了它。

  3. Control flow - breaking out of or restarting a loop, or returning from a method.控制流 - 中断或重新启动循环,或从方法返回。 What loop?什么循环? What method?什么方法? They have already ended.他们已经结束了。 The code makes no sense.代码没有意义。

See?看? Lambda lack of transparency is in all ways great (because they make no sense), if your lambda is 'travelling'.如果您的 lambda 是“旅行”,那么 Lambda 缺乏透明度在各方面都很好(因为它们没有意义)。 Hence, lambdas are best used for this, they have no downsides at that point.因此,lambda 最适合用于此,它们在这一点上没有缺点。

Thus, let's talk about travelling lambdas: The very notion is to take code and not execute it.因此,让我们来谈谈旅行 lambda:其概念是获取代码而不是执行它。 Instead, you hand it off to other code that does whatever it wants.相反,你把它交给其他代码做它想做的任何事情。 It may run it 2 days from now when someone connects to your web server, using path /foobar .当有人使用路径/foobar连接到您的 web 服务器时,它可能会在 2 天后运行。 It may run every time someone adds a new entry to a TreeSet in order to figure out where in the tree the item should be placed (that's precisely the fate of the lambda you pass to new TreeSet<X>((a, b) -> compare-a-and-b-here) .每次有人向 TreeSet 添加新条目时,它可能会运行,以便确定项目应该放置在树中的哪个位置(这正是您传递给new TreeSet<X>((a, b) -> compare-a-and-b-here)

Even in control flow situations (which are to be avoided if possible), your lambda still travels, it just travels to place that does immediately ends up using it, but the point of the lambda remains control flow abstraction: You don't run the code in it, you hand your lambda off to something else which will then immediately run that 0 to many times.即使在控制流情况下(如果可能,应避免),您的 lambda 仍在移动,它只是移动到立即使用它的地方,但 lambda 的要点仍然是控制流抽象:不运行在其中的代码,您将 lambda 交给其他东西,然后它会立即运行 0 到多次。 That's exactly what is happening here:这正是这里发生的事情:

list.forEach(System.out::println);

I'm taking the code notion of System.out.println(someString) , and I don't run it - no, I bundle up that idea in a lambda and then pass this notion to list's forEach method which will then invoke it for me, on every item in the list.我采用System.out.println(someString)的代码概念,但我不运行它 - 不,我将这个想法捆绑在 lambda 中,然后将此概念传递给列表的 forEach 方法,然后调用它我,在列表中的每个项目上。 As mentioned, this is bad code , because it needlessly uses lambdas in control flow abstraction mdoe which is inferior to just for (var item: list) System.out.println(item);如前所述,这是一个糟糕的代码,因为它在控制流抽象 mdoe 中不必要地使用了 lambda,这不如for (var item: list) System.out.println(item); , but it gets the point across. ,但它得到了重点。

It just doesn't make sense to want to write a lambda and immediately execute it.想写一个 lambda 并立即执行它是没有意义的。 Why not just... execute it?为什么不只是......执行它?

In your example from the book, you don't actually execute the lambda as you make it.在书中的示例中,您实际上并未执行 lambda 。 You just.. make it, and hand it off to the runTest method, and it runs it.您只需.. 完成它,并将其交给runTest方法,然后它就会运行它。 The clue is, runTest is a method (vs your attempts - Predicate is not a method), it's not magical or weird, just.. a method, that so happens to take a Function<A, B> as argument, and the lambda you write so happens to 'fit' - it can be interpreted as an implementation of Function<A, B> , and thus that code compiles and does what it does.线索是, runTest是一种方法(与您的尝试相比 - Predicate不是一种方法),它并不神奇或奇怪,只是.. 一种方法,恰好以Function<A, B>作为参数,而 lambda你写的恰好是“适合”——它可以被解释为Function<A, B>的实现,因此该代码可以编译并完成它的工作。

You'd have to do the same thing.你必须做同样的事情。

But, if that code is a single-use helper method, then there's no point to the lambda in the first place.但是,如果该代码是一次性使用的辅助方法,那么 lambda 毫无意义。

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

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