Đối số lambda Java 8


186

Giả sử tôi có giao diện chức năng sau trong Java 8:

interface Action<T, U> {
   U execute(T t);
}

Và đối với một số trường hợp tôi cần một hành động không có đối số hoặc kiểu trả về. Vì vậy, tôi viết một cái gì đó như thế này:

Action<Void, Void> a = () -> { System.out.println("Do nothing!"); };

Tuy nhiên, nó mang lại cho tôi lỗi biên dịch, tôi cần phải viết nó dưới dạng

Action<Void, Void> a = (Void v) -> { System.out.println("Do nothing!"); return null;};

Cái nào xấu. Có cách nào để thoát khỏi Voidtham số loại?



7
Nếu bạn cần một hành động, như bạn đã xác định, thì không thể. Tuy nhiên, ví dụ đầu tiên của bạn có thể phù hợp với một Runnablethứ mà bạn đang tìm kiếmRunnable r = () -> System.out.println("Do nothing!");
Alexis C.

1
@BobTheBuilder Tôi không muốn sử dụng Người tiêu dùng như được đề xuất trong bài đăng đó.
Wickoo

2
Câu trả lời của Matt làm cho các loại hoạt động, nhưng người gọi sẽ làm gì khi nhận được giá trị trả về null?
Stuart Marks

8
Bạn có thể bắt chéo ngón tay và hy vọng rằng các đề xuất 2 & 3 trong bài đăng này được chấp nhận cho Java 9!
assylias

Câu trả lời:


109

Cú pháp bạn theo sau là có thể với một hàm trợ giúp nhỏ chuyển đổi Runnablethành Action<Void, Void>( Actionví dụ bạn có thể đặt nó vào ):

public static Action<Void, Void> action(Runnable runnable) {
    return (v) -> {
        runnable.run();
        return null;
    };
}

// Somewhere else in your code
 Action<Void, Void> action = action(() -> System.out.println("foo"));

4
Đây là cách giải quyết rõ ràng nhất bạn có thể nhận được, IMO, vì vậy +1 (hoặc với một phương thức tĩnh trong chính giao diện)
Alexis C.

Giải pháp của Konstantin Yovkov dưới đây (với @FactoralInterface) là một giải pháp tốt hơn, vì nó không liên quan đến thuốc generic và không yêu cầu thêm mã.
uthomas

@uthomas Xin lỗi, tôi không thấy câu trả lời liên quan @FunctionalInterface. Anh ta chỉ nói, rằng không thể kéo dài nó ...
Matt

1
Xin chào @Matt, xin lỗi. Tôi đã phản ứng quá nhanh. Đối với câu hỏi nhất định bạn trả lời là hoàn toàn hợp lệ. Thật không may, phiếu bầu của tôi bị khóa, vì vậy tôi không thể xóa -1 của mình cho câu trả lời này. Hai lưu ý: 1. Thay vì Runnablehành động nên thực hiện một tùy chỉnh @FunctionalInterfaceđược gọi là SideEffect, 2. sự cần thiết của chức năng trợ giúp như vậy nhấn mạnh rằng một cái gì đó kỳ lạ đang diễn ra và có lẽ sự trừu tượng bị phá vỡ.
uthomas

526

Sử dụng Suppliernếu nó không mất gì, nhưng trả lại một cái gì đó.

Sử dụng Consumernếu nó mất một cái gì đó, nhưng không trả lại gì.

Sử dụng Callablenếu nó trả về một kết quả và có thể ném (gần giống với Thunkcác thuật ngữ CS nói chung).

Sử dụng Runnablenếu nó không và không thể ném.


Như một ví dụ tôi đã làm điều này để bọc cuộc gọi trở lại "void" : public static void wrapCall(Runnable r) { r.run(); }. Cảm ơn
Tối đa

12
câu trả lời đẹp. Ngắn gọn và chính xác.
Clint Eastwood

Thật không có ích nếu nó phải ném một ngoại lệ được kiểm tra, thật không may.
Jesse Glick

11
Khi hoàn thành câu trả lời này, sẽ không có giá trị chỉnh sửa: bạn cũng có thể sử dụng BiConsumer (mất 2, trả về 0), Hàm (mất 1, trả lại 1) và BiFactor (mất 2, trả về 1). Đó là những điều quan trọng nhất cần biết
CLOVIS

2
Có bất cứ điều gì như Callable (đưa ra một ngoại lệ trong phương thức call () của nó) nhưng không yêu cầu bất kỳ giá trị trả về nào không?
dpelisek

40

Lambda:

() -> { System.out.println("Do nothing!"); };

thực sự đại diện cho một triển khai cho một giao diện như:

public interface Something {
    void action();
}

khác hoàn toàn so với cái bạn đã xác định. Đó là lý do tại sao bạn gặp lỗi.

Vì bạn không thể mở rộng @FunctionalInterface, cũng không giới thiệu một thương hiệu mới, nên tôi nghĩ bạn không có nhiều lựa chọn. Tuy nhiên, bạn có thể sử dụng các Optional<T>giao diện để biểu thị rằng một số giá trị (kiểu trả về hoặc tham số phương thức) bị thiếu. Tuy nhiên, điều này sẽ không làm cho cơ thể lambda đơn giản hơn.


