Làm cách nào để xác định một phương thức lấy lambda làm tham số trong Java 8?


363

Trong Java 8, các phương thức có thể được tạo như các biểu thức Lambda và có thể được truyền bằng tham chiếu (với một chút công việc dưới mui xe). Có rất nhiều ví dụ trực tuyến với lambdas được tạo và sử dụng với các phương thức, nhưng không có ví dụ nào về cách tạo phương thức lấy lambda làm tham số. Cú pháp cho điều đó là gì?

MyClass.method((a, b) -> a+b);


class MyClass{
  //How do I define this method?
  static int method(Lambda l){
    return l(5, 10);
  }
}

29
Câu hỏi hay. Và bạn đã đúng: Không có hướng dẫn nào chứa phần đó.
Martin

Câu trả lời:


247

Lambdas hoàn toàn là một cấu trúc trang web cuộc gọi: người nhận lambda không cần biết rằng Lambda có liên quan, thay vào đó, nó chấp nhận Giao diện với phương thức thích hợp.

Nói cách khác, bạn xác định hoặc sử dụng giao diện chức năng (nghĩa là giao diện với một phương thức duy nhất) chấp nhận và trả về chính xác những gì bạn muốn.

Đối với Java 8 này đi kèm với một tập hợp các loại giao diện thường được sử dụng trong java.util.function(nhờ Maurice Naftalin cho gợi ý về JavaDoc).

Đối với trường hợp cụ thể này sử dụng có java.util.function.IntBinaryOperatorvới một đơn int applyAsInt(int left, int right)phương , vì vậy bạn có thể viết của bạn methodnhư thế này:

static int method(IntBinaryOperator op){
    return op.applyAsInt(5, 10);
}

Nhưng bạn cũng có thể xác định giao diện của riêng mình và sử dụng nó như thế này:

public interface TwoArgIntOperator {
    public int op(int a, int b);
}

//elsewhere:
static int method(TwoArgIntOperator operator) {
    return operator.op(5, 10);
}

Sử dụng giao diện của riêng bạn có lợi thế là bạn có thể có những cái tên thể hiện rõ hơn ý định.


5
Sẽ có các giao diện tích hợp sẽ được sử dụng hay tôi phải tạo giao diện cho mọi lambda tôi muốn dùng?
Marius

Một sự thỏa hiệp tốt cho tình trạng khó sử dụng so với tình trạng khó xử của tên mô tả sẽ là mở rộng giao diện tích hợp mà không ghi đè lên phương thức mà nó chỉ định. Điều đó cung cấp cho bạn tên mô tả của bạn chỉ với một dòng mã bổ sung duy nhất.
Will Byrne

Tôi không hiểu Anh ta có thể vượt qua lambda cho bất cứ điều gì và nó sẽ làm việc? Chuyện gì xảy ra nếu anh đi (int a, int b, int c)cho TwoArgIntOperator. Điều gì xảy ra nếu TwoArgIntOperatorhai phương thức có cùng chữ ký. Câu trả lời này là khó hiểu.
Tomáš Zato - Phục hồi Monica

7
@ TomášZato: nếu bạn sử dụng lambda với các đối số không khớp, trình biên dịch sẽ khiếu nại. Và các giao diện với hai phương thức (không mặc định) sẽ không thể sử dụng được như lambdas, vì chỉ các giao diện chức năng có thể được sử dụng.
Joachim Sauer

Một câu hỏi cơ bản: Tôi nghĩ rằng tôi vẫn không hiểu khía cạnh của việc truyền một phương thức như là một tham số với các tham số của phương thức đó bị thiếu. Nếu anh ta truyền TwoArgIntOperator làm tham số và anh ta cần truyền riêng tham số của phương thức đó, thì nó có xấu không? Có cách nào để vượt qua cơ thể thực thi hoàn chỉnh cùng với tham số không? Giống như trong ví dụ của bạn, một cách để tránh mã hóa "5" và "10".
instanceOfObject

63

Để sử dụng biểu thức Lambda, bạn cần tạo giao diện chức năng của riêng mình hoặc sử dụng giao diện chức năng Java cho hoạt động yêu cầu hai số nguyên và trả về làm giá trị. IntBinaryOperator

Sử dụng giao diện chức năng do người dùng xác định

interface TwoArgInterface {

    public int operation(int a, int b);
}

public class MyClass {

    public static void main(String javalatte[]) {
        // this is lambda expression
        TwoArgInterface plusOperation = (a, b) -> a + b;
        System.out.println("Sum of 10,34 : " + plusOperation.operation(10, 34));

    }
}

