Jak określić typy funkcji dla metod void (nie Void) w Java8?

143

Bawię się Javą 8, aby dowiedzieć się, jak funkcjonuje jako obywatel pierwszej klasy. Mam następujący fragment:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

To, co próbuję zrobić, to przekazać metodę displayIntdo metody myForEachprzy użyciu odwołania do metody. Kompilator generuje następujący błąd:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

Kompilator narzeka na to void cannot be converted to Void. Nie wiem, jak określić typ interfejsu funkcji w podpisie myForEachtakiego, który kompiluje kod. Wiem, że mógłbym po prostu zmienić typ powrotu displayIntdo, Voida następnie wrócić null. Mogą jednak zaistnieć sytuacje, w których nie będzie można zmienić metody, którą chcę przekazać gdzie indziej. Czy istnieje łatwy sposób ponownego wykorzystania displayIntw obecnej postaci?

Valentin
źródło

Odpowiedzi:

244

Próbujesz użyć złego typu interfejsu. W tym przypadku typ Funkcja nie jest odpowiedni, ponieważ otrzymuje parametr i zwraca wartość. Zamiast tego powinieneś użyć Konsumenta (wcześniej znanego jako Blok)

Typ funkcji jest zadeklarowany jako

interface Function<T,R> {
  R apply(T t);
}

Jednak typ Konsumenta jest zgodny z tym, którego szukasz:

interface Consumer<T> {
   void accept(T t);
}

W związku z tym Consumer jest kompatybilny z metodami, które otrzymują T i nic nie zwracają (void). I tego właśnie chcesz.

Na przykład, gdybym chciał wyświetlić wszystkie elementy na liście, mógłbym po prostu utworzyć konsumenta za pomocą wyrażenia lambda:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Jak widać powyżej, w tym przypadku wyrażenie lambda otrzymuje parametr i nie zwraca żadnej wartości.

Teraz, gdybym chciał użyć odwołania do metody zamiast wyrażenia lambda do utworzenia konsumowania tego typu, potrzebuję metody, która odbiera ciąg i zwraca void, prawda ?.

Mógłbym użyć różnych typów odwołań do metod, ale w tym przypadku skorzystajmy z odwołania do printlnmetody System.outobiektu, używając metody w obiekcie, na przykład:

Consumer<String> block = System.out::println

Albo mógłbym po prostu zrobić

allJedi.forEach(System.out::println);

printlnMetoda jest właściwa, ponieważ odbiera wartość i ma typ powrót pustkę, podobnie jak acceptmetody w konsumenckich.

Tak więc w swoim kodzie musisz zmienić sygnaturę metody na coś podobnego do:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

Następnie powinieneś być w stanie stworzyć konsumenta, używając statycznego odwołania do metody, w twoim przypadku, wykonując:

myForEach(theList, Test::displayInt);

Ostatecznie możesz nawet myForEachcałkowicie pozbyć się swojej metody i po prostu zrobić:

theList.forEach(Test::displayInt);

O funkcjach jako obywatele pierwszej klasy

Wszystko zostało powiedziane, prawda jest taka, że ​​Java 8 nie będzie miała funkcji jako obywatele pierwszej klasy, ponieważ typ funkcji strukturalnej nie zostanie dodany do języka. Java po prostu zaoferuje alternatywny sposób tworzenia implementacji interfejsów funkcjonalnych na podstawie wyrażeń lambda i odwołań do metod. Ostatecznie wyrażenia lambda i odwołania do metod będą powiązane z odniesieniami do obiektów, dlatego wszystko, co mamy, to obiekty jako obywatele pierwszej klasy. Ważną rzeczą jest to, że funkcjonalność istnieje, ponieważ możemy przekazywać obiekty jako parametry, wiązać je z odwołaniami do zmiennych i zwracać je jako wartości z innych metod, wtedy służą one podobnemu celowi.

Edwin Dalorzo
źródło
1
Nawiasem mówiąc, Blockzostał zmieniony na Consumerw najnowszych wersjach JDK 8 API
Edwin Dalorzo
4
Ostatni akapit omawia kilka ważnych faktów: wyrażenia lambda i odwołania do metod to coś więcej niż tylko dodatek składniowy. Sama JVM dostarcza nowe typy do obsługi odwołań do metod bardziej wydajnie niż w przypadku klas anonimowych (co najważniejsze MethodHandle), a używając tego kompilator Java jest w stanie stworzyć bardziej wydajny kod, np. Implementując lambdy bez zamykania jako metody statyczne, zapobiegając w ten sposób generowaniu zajęcia dodatkowe.
Feuermurmel
7
A poprawna wersja Function<Void, Void>to Runnable.
OrangeDog
2
@OrangeDog To nie jest do końca prawda. W komentarzach Runnablei Callableinterfejsach jest napisane, że mają być używane w połączeniu z wątkami . Irytującą konsekwencją tego jest to, że niektóre narzędzia do analizy statycznej (takie jak Sonar) będą narzekać, jeśli wywołasz run()metodę bezpośrednio.
Denis
Opierając się wyłącznie na Javie, zastanawiam się, jakie są szczególne przypadki, w których Java nie może traktować funkcji jako obywateli pierwszej klasy, nawet po wprowadzeniu funkcjonalnych interfejsów i lambd od czasu Java8?
Vivek Sethi,
21

Kiedy musisz przyjąć funkcję jako argument, która nie przyjmuje argumentów i nie zwraca wyniku (void), moim zdaniem nadal najlepiej jest mieć coś takiego

  public interface Thunk { void apply(); }

gdzieś w swoim kodzie. Na moich kursach programowania funkcjonalnego do opisania takich funkcji używano słowa „thunk”. Dlaczego nie ma go w java.util.function, jest poza moim zrozumieniem.

W innych przypadkach stwierdzam, że nawet wtedy, gdy java.util.function ma coś, co pasuje do podpisu, którego potrzebuję - nadal nie zawsze wydaje się właściwe, gdy nazewnictwo interfejsu nie pasuje do użycia funkcji w moim kodzie. Wydaje mi się, że jest to podobna kwestia, która pojawia się w innym miejscu tutaj w odniesieniu do „Runnable” - który jest terminem związanym z klasą Thread - więc chociaż może mieć podpis, którego potrzebuję, nadal może zmylić czytelnika.

LOAS
źródło
W szczególności Runnablenie zezwala na sprawdzane wyjątki, przez co jest bezużyteczny dla wielu aplikacji. Ponieważ Callable<Void>nie jest to również przydatne, wydaje się, że musisz zdefiniować własne. Brzydki.
Jesse Glick
Odnosząc się do sprawdzonego problemu z wyjątkami, nowe funkcje Java generalnie borykają się z tym problemem. jest to ogromny bloker dla niektórych rzeczy, takich jak Stream API. Bardzo kiepskie projektowanie z ich strony.
user2223059
0

Ustaw typ zwrotu na Voidzamiast voidireturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

LUB

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});
Dojo
źródło
-2

Uważam, że zamiast tego powinieneś używać interfejsu klienta Function<T, R>.

Konsument jest w zasadzie funkcjonalnym interfejsem zaprojektowanym do akceptowania wartości i niczego nie zwracającego (tj. Void)

W twoim przypadku możesz utworzyć konsumenta w innym miejscu kodu w ten sposób:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Następnie możesz zamienić myForEachkod na poniższy fragment:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Traktujesz myFunction jako obiekt pierwszej klasy.

Anthony Anyanwu
źródło
2
Co to dodaje poza istniejącymi odpowiedziami?
Matthew Przeczytaj