Vấn đề là Somethingchức năng của bạn không thể là một kiểu con của Actionloại của tôi và tôi không thể có hai loại khác nhau.
Wickoo

Về mặt kỹ thuật anh ta có thể, nhưng anh ta nói anh ta muốn tránh nó. :)
Konstantin Yovkov

31

Bạn có thể tạo giao diện phụ cho trường hợp đặc biệt đó:

interface Command extends Action<Void, Void> {
  default Void execute(Void v) {
    execute();
    return null;
  }
  void execute();
}

Nó sử dụng một phương thức mặc định để ghi đè phương thức tham số được kế thừa Void execute(Void), ủy quyền cuộc gọi cho phương thức đơn giản hơn void execute().

Kết quả là nó đơn giản hơn nhiều để sử dụng:

Command c = () -> System.out.println("Do nothing!");

5

Đó là không thể. Một hàm có kiểu trả về không trống (ngay cả khi nó Void) phải trả về một giá trị. Tuy nhiên, bạn có thể thêm các phương thức tĩnh để Actioncho phép bạn "tạo" a Action:

interface Action<T, U> {
   U execute(T t);

   public static Action<Void, Void> create(Runnable r) {
       return (t) -> {r.run(); return null;};
   }

   public static <T, U> Action<T, U> create(Action<T, U> action) {
       return action;
   } 
}

Điều đó sẽ cho phép bạn viết như sau:

// create action from Runnable
Action.create(()-> System.out.println("Hello World")).execute(null);
// create normal action
System.out.println(Action.create((Integer i) -> "number: " + i).execute(100));

5

Tôi nghĩ bảng này ngắn và hữu ích:

Supplier       ()    -> x
Consumer       x     -> ()
Callable       ()    -> x throws ex
Runnable       ()    -> ()
Function       x     -> y
BiFunction     x,y   -> z
Predicate      x     -> boolean
UnaryOperator  x1    -> x2
BinaryOperator x1,x2 -> x3

Như đã nói ở các câu trả lời khác, tùy chọn thích hợp cho vấn đề này là Runnable


4

Thêm một phương thức tĩnh bên trong giao diện chức năng của bạn

package example;

interface Action<T, U> {
       U execute(T t);
       static  Action<Void,Void> invoke(Runnable runnable){
           return (v) -> {
               runnable.run();
                return null;
            };         
       }
    }

public class Lambda {


    public static void main(String[] args) {

        Action<Void, Void> a = Action.invoke(() -> System.out.println("Do nothing!"));
        Void t = null;
        a.execute(t);
    }

}

Đầu ra

Do nothing!

3

Tôi không nghĩ là có thể, bởi vì các định nghĩa hàm không khớp trong ví dụ của bạn.

Biểu thức lambda của bạn được đánh giá chính xác như

void action() { }

trong khi tuyên bố của bạn trông giống như

Void action(Void v) {
    //must return Void type.
}

làm ví dụ, nếu bạn có giao diện sau

public interface VoidInterface {
    public Void action(Void v);
}

loại chức năng duy nhất (trong khi khởi tạo) sẽ tương thích

new VoidInterface() {
    public Void action(Void v) {
        //do something
        return v;
    }
}

và thiếu câu lệnh return hoặc đối số sẽ cung cấp cho bạn lỗi trình biên dịch.

Do đó, nếu bạn khai báo một hàm lấy một đối số và trả về một hàm, tôi nghĩ rằng không thể chuyển đổi nó thành hàm không được đề cập ở trên.


3

Chỉ để tham khảo giao diện chức năng nào có thể được sử dụng để tham chiếu phương thức trong trường hợp phương thức ném và / hoặc trả về giá trị.

void notReturnsNotThrows() {};
void notReturnsThrows() throws Exception {}
String returnsNotThrows() { return ""; }
String returnsThrows() throws Exception { return ""; }

{
    Runnable r1 = this::notReturnsNotThrows; //ok
    Runnable r2 = this::notReturnsThrows; //error
    Runnable r3 = this::returnsNotThrows; //ok
    Runnable r4 = this::returnsThrows; //error

    Callable c1 = this::notReturnsNotThrows; //error
    Callable c2 = this::notReturnsThrows; //error
    Callable c3 = this::returnsNotThrows; //ok
    Callable c4 = this::returnsThrows; //ok

}


interface VoidCallableExtendsCallable extends Callable<Void> {
    @Override
    Void call() throws Exception;
}

interface VoidCallable {
    void call() throws Exception;
}

{
    VoidCallableExtendsCallable vcec1 = this::notReturnsNotThrows; //error
    VoidCallableExtendsCallable vcec2 = this::notReturnsThrows; //error
    VoidCallableExtendsCallable vcec3 = this::returnsNotThrows; //error
    VoidCallableExtendsCallable vcec4 = this::returnsThrows; //error

    VoidCallable vc1 = this::notReturnsNotThrows; //ok
    VoidCallable vc2 = this::notReturnsThrows; //ok
    VoidCallable vc3 = this::returnsNotThrows; //ok
    VoidCallable vc4 = this::returnsThrows; //ok
}

Vui lòng thêm một số bối cảnh. Điều này có vẻ thú vị, nhưng ý nghĩa của nó không rõ ràng ngay lập tức.
bnieland
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.