Java8 Lambdas vs classes anônimas

111

Como o Java8 foi lançado recentemente e suas novas expressões lambda parecem ser muito legais, eu queria saber se isso significa o fim das classes Anonymous com as quais estávamos tão acostumados.

Estive pesquisando um pouco sobre isso e encontrei alguns exemplos interessantes de como as expressões Lambda substituirão sistematicamente essas classes, como o método de classificação da Coleção, que costumava obter uma instância Anônima de Comparator para realizar a classificação:

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

Agora pode ser feito usando Lambdas:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

E parece surpreendentemente conciso. Portanto, minha pergunta é: há algum motivo para continuar usando essas classes em Java8 em vez de Lambdas?

EDITAR

Mesma pergunta, mas na direção oposta, quais são os benefícios de usar Lambdas em vez de classes anônimas, uma vez que Lambdas só podem ser usados ​​com interfaces de método único, esse novo recurso é apenas um atalho usado em poucos casos ou é realmente útil?

Amin Abu-Taleb
fonte
5
Claro, para todas aquelas classes anônimas que fornecem métodos com efeitos colaterais.
tobias_k
11
Apenas para sua informação, você também pode construir o comparador como:, Comparator.comparing(Person::getFirstName)se getFirstName()seria um método que retorna firstName.
skiwi
1
Ou classes anônimas com vários métodos, ou ...
Mark Rotteveel
1
Estou tentado a votar por fechar como muito amplo, especialmente devido às perguntas adicionais após EDIT .
Mark Rotteveel
1
Um bom artigo detalhado sobre este tópico: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Respostas:

108

Uma classe interna anônima (AIC) pode ser usada para criar uma subclasse de uma classe abstrata ou uma classe concreta. Um AIC também pode fornecer uma implementação concreta de uma interface, incluindo a adição de estado (campos). Uma instância de um AIC pode ser referenciada usando thisem seus corpos de método, portanto, métodos adicionais podem ser chamados, seu estado pode ser mutado com o tempo, etc. Nenhum desses se aplica a lambdas.

Eu acho que a maioria dos usos de AICs era para fornecer implementações sem estado de funções únicas e, portanto, podem ser substituídos por expressões lambda, mas existem outros usos de AICs para os quais lambdas não podem ser usados. Os AICs estão aqui para ficar.

ATUALIZAR

Outra diferença entre AICs e expressões lambda é que os AICs introduzem um novo escopo. Ou seja, os nomes são resolvidos a partir das superclasses e interfaces do AIC e podem sombrear nomes que ocorrem no ambiente envolvente lexicamente. Para lambdas, todos os nomes são resolvidos lexicamente.

Stuart Marks
fonte
1
Lambdas podem ter estado. A esse respeito, não vejo diferença entre Lambdas e AIC.
nosid
1
@nosid AICs, como instâncias de qualquer classe, podem conter estado em campos, e este estado é acessível a (e potencialmente mutável por) qualquer método da classe. Este estado existe até que o objeto seja GC, ou seja, tem extensão indefinida, portanto, pode persistir nas chamadas de método. O único estado com extensão indefinida que lambdas possui é capturado no momento em que o lambda é encontrado; este estado é imutável. Variáveis ​​locais dentro de um lambda são mutáveis, mas existem apenas enquanto uma invocação lambda está em andamento.
Stuart Marks
1
@nosid Ah, o hack de array de elemento único. Só não tente usar seu contador de vários tópicos. Se você vai alocar algo no heap e capturá-lo em um lambda, também pode usar um AIC e adicionar um campo que pode ser modificado diretamente. Usar um lambda dessa forma pode funcionar, mas por que se preocupar quando você pode usar um objeto real?
Stuart Marks
2
O AIC criará um arquivo parecido com este, A$1.classmas o Lambda não. Posso adicionar isso na diferença?
Asif Mushtaq
2
@UnKnown Essa é principalmente uma preocupação de implementação; isso não afeta a maneira como se programa com AICs versus lambdas, que é o principal assunto desta questão. Observe que uma expressão lambda gera uma classe com um nome como LambdaClass$$Lambda$1/1078694789. No entanto, essa classe é gerada instantaneamente pela metafábrica lambda, não pela javac, portanto, não há .classarquivo correspondente . Novamente, no entanto, essa é uma preocupação de implementação.
Stuart marca
60

Lambdas embora seja um ótimo recurso, só funcionará com tipos de SAM. Ou seja, interfaces com apenas um único método abstrato. Ele falhará assim que sua interface contiver mais de 1 método abstrato. É aí que as classes anônimas serão úteis.

Então, não, não podemos simplesmente ignorar classes anônimas. E apenas para sua informação, seu sort()método pode ser mais simplificado, pulando a declaração de tipo para p1e p2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

Você também pode usar a referência de método aqui. Você adiciona um compareByFirstName()método na Personclasse e usa:

Collections.sort(personList, Person::compareByFirstName);

ou adicione um getter para firstNameobter diretamente o método Comparatorfrom Comparator.comparing():