Sử dụng giao diện chức năng Java

import java.util.function.IntBinaryOperator;

public class MyClass1 {

    static void main(String javalatte[]) {
        // this is lambda expression
        IntBinaryOperator plusOperation = (a, b) -> a + b;
        System.out.println("Sum of 10,34 : " + plusOperation.applyAsInt(10, 34));

    }
}

Ví dụ khác tôi đã tạo ở đây


Các liên kết đến các IntBinaryOperatortài liệu đã chết.
Hendrikto

1
đây là ví dụ tốt nhất về lambda mà tôi đã tìm thấy cho đến nay và đó là ví dụ duy nhất thực sự khiến tôi 'hiểu' nó cuối cùng.
JimmySmithJR

1
Sooooooo ... về cơ bản là một đại biểu, nhưng chúng ta không nên gọi nó như vậy?
Ryan Lundy

37

Đối với các hàm không có nhiều hơn 2 tham số, bạn có thể chuyển chúng mà không cần xác định giao diện của riêng mình. Ví dụ,

class Klass {
  static List<String> foo(Integer a, String b) { ... }
}

class MyClass{

  static List<String> method(BiFunction<Integer, String, List<String>> fn){
    return fn.apply(5, "FooBar");
  }
}

List<String> lStr = MyClass.method((a, b) -> Klass.foo((Integer) a, (String) b));

Trong BiFunction<Integer, String, List<String>>, IntegerStringlà các tham số của nó, và List<String>là kiểu trả về của nó.

Đối với một hàm chỉ có một tham số, bạn có thể sử dụng Function<T, R>, Tloại tham số của nó ở đâu và Rlà loại giá trị trả về của nó. Tham khảo trang này để biết tất cả các giao diện đã được Java cung cấp.


15

Có phiên bản Java 8 JavaDocs có thể truy cập Web công khai, được liên kết từ http://lambdafaq.org/lambda-resours . . -lambda câu hỏi.

NB Câu trả lời này được viết trước khi tài liệu GA của Java 8 trở nên công khai . Mặc dù vậy, tôi đã rời khỏi vị trí, vì Câu hỏi thường gặp về Lambda vẫn có thể hữu ích cho những người tìm hiểu về các tính năng được giới thiệu trong Java 8.


2
Cảm ơn các liên kết và thực tế là bạn duy trì trang web đó! Tôi đã tự do thêm các liên kết đến JavaDoc công khai của bạn vào câu trả lời của tôi.
Joachim Sauer

1
Một lưu ý phụ: Có vẻ như bạn đang xây dựng cho Lambdas những gì Angelika Langer đã xây dựng cho Generics . Cảm ơn vì điều đó, Java cần những tài nguyên như vậy!
Joachim Sauer

1
Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi. - Từ đánh giá
ClearLogic

@ClearLogic Có, đồng ý. AFAIR Tôi không muốn thêm bất cứ điều gì vào các câu trả lời hiện có mà chỉ chỉ ra nơi tôi đã đăng một bản sao của tài liệu API, vào thời điểm đó không dễ truy cập.
Maurice Naftalin

7

Đối với tôi, giải pháp có ý nghĩa nhất là xác định Callbackgiao diện:

interface Callback {
    void call();
}

và sau đó sử dụng nó làm tham số trong hàm bạn muốn gọi:

void somewhereInYourCode() {
    method(() -> {
        // You've passed a lambda!
        // method() is done, do whatever you want here.
    });
}

void method(Callback callback) {
    // Do what you have to do
    // ...

    // Don't forget to notify the caller once you're done
    callback.call();
}

Chỉ là một sự chính xác

Lambda không phải là một giao diện đặc biệt, lớp hay bất cứ thứ gì bạn có thể tự khai báo. Lambdachỉ là tên được đặt cho () -> {}cú pháp đặc biệt, cho phép dễ đọc hơn khi truyền các giao diện phương thức đơn làm tham số. Nó được thiết kế để thay thế điều này:

method(new Callback() {
    @Override
    public void call() {
        // Classic interface implementation, lot of useless boilerplate code.
        // method() is done, do whatever you want here.
    }
});

Vì vậy, trong ví dụ trên, Callbackkhông một lambda, nó chỉ là một giao diện thường xuyên; lambdalà tên của cú pháp phím tắt mà bạn có thể sử dụng để thực hiện nó.


6

Đối với bất cứ ai đang googling này, một phương pháp tốt sẽ được sử dụng java.util.function.BiConsumer. Ví dụ:

