Аргумент недійсності Java 8 lambda


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!");
Alexis C.

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 (або зі статичним методом у самому інтерфейсі)
Алексіс К.

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

@uthomas Вибачте, я не бачу відповіді @FunctionalInterface. Він просто каже, що продовжити це неможливо ...
Метт

1
Привіт @Matt, вибач. Я реагував занадто швидко. На дане запитання ви відповідаєте цілком справедливо. На жаль, мій голос заблокований, тому я не можу зняти -1 на цю відповідь. Дві зауваження: 1. Замість того, щоб Runnableдіяти, слід прийняти @FunctionalInterfaceщось, що називається SideEffect, 2. потреба в такій функції помічника підкреслює, що відбувається щось дивне і, можливо, абстракція порушена.
утом

530

Використовуйте, Supplierякщо нічого не потрібно, але щось повертає.

Використовуйте, Consumerякщо це щось бере, але нічого не повертає.

Використовуйте, Callableякщо він повертає результат і може кинути (найбільш схожий на Thunkзагальні умови CS).

Використовуйте, Runnableякщо він не робить і не може кинути.


В якості прикладу я зробив це , щоб обернути «порожнечі» зворотний виклик: public static void wrapCall(Runnable r) { r.run(); }. Спасибі
Maxence

13
прекрасна відповідь. Короткий і точний.
Клінт Іствуд

Не допомагає, якщо він, на жаль, повинен кинути перевірений виняток.
Джессі Глік

13
Як завершення цієї відповіді, яку не варто було б редагувати: ви також можете використовувати BiConsumer (займає 2, повертає 0), Функцію (займає 1, повертає 1) та BiFunction (займає 2, повертає 1). Це найголовніше знати
CLOVIS

2
Чи є щось на зразок Callable (який викидає виняток у своєму методі call ()), але для цього потрібне якесь повернене значення?
dpelisek

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!");

Звідки ця дія <Недійсна, Недійсна>? Ні інтерфейси Swing, ні JAX-WX Action не мають такого загального інтерфейсу?
luis.espinal

1
@ luis.espinal: Action<T, U>оголошено у питанні .....
Jordão

Ха-ха-ха, як чорт я пропустив це? Дякую!
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), повинна повернути значення. Однак ви можете додати статичні методи, 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
}

Будь ласка, додайте ще трохи контексту. Це виглядає цікаво, але його значення не відразу очевидно.
Бніленд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.