Eu tenho mudado para Java a partir de C # depois de algumas recomendações de alguns no CodeReview. Portanto, quando eu estava pesquisando o LWJGL, lembrei-me de que toda chamada a Display
ser executada no mesmo encadeamento em que o Display.create()
método foi chamado. Lembrando disso, criei uma aula que se parece um pouco com isso.
public class LwjglDisplayWindow implements DisplayWindow {
private final static int TargetFramesPerSecond = 60;
private final Scheduler _scheduler;
public LwjglDisplayWindow(Scheduler displayScheduler, DisplayMode displayMode) throws LWJGLException {
_scheduler = displayScheduler;
Display.setDisplayMode(displayMode);
Display.create();
}
public void dispose() {
Display.destroy();
}
@Override
public int getTargetFramesPerSecond() { return TargetFramesPerSecond; }
@Override
public Future<Boolean> isClosed() {
return _scheduler.schedule(() -> Display.isCloseRequested());
}
}
Ao escrever esta classe, você notará que eu criei um método chamado isClosed()
que retorna a Future<Boolean>
. Isso envia uma função para minha Scheduler
interface (que nada mais é do que um invólucro em torno de um ScheduledExecutorService
. Ao escrever o schedule
método no Scheduler
, observei que eu poderia usar um Supplier<T>
argumento ou um Callable<T>
argumento para representar a função que é transmitida. ScheduledExecutorService
Não continha um substituir por Supplier<T>
mas notei que a expressão lambda () -> Display.isCloseRequested()
é realmente do tipo compatível com ambos Callable<bool>
e Supplier<bool>
.
A minha pergunta é: existe uma diferença entre os dois, semanticamente ou de outra forma - e se sim, o que é, para que eu possa aderir?
Respostas:
A resposta curta é que ambos estão usando interfaces funcionais, mas também vale a pena notar que nem todas as interfaces funcionais devem ter a
@FunctionalInterface
anotação. A parte crítica do JavaDoc diz:E a definição mais simples de uma interface funcional é (simplesmente, sem outras exclusões) apenas:
Portanto, na resposta de @Maciej Chalapuk , também é possível descartar a anotação e especificar a lambda desejada:
Agora, o que cria interfaces funcionais
Callable
eSupplier
funcionais é que elas contêm exatamente um método abstrato:Callable.call()
Supplier.get()
Como os dois métodos não aceitam um argumento (em oposição ao
MyInterface.myCall(int)
exemplo), os parâmetros formais estão vazios (()
).Como você já deve poder inferir, isso ocorre apenas porque os dois métodos abstratos retornarão o tipo da expressão usada. Você definitivamente deveria usar um
Callable
dado seu uso de aScheduledExecutorService
.Exploração adicional (além do escopo da pergunta)
Ambas as interfaces vêm de pacotes totalmente diferentes , portanto, também são usadas de maneira diferente. No seu caso, não vejo como uma implementação de será usada, a menos que esteja fornecendo um :
Supplier<T>
Callable
O primeiro
() ->
pode ser vagamente interpretado como "aSupplier
dá ..." e o segundo como "aCallable
dá ...".return value;
é o corpo daCallable
lambda, que em si é o corpo daSupplier
lambda.No entanto, o uso neste exemplo artificial fica um pouco complicado, pois agora você precisa,
get()
desde oSupplier
primeiro, antes deget()
escrever o seu resultado noFuture
, o que, porcall()
sua vez, seráCallable
assíncrono.fonte
Uma diferença básica entre as 2 interfaces é que Callable permite que exceções verificadas sejam lançadas na implementação, enquanto o Fornecedor não.
Aqui estão os trechos de código do JDK que destacam isso -
fonte
Supplier
, como a API de fluxo. Você absolutamente pode passar lambdas e referências de métodos a métodos que usam aCallable
.Como você observa, na prática, eles fazem a mesma coisa (fornecem algum tipo de valor); no entanto, em princípio, eles pretendem fazer coisas diferentes:
A
Callable
é " Uma tarefa que retorna um resultado , enquanto aSupplier
é" um fornecedor de resultados ". Em outras palavras, aCallable
é uma maneira de referenciar uma unidade de trabalho ainda não executada, enquanto aSupplier
é uma maneira de referenciar um valor ainda desconhecido.É possível que um
Callable
poderia fazer muito pouco trabalho e simplesmente retornar um valor. Também é possívelSupplier
que você possa fazer bastante trabalho (por exemplo, construir uma grande estrutura de dados). Mas, de um modo geral, o que lhe interessa é o principal objetivo. Por exemplo, umExecutorService
funciona comCallable
s, porque seu principal objetivo é executar unidades de trabalho. Um armazenamento de dados carregado preguiçosamente usaria umSupplier
, porque ele se preocupa com o que está sendo fornecido um valor, sem muita preocupação com a quantidade de trabalho que pode demorar.Outra maneira de expressar a distinção é que a
Callable
pode ter efeitos colaterais (por exemplo, gravar em um arquivo), enquanto aSupplier
geralmente deve ser livre de efeitos colaterais. A documentação não menciona explicitamente isso (já que não é um requisito ), mas eu sugiro pensar nesses termos. Se o trabalho for idempotente, use aSupplier
, se não, use aCallable
.fonte
Ambas são interfaces Java normais, sem semântica especial. Callable é uma parte da API simultânea. O fornecedor faz parte da nova API de programação funcional. Eles podem ser criados a partir de expressões lambda graças a alterações no Java8. @FunctionalInterface faz com que o compilador verifique se a interface está funcional e um erro se não estiver, mas uma interface não precisa dessa anotação para ser uma interface funcional e ser implementada por lambdas. É assim que um método pode ser uma substituição sem ser marcado como @Override, mas não vice-versa.
Você pode definir suas próprias interfaces compatíveis com lambdas e documentá-las com
@FunctionalInterface
anotação. A documentação é opcional.fonte
IntPredicate
em Java.