Import java.util.function.Consumer
public Class Main {
    public static void runLambda(BiConsumer<Integer, Integer> lambda) {
        lambda.accept(102, 54)
    }

    public static void main(String[] args) {
        runLambda((int1, int2) -> System.out.println(int1 + " + " + int2 + " = " + (int1 + int2)));
    }

Outprint sẽ là: 166


1
Thay vì Consumer<Pair<A,B>>sử dụng BiConsumer<A,B>cho trường hợp này. ( tài liệu )
tộc

Không biết rằng đã tồn tại, tôi nên chọn lọc gói chức năng vào lần tới.
Big_Bad_E

5

Biểu thức lambda có thể được truyền dưới dạng đối số. Để truyền biểu thức lambda làm đối số, loại tham số (nhận biểu thức lambda làm đối số) phải thuộc loại giao diện chức năng.

Nếu có giao diện chức năng -

interface IMyFunc {
   boolean test(int num);
}

Và có một phương thức lọc chỉ thêm int trong danh sách nếu nó lớn hơn 5. Lưu ý ở đây rằng phương thức lọc có giao diện tự do IMyFunc là một trong các tham số. Trong trường hợp đó, biểu thức lambda có thể được truyền vào làm đối số cho tham số phương thức.

public class LambdaDemo {
    public static List<Integer> filter(IMyFunc testNum, List<Integer> listItems) {
        List<Integer> result = new ArrayList<Integer>();
        for(Integer item: listItems) {
            if(testNum.test(item)) {
                result.add(item);
            }
        }
        return result;
    }
    public static void main(String[] args) {
        List<Integer> myList = new ArrayList<Integer>();
        myList.add(1);
        myList.add(4);
        myList.add(6);
        myList.add(7);
        // calling filter method with a lambda expression
        // as one of the param
        Collection<Integer> values = filter(n -> n > 5, myList);

        System.out.println("Filtered values " + values);
    }
}

2

Bạn có thể sử dụng các giao diện chức năng như đã đề cập ở trên. dưới đây là một số ví dụ

Function<Integer, Integer> f1 = num->(num*2+1);
System.out.println(f1.apply(10));

Predicate<Integer> f2= num->(num > 10);
System.out.println(f2.test(10));
System.out.println(f2.test(11));

Supplier<Integer> f3= ()-> 100;
System.out.println(f3.get());

Hy vọng nó giúp


1

Chà, thật dễ dàng. Mục đích của biểu thức lambda là để thực hiện Giao diện chức năng. Đó là giao diện chỉ có một phương thức. Đây là bài viết awesone về các giao diện chức năng được xác định trước và di sản.

Dù sao, nếu bạn muốn thực hiện giao diện chức năng của riêng mình, hãy làm cho nó. Chỉ cho ví dụ đơn giản:

public interface MyFunctionalInterface {
    String makeIt(String s);
}

Vì vậy, hãy tạo một lớp, trong đó chúng ta sẽ tạo một phương thức, chấp nhận kiểu MyFunctionalInterface :

public class Main {

    static void printIt(String s, MyFunctionalInterface f) {
        System.out.println(f.makeIt(s));
    }

    public static void main(String[] args) {

    }
}

Điều cuối cùng bạn nên làm là chuyển giao việc triển khai MyFactoralInterface cho phương thức chúng tôi đã xác định:

public class Main {

    static void printIt(String s, MyFunctionalInterface f) {
        System.out.println(f.makeIt(s));
    }

    public static void main(String[] args) {
        printIt("Java", s -> s + " is Awesome");
    }
}

Đó là nó!


1

Lambda không phải là một đối tượng mà là một Giao diện chức năng. Người ta có thể định nghĩa nhiều Giao diện chức năng như họ có thể sử dụng @FuntionalInterface làm chú thích

@FuntionalInterface
public interface SumLambdaExpression {
     public int do(int a, int b);
}

public class MyClass {
     public static void main(String [] args) {
          SumLambdaExpression s = (a,b)->a+b;
          lambdaArgFunction(s);
     }

     public static void lambdaArgFunction(SumLambdaExpression s) {
          System.out.println("Output : "+s.do(2,5));
     }
}

Đầu ra sẽ như sau

Output : 7

Khái niệm cơ bản của biểu thức Lambda là xác định logic của riêng bạn nhưng các đối số đã được xác định. Vì vậy, trong đoạn mã trên, bạn có thể thay đổi định nghĩa của hàm do ngoài bất kỳ định nghĩa nào khác, nhưng các đối số của bạn bị giới hạn ở 2.


1

Về cơ bản để vượt qua một biểu thức lamda như một tham số, chúng ta cần một loại mà chúng ta có thể giữ nó. Cũng giống như một giá trị số nguyên chúng ta giữ trong nguyên thủy int hoặc lớp Integer. Java không có một kiểu riêng cho biểu thức lamda thay vào đó nó sử dụng một giao diện làm kiểu để giữ đối số. Nhưng giao diện đó phải là một giao diện chức năng .


0

Làm như sau ..

Bạn đã khai báo method(lambda l) Tất cả những gì bạn muốn làm là tạo Giao diện với tên lambdavà khai báo một phương thức trừu tượng

public int add(int a,int b);  

Tên phương thức không quan trọng ở đây ..

Vì vậy, khi u gọi MyClass.method( (a,b)->a+b) thực hiện này (a,b)->a+bsẽ được tiêm vào giao diện add bạn phương pháp Vì vậy, bất cứ khi nào bạn gọi l.addnó là sẽ mất thi này và thực hiện bổ sung abreturn l.add(2,3)sẽ trở lại 5. - Về cơ bản đây là những gì lambda làm ..


-1

Đây là cách C # xử lý vấn đề này (nhưng được biểu thị dưới dạng mã Java). Một cái gì đó như thế này có thể đáp ứng hầu hết tất cả các nhu cầu của bạn:

import static org.util.function.Functions.*;

public class Test {

    public static void main(String[] args)
    {
        Test.invoke((a, b) -> a + b);       
    }

    public static void invoke(Func2<Integer, Integer, Integer> func)
    {
        System.out.println(func.apply(5, 6));
    }
}

package org.util.function;

public interface Functions {

    //Actions:
    public interface Action {
        public void apply();
    }

    public interface Action1<T1> {
        public void apply(T1 arg1);
    }

    public interface Action2<T1, T2> {
        public void apply(T1 arg1, T2 arg2);
    }

    public interface Action3<T1, T2, T3> {
        public void apply(T1 arg1, T2 arg2, T3 arg3);
    }

    public interface Action4<T1, T2, T3, T4> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    }

    public interface Action5<T1, T2, T3, T4, T5> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    }

    public interface Action6<T1, T2, T3, T4, T5, T6> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    }

    public interface Action7<T1, T2, T3, T4, T5, T6, T7> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    }

    public interface Action8<T1, T2, T3, T4, T5, T6, T7, T8> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    }

    //Functions:
    public interface Func<TResult> {
        public TResult apply();
    }

    public interface Func1<T1, TResult> {
        public TResult apply(T1 arg1);
    }

    public interface Func2<T1, T2, TResult> {
        public TResult apply(T1 arg1, T2 arg2);
    }

    public interface Func3<T1, T2, T3, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3);
    }

    public interface Func4<T1, T2, T3, T4, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    }

    public interface Func5<T1, T2, T3, T4, T5, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    }

    public interface Func6<T1, T2, T3, T4, T5, T6, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    }

    public interface Func7<T1, T2, T3, T4, T5, T6, T7, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    }

    public interface Func8<T1, T2, T3, T4, T5, T6, T7, T8, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    }
}

