Java 8 лямбда Void аргумент


188

Допустим, у меня есть следующий функциональный интерфейс в Java 8:

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

А в некоторых случаях мне нужно действие без аргументов или возвращаемого типа. Поэтому я пишу что-то вроде этого:

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

Тем не менее, это дает мне ошибку компиляции, мне нужно записать это как

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

Что некрасиво. Есть ли способ избавиться от Voidпараметра типа?



7
Если вам нужно действие, как вы его определили, это невозможно. Тем не менее, ваш первый пример может вписаться в то Runnable, что вы ищетеRunnable r = () -> System.out.println("Do nothing!");
Алексис С.

1
@BobTheBuilder Я не хочу использовать Потребителя, как предложено в этом посте.
Wickoo

2
Ответ Мэтта заставляет типы работать, но что делает вызывающая программа, когда получает нулевое возвращаемое значение?
Стюарт Маркс

8
Вы можете скрестить пальцы и надеяться, что предложения 2 и 3 в этом посте будут приняты для Java 9!
assylias

Ответы:


110

Синтаксис, который вам нужен, возможен с помощью небольшой вспомогательной функции, которая преобразует Runnableв Action<Void, Void>(например, вы можете поместить его в Action):

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
Это самый чистый обходной путь, который вы можете получить, IMO, так что +1 (или со статическим методом в самом интерфейсе)
Alexis C.

Решение Константина Йовкова, представленное ниже (с @FunctionalInterface), является лучшим решением, поскольку оно не требует обобщений и не требует дополнительного кода.
Утом

@uthomas Извините, я не вижу ответа @FunctionalInterface. Он просто говорит, что это невозможно продлить ...
Мэтт

1
Привет @Matt, прости. Я отреагировал слишком быстро. На данный вопрос вы ответите совершенно правильно. К сожалению, мой голос заблокирован, поэтому я не могу удалить свой -1 в этом ответе. Два примечания: 1. Вместо Runnableдействия следует использовать пользовательское @FunctionalInterfaceчто-то под названием SideEffect, 2. Необходимость такой вспомогательной функции подчеркивает, что происходит что-то странное и, возможно, абстракция нарушена.
Утом

531

Используйте, Supplierесли это ничего не берет, но возвращает что-то.

Используйте, Consumerесли оно что-то берет, но ничего не возвращает.

Используйте, Callableесли он возвращает результат и может сгенерировать (наиболее похоже на Thunkобщие термины CS).

Используйте, Runnableесли это не делает ни один и не может бросить.


В качестве примера я сделал это , чтобы обернуть «пустоты» обратный вызов: public static void wrapCall(Runnable r) { r.run(); }. Спасибо
Максенс

13
красивый ответ. Коротко и точно.
Клинт Иствуд,

Не помогает, если он должен выдать проверенное исключение, к сожалению.
Джесси Глик

13
В завершение этого ответа, который не стоит редактировать: вы также можете использовать BiConsumer (принимает 2, возвращает 0), Function (принимает 1, возвращает 1) и BiFunction (принимает 2, возвращает 1). Это наиболее важно знать
КЛОВИС,

2
Есть ли что-то вроде Callable (который вызывает исключение в методе call ()), но требует ли возвращаемого значения?
дпелисек

40

Лямбда:

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

на самом деле представляет собой реализацию для интерфейса, как:

public interface Something {
    void action();
}

который полностью отличается от того, который вы определили. Вот почему вы получаете ошибку.

Так как вы не можете продлить @FunctionalInterface или представить новый, я думаю, у вас не так много вариантов. Вы можете использовать Optional<T>интерфейсы, чтобы указать, что некоторые значения (возвращаемый тип или параметр метода) отсутствуют. Однако это не сделает лямбда-тело более простым.


Проблема в том, что ваша Somethingфункция не может быть подтипом моего Actionтипа, и у меня не может быть двух разных типов.
Wickoo

Технически он может, но он сказал, что хочет избежать этого. :)
Константин Йовков

31

Вы можете создать подчиненный интерфейс для этого особого случая:

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

Он использует метод по умолчанию для переопределения унаследованного параметризованного метода Void execute(Void), делегируя вызов более простому методу void execute().

В результате гораздо проще использовать:

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

Откуда происходит это действие <Void, Void>? Ни интерфейсы Swing, ни интерфейсы JAX-WX Action не имеют такого общего интерфейса?
luis.espinal

1
@ luis.espinal: Action<T, U>объявлено в вопросе .....
Джордао

Хахаха, как, черт возьми, я это пропустил? Спасибо!
luis.espinal

6

Я думаю, что эта таблица короткая и полезная:

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

Как сказано в других ответах, подходящим вариантом для этой проблемы является Runnable


5

Это невозможно. Функция, которая имеет тип возврата не void (даже если он есть Void), должна возвращать значение. Однако вы можете добавить статические методы, Actionчто позволит вам «создать» 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;
   } 
}

Это позволит вам написать следующее:

// 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));

4

Добавьте статический метод в ваш функциональный интерфейс

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);
    }

}

Вывод

Do nothing!

3

Я не думаю, что это возможно, потому что определения функций не совпадают в вашем примере.

Ваше лямбда-выражение оценивается точно так же, как

void action() { }

тогда как ваша декларация выглядит

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

Например, если у вас есть следующий интерфейс

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

единственный вид функции (при создании экземпляра), который будет совместим, выглядит

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

и отсутствие оператора возврата или аргумента приведет к ошибке компилятора.

Поэтому, если вы объявляете функцию, которая принимает аргумент и возвращает его, я думаю, что невозможно преобразовать ее в функцию, которая не выполняет ни одного из упомянутых выше.


3

Просто для справки, какой функциональный интерфейс может быть использован для ссылки на метод в случаях, когда метод вызывает и / или возвращает значение.

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
}

Пожалуйста, добавьте еще немного контекста. Это выглядит интересно, но его смысл не сразу очевиден.
Bnieland
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.