Como especificar tipos de função para métodos void (não void) no Java8?

143

Estou brincando com o Java 8 para descobrir como funciona como cidadãos de primeira classe. Eu tenho o seguinte trecho:

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

O que estou tentando fazer é passar método displayIntpara método myForEachusando uma referência de método. Para o compilador produz o seguinte erro:

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

O compilador reclama disso void cannot be converted to Void. Não sei como especificar o tipo da interface da função na assinatura, de myForEachmodo que o código seja compilado. Eu sei que poderia simplesmente alterar o tipo de retorno de displayIntpara Voide depois retornar null. No entanto, pode haver situações em que não é possível alterar o método que desejo passar para outro lugar. Existe uma maneira fácil de reutilizar displayIntcomo é?

Valentin
fonte

Respostas:

244

Você está tentando usar o tipo de interface incorreto. O tipo Function não é apropriado neste caso, porque recebe um parâmetro e tem um valor de retorno. Em vez disso, você deve usar o Consumer (anteriormente conhecido como Block)

O tipo de função é declarado como

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

No entanto, o tipo Consumidor é compatível com o que você está procurando:

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

Como tal, o Consumidor é compatível com métodos que recebem um T e não retornam nada (nulo). E é isso que você quer.

Por exemplo, se eu quisesse exibir todos os elementos em uma lista, poderia simplesmente criar um consumidor para isso com uma expressão lambda:

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

Você pode ver acima que, nesse caso, a expressão lambda recebe um parâmetro e não tem valor de retorno.

Agora, se eu quiser usar uma referência de método em vez de uma expressão lambda para criar um consumo desse tipo, preciso de um método que receba uma String e retorne nulo, certo?

Eu poderia usar tipos diferentes de referências de método, mas, neste caso, vamos tirar proveito de uma referência de método de objeto usando o printlnmétodo no System.outobjeto, assim:

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

Ou eu poderia simplesmente fazer

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

O printlnmétodo é apropriado porque recebe um valor e tem um tipo de retorno nulo, assim como o acceptmétodo no Consumidor.

Portanto, no seu código, você precisa alterar a assinatura do método para algo como:

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

E você deve ser capaz de criar um consumidor, usando uma referência de método estático, no seu caso, fazendo:

myForEach(theList, Test::displayInt);

Por fim, você pode até se livrar myForEachcompletamente do seu método e simplesmente fazer:

theList.forEach(Test::displayInt);

Sobre as funções como cidadãos de primeira classe

Tudo dito, a verdade é que o Java 8 não terá funções como cidadãos de primeira classe, pois um tipo de função estrutural não será adicionado à linguagem. O Java simplesmente oferecerá uma maneira alternativa de criar implementações de interfaces funcionais a partir de expressões lambda e referências de métodos. Por fim, expressões lambda e referências a métodos serão vinculadas a referências a objetos; portanto, tudo o que temos são objetos como cidadãos de primeira classe. O importante é que a funcionalidade existe, já que podemos passar objetos como parâmetros, vinculá-los a referências de variáveis ​​e retorná-los como valores de outros métodos; então, eles praticamente servem a um propósito semelhante.

Edwin Dalorzo
fonte
1
By the way, Blockfoi alterado para Consumernos últimos lançamentos da API do JDK 8
Edwin Dalorzo
4
O último parágrafo aborda alguns fatos importantes: expressões Lambda e referências a métodos são mais do que uma adição sintática. A própria JVM fornece novos tipos para manipular referências de método com mais eficiência do que com classes anônimas (o mais importante MethodHandle) e, usando isso, o compilador Java pode produzir código mais eficiente, por exemplo, implementando lambdas sem fechamento como métodos estáticos, impedindo a geração de aulas adicionais.
Feuermurmel
7
E a versão correta do Function<Void, Void>é Runnable.
OrangeDog 27/09/16
2
@ OrangeDog Isso não é totalmente verdade. Nos comentários Runnablee Callableinterfaces, está escrito que eles devem ser usados ​​em conjunto com threads . A conseqüência irritante disso é que algumas ferramentas de análise estática (como o Sonar) irão reclamar se você chamar o run()método diretamente.
Denis
Vindo apenas de um background Java, pergunto-me quais são os casos em que Java não pode tratar funções como cidadãos de primeira classe, mesmo após a introdução de interfaces funcionais e lambdas desde o Java8?
Vivek Sethi
21

Quando você precisa aceitar uma função como argumento que não aceita argumentos e não retorna resultado (vazio), na minha opinião ainda é melhor ter algo como

  public interface Thunk { void apply(); }

em algum lugar do seu código. Nos meus cursos de programação funcional, a palavra 'thunk' foi usada para descrever essas funções. Por que não está no java.util.function está além da minha compreensão.

Em outros casos, acho que mesmo quando o java.util.function tem algo que coincide com a assinatura que eu quero - ainda nem sempre parece certo quando a nomeação da interface não corresponde ao uso da função no meu código. Eu acho que é um argumento semelhante que é feito em outro lugar aqui sobre 'Runnable' - que é um termo associado à classe Thread -, portanto, embora possa ter a assinatura de que eu preciso, ainda é provável que confunda o leitor.

LOAS
fonte
Em particular Runnable, não permite exceções verificadas, tornando-o inútil para muitos aplicativos. Como Callable<Void>também não é útil, você precisa definir o que parece. Feio.
perfil completo de Jesse Glick
Em referência à questão da exceção verificada, as novas funções do Java geralmente lutam com isso. é um grande bloqueador para algumas coisas, como a API Stream. Design muito ruim da parte deles.
user2223059
0

Defina o tipo de retorno como em Voidvez de voidereturn null

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

OU

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

Eu sinto que você deveria estar usando a interface Consumer em vez de Function<T, R>.

Um consumidor é basicamente uma interface funcional projetada para aceitar um valor e não retornar nada (ou seja, nulo)

No seu caso, você pode criar um consumidor em outro lugar no seu código, assim:

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

Em seguida, você pode substituir seu myForEachcódigo pelo snippet abaixo:

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

Você trata myFunction como um objeto de primeira classe.

Anthony Anyanwu
fonte
2
O que isso acrescenta além das respostas existentes?
Matthew Leia