Collections.sort(personList, Comparator.comparing(Person::getFirstName));
Rohit Jain
fonte
4
Eu sabia, mas prefiro o longo em termos de legibilidade, porque senão poderia ser confuso descobrir de onde vêm essas variáveis.
Amin Abu-Taleb
@ AminAbu-Taleb Por que seria confuso. Essa é uma sintaxe Lambda válida. Os tipos são inferidos de qualquer maneira. De qualquer forma, é uma escolha pessoal. Você pode dar tipos explícitos. Sem problemas.
Rohit Jain
1
Há outra diferença sutil entre lambdas e classes anônimas: As classes anônimas podem ser anotadas diretamente com as novas anotações de tipo Java 8 , como, por exemplo new @MyTypeAnnotation SomeInterface(){};. Isso não é possível para expressões lambda. Para obter detalhes, veja minha pergunta aqui: Anotando a interface funcional de uma Expressão Lambda .
Balder
36

Desempenho lambda com classes anônimas

Quando o aplicativo é iniciado, cada arquivo de classe deve ser carregado e verificado.

As classes anônimas são processadas pelo compilador como um novo subtipo para a classe ou interface fornecida, portanto, será gerado um novo arquivo de classe para cada uma.

Lambdas são diferentes na geração de bytecode, são mais eficientes, usam a instrução invokedynamic que vem com o JDK7.

Para Lambdas, esta instrução é usada para atrasar a tradução da expressão lambda em bytecode até o tempo de execução. (a instrução será invocada apenas pela primeira vez)

Como resultado, a expressão Lambda se tornará um método estático (criado em tempo de execução). (Há uma pequena diferença com stateles e casos com estado, eles são resolvidos por meio de argumentos de método gerados)

Dmitriy Kuzkin
fonte
Cada lambda também precisa de uma nova classe, mas ela é gerada em tempo de execução, portanto, nesse sentido, lambdas não são mais eficientes do que classes anônimas. Lambdas são criados por meio do invokedynamicqual geralmente é mais lento do que o invokespecialusado para criar novas instâncias de classes anônimas. Portanto, nesse sentido, os lambdas também são mais lentos (no entanto, a JVM pode otimizar as invokedynamicchamadas na maioria das vezes).
ZhekaKozlov
3
@AndreiTomashpolskiy 1. Por favor, seja educado. 2. Leia este comentário de um engenheiro de compilador: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov
@ZhekaKozlov, você não precisa ser um engenheiro de compilador para ler o código-fonte do JRE e usar o javap / depurador. O que está faltando é que a geração de uma classe wrapper para um método lambda seja feita inteiramente na memória e não custa quase nada, enquanto a instanciação de um AIC envolve resolver e carregar o recurso de classe correspondente (o que significa chamada de sistema de E / S). Conseqüentemente, invokedynamiccom classes ad-hoc, a geração é extremamente rápida em comparação com classes anônimas compiladas.
Andrei Tomashpolskiy
@AndreiTomashpolskiy I / O não é necessariamente lento
ZhekaKozlov
14

Existem as seguintes diferenças:

1) Sintaxe

Expressões lambda parecem legais em comparação com Anonymous Inner Class (AIC)

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2. Âmbito

Uma classe interna anônima é uma classe, o que significa que ela tem escopo para variáveis ​​definidas dentro da classe interna.

Enquanto que a expressão lambda não é um escopo próprio, mas faz parte do escopo envolvente.

Uma regra semelhante se aplica a super e esta palavra - chave ao usar dentro da classe interna anônima e expressão lambda. No caso de classe interna anônima, esta palavra-chave se refere ao escopo local e a palavra-chave super se refere à superclasse da classe anônima. Enquanto no caso da expressão lambda, esta palavra-chave se refere ao objeto do tipo envolvente e super se refere à superclasse da classe envolvente.

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) Desempenho

Em tempo de execução, as classes internas anônimas exigem carregamento de classe, alocação de memória e inicialização de objeto e invocação de um método não estático, enquanto a expressão lambda é pura atividade de tempo de compilação e não incorre em custos extras durante o tempo de execução. Portanto, o desempenho da expressão lambda é melhor em comparação com as classes internas anônimas. **

** Sei que este ponto não é totalmente verdadeiro. Consulte a pergunta a seguir para obter detalhes. Lambda vs desempenho de classe interna anônima: reduzindo a carga no ClassLoader?

atom217
fonte
3

Lambda's em java 8 foi introduzido para programação funcional. Onde você pode evitar o código clichê. Me deparei com um artigo interessante sobre lambda.

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

É aconselhável usar funções lambda para lógicas simples. Se implementar lógica complexa usando lambdas, será uma sobrecarga na depuração do código em caso de problema.

Tim
fonte
0

As classes anônimas estão aí para ficar porque lambda é bom para funções com métodos abstratos únicos, mas para todos os outros casos as classes internas anônimas são o seu salvador.

spidy
fonte
-1
  • A sintaxe lambda não requer a gravação do código óbvio que o java pode inferir.
  • Ao usar invoke dynamic, lambda não é convertido de volta para as classes anônimas durante o tempo de compilação (Java não precisa passar pela criação de objetos, apenas se preocupa com a assinatura do método, pode vincular ao método sem criar objeto
  • lambda coloca mais ênfase no que queremos fazer ao invés do que temos que fazer antes de podermos fazer
MagGGG
fonte