-2

Có sự linh hoạt trong việc sử dụng lambda làm tham số. Nó cho phép lập trình chức năng trong java. Cú pháp cơ bản là

param -> phương thức_body

Sau đây là một cách, bạn có thể định nghĩa một phương thức lấy giao diện chức năng (lambda được sử dụng) làm tham số. a. nếu bạn muốn định nghĩa một phương thức được khai báo bên trong một giao diện chức năng, giả sử, giao diện chức năng được đưa ra dưới dạng một đối số / tham số cho một phương thức được gọi từmain()

@FunctionalInterface
interface FInterface{
    int callMeLambda(String temp);
}


class ConcreteClass{

    void funcUsesAnonymousOrLambda(FInterface fi){
        System.out.println("===Executing method arg instantiated with Lambda==="));
    }

    public static void main(){
        // calls a method having FInterface as an argument.
        funcUsesAnonymousOrLambda(new FInterface() {

            int callMeLambda(String temp){ //define callMeLambda(){} here..
                return 0;
            }
        }
    }

/***********Can be replaced by Lambda below*********/
        funcUsesAnonymousOrLambda( (x) -> {
            return 0; //(1)
        }

    }

FInterface fi = (x) -> {return 0; };

funcUsesAnonymousOrLambda (fi);

Ở đây có thể thấy ở đây, làm thế nào một biểu thức lambda có thể được thay thế bằng một giao diện.

Ở trên giải thích một cách sử dụng cụ thể của biểu thức lambda, có nhiều hơn nữa. tham chiếu lambda Java 8 trong lambda không thể sửa đổi biến từ lambda bên ngoài

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.