Come specificare i tipi di funzione per i metodi void (non Void) in Java8?


143

Sto giocando con Java 8 per scoprire come funziona come cittadini di prima classe. Ho il seguente frammento:

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

Quello che sto cercando di fare è passare il metodo displayIntal metodo myForEachusando un riferimento al metodo. Al compilatore produce il seguente errore:

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

Il compilatore si lamenta di questo void cannot be converted to Void. Non so come specificare il tipo di interfaccia della funzione nella firma in modo myForEachtale che il codice venga compilato. So che potrei semplicemente cambiare il tipo di ritorno di displayIntal Voide poi tornare null. Tuttavia, potrebbero esserci situazioni in cui non è possibile modificare il metodo che voglio passare altrove. C'è un modo semplice per riutilizzarlo displayIntcosì com'è?

Risposte:


244

Stai tentando di utilizzare il tipo di interfaccia errato. Il tipo Funzione non è appropriato in questo caso perché riceve un parametro e ha un valore di ritorno. Invece dovresti usare Consumer (precedentemente noto come Block)

Il tipo di funzione è dichiarato come

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

Tuttavia, il tipo di consumatore è compatibile con quello che stai cercando:

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

Pertanto, il consumatore è compatibile con i metodi che ricevono una T e non restituiscono nulla (nulla). E questo è quello che vuoi.

Ad esempio, se volessi visualizzare tutti gli elementi in un elenco, potrei semplicemente creare un consumatore per quello con un'espressione lambda:

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

Si può vedere sopra che in questo caso, l'espressione lambda riceve un parametro e non ha valore di ritorno.

Ora, se volessi usare un riferimento al metodo anziché un'espressione lambda per creare un consumo di questo tipo, allora avrei bisogno di un metodo che riceve una stringa e restituisce il vuoto, giusto ?.

Potrei usare diversi tipi di riferimenti a metodi, ma in questo caso sfruttiamo un riferimento a un metodo oggetto usando il printlnmetodo System.outnell'oggetto, in questo modo:

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

O potrei semplicemente fare

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

Il printlnmetodo è appropriato perché riceve un valore e ha un tipo restituito vuoto, proprio come il acceptmetodo in Consumer.

Quindi, nel tuo codice, è necessario modificare la firma del metodo in modo simile a:

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

E quindi dovresti essere in grado di creare un consumatore, usando un riferimento di metodo statico, nel tuo caso facendo:

myForEach(theList, Test::displayInt);

Alla fine, potresti persino eliminare del myForEachtutto il tuo metodo e semplicemente fare:

theList.forEach(Test::displayInt);

Informazioni sulle funzioni di cittadini di prima classe

Detto questo, la verità è che Java 8 non avrà funzioni come cittadini di prima classe poiché un tipo di funzione strutturale non verrà aggiunto al linguaggio. Java offrirà semplicemente un modo alternativo per creare implementazioni di interfacce funzionali da espressioni lambda e riferimenti a metodi. Alla fine le espressioni lambda e i riferimenti ai metodi saranno legati ai riferimenti agli oggetti, quindi tutto ciò che abbiamo sono gli oggetti come cittadini di prima classe. L'importante è che la funzionalità sia presente poiché possiamo passare oggetti come parametri, collegarli a riferimenti variabili e restituirli come valori da altri metodi, quindi servono praticamente a uno scopo simile.


1
A proposito, è Blockstato modificato Consumernelle ultime versioni dell'API JDK 8
Edwin Dalorzo il

4
L'ultimo paragrafo analizza alcuni fatti importanti: le espressioni lambda e i riferimenti ai metodi sono più di un'aggiunta sintattica. La stessa JVM fornisce nuovi tipi per gestire i riferimenti ai metodi in modo più efficiente rispetto alle classi anonime (soprattutto MethodHandle) e, utilizzando questo, il compilatore Java è in grado di produrre codice più efficiente, ad esempio implementando lambda senza chiusura come metodi statici, impedendo così la generazione di classi aggiuntive.
Feuermurmel,

7
E la versione corretta di Function<Void, Void>è Runnable.
OrangeDog,

2
@OrangeDog Questo non è del tutto vero. Nei commenti Runnablee nelle Callableinterfacce è scritto che dovrebbero essere usati insieme ai thread . La fastidiosa conseguenza di ciò è che alcuni strumenti di analisi statica (come Sonar) si lamentano se si chiama run()direttamente il metodo.
Denis,

Proveniente esclusivamente da un background Java, mi chiedo quali sono in particolare i casi in cui Java non può trattare le funzioni come cittadini di prima classe anche dopo l'introduzione di interfacce funzionali e lambda da Java8?
Vivek Sethi,

21

Quando è necessario accettare una funzione come argomento che non accetta argomenti e non restituisce alcun risultato (vuoto), a mio avviso è comunque meglio avere qualcosa di simile

  public interface Thunk { void apply(); }

da qualche parte nel tuo codice. Nei miei corsi di programmazione funzionale la parola "thunk" è stata usata per descrivere tali funzioni. Perché non è in java.util.function è oltre la mia comprensione.

In altri casi trovo che anche quando java.util.function ha qualcosa che corrisponda alla firma che desidero - non sempre si sente bene quando la denominazione dell'interfaccia non corrisponde all'uso della funzione nel mio codice. Immagino sia un punto simile che è stato fatto altrove qui riguardo a "Runnable" - che è un termine associato alla classe Thread - quindi mentre può avere la firma di cui ho bisogno, è comunque probabile che confonda il lettore.


In particolare Runnablenon consente eccezioni verificate, rendendolo inutile per molte applicazioni. Dal momento che Callable<Void>non è utile neanche, è necessario definire il proprio sembra. Brutta.
Jesse Glick,

In riferimento al problema dell'eccezione verificata, le nuove funzioni di Java in generale lottano con questo. è un enorme blocco per alcune cose come l'API Stream. Progettazione molto scarsa da parte loro.
user2223059

0

Impostare il tipo restituito su Voidanziché voidereturn null

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

O

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

-2

Sento che dovresti usare l'interfaccia Consumer invece di Function<T, R>.

Un consumatore è sostanzialmente un'interfaccia funzionale progettata per accettare un valore e non restituire nulla (cioè nulla)

Nel tuo caso, puoi creare un consumatore altrove nel tuo codice in questo modo:

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

Quindi puoi sostituire il myForEachcodice con lo snippet di seguito:

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

Trattate myFunction come un oggetto di prima classe.


2
Cosa aggiunge questo oltre alle risposte esistenti?
Matteo Leggi il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.