Аргумент недійсності 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.