JavaRush /Blogue Java /Random-PT /Popular sobre expressões lambda em Java. Com exemplos e t...
Стас Пасинков
Nível 26
Киев

Popular sobre expressões lambda em Java. Com exemplos e tarefas. Parte 2

Publicado no grupo Random-PT
Para quem é este artigo?
  • Para quem leu a primeira parte deste artigo;

  • Para quem pensa que já conhece bem o Java Core, mas não tem ideia sobre expressões lambda em Java. Ou talvez você já tenha ouvido algo sobre lambdas, mas sem detalhes.

  • para quem tem algum entendimento de expressões lambda, mas ainda tem medo e é incomum de usá-las.

Acesso a variáveis ​​externas

Este código será compilado com uma classe anônima?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Não. A variável counterdeve ser final. Ou não necessariamente final, mas em qualquer caso não pode alterar o seu valor. O mesmo princípio é usado em expressões lambda. Eles têm acesso a todas as variáveis ​​que lhes são “visíveis” desde o local onde são declaradas. Mas o lambda não deve alterá-los (atribuir um novo valor). É verdade que existe uma opção para contornar essa limitação em aulas anônimas. Basta criar uma variável do tipo referência e alterar o estado interno do objeto. Neste caso, a própria variável apontará para o mesmo objeto, e neste caso você pode indicá-la com segurança como final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Aqui nossa variável counteré uma referência a um objeto do tipo AtomicInteger. E para alterar o estado deste objeto, é utilizado o método incrementAndGet(). O valor da variável em si não muda enquanto o programa está em execução e sempre aponta para o mesmo objeto, o que nos permite declarar uma variável imediatamente com a palavra-chave final. Os mesmos exemplos, mas com expressões lambda:
int counter = 0;
Runnable r = () -> counter++;
Não compila pelo mesmo motivo que a opção com classe anônima: counternão deve mudar enquanto o programa está em execução. Mas assim - está tudo bem:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Isso também se aplica à chamada de métodos. De dentro de uma expressão lambda, você pode não apenas acessar todas as variáveis ​​“visíveis”, mas também chamar os métodos aos quais você tem acesso.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Embora o método staticMethod()seja privado, ele é “acessível” para ser chamado dentro do método main(), portanto também é acessível para ser chamado de dentro do lambda que é criado no método main.

O momento de execução do código da expressão lambda

Essa pergunta pode parecer muito simples para você, mas vale a pena perguntar: quando o código dentro da expressão lambda será executado? No momento da criação? Ou no momento em que (ainda não se sabe onde) será chamado? É muito fácil verificar.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Saída em exibição:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Pode-se observar que o código da expressão lambda foi executado bem no final, após a criação do thread e somente quando o processo de execução do programa atingiu a execução real do método run(). E de forma alguma no momento do seu anúncio. Ao declarar uma expressão lambda, apenas criamos um objeto do tipo Runnablee descrevemos o comportamento de seu método run(). O método em si foi lançado muito mais tarde.

Referências de métodos?

Não está diretamente relacionado aos lambdas em si, mas acho que seria lógico dizer algumas palavras sobre isso neste artigo. Digamos que temos uma expressão lambda que não faz nada de especial, mas apenas chama algum método.
x -> System.out.println(x)
Eles lhe entregaram algo х, e ele simplesmente o chamou System.out.println()e o passou ali х. Neste caso, podemos substituí-lo por um link para o método que necessitamos. Assim:
System.out::println
Sim, sem os parênteses no final! Exemplo mais completo:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
Na última linha usamos um método forEach()que aceita um objeto de interface Consumer. Esta é novamente uma interface funcional com apenas um método void accept(T t). Assim, escrevemos uma expressão lambda que leva um parâmetro (como é digitado na própria interface, não indicamos o tipo do parâmetro, mas indicamos que será chamado х). No corpo da expressão lambda escrevemos o código que será executado quando o método for chamado accept(). Aqui simplesmente exibimos na tela o que está na variável х. O próprio método forEach()percorre todos os elementos da coleção, chama Consumero método do objeto de interface passado para ele (nosso lambda) accept(), onde passa cada elemento da coleção. Como já disse, esta é uma expressão lambda (simplesmente chamando outro método) que podemos substituir por uma referência ao método que precisamos.
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
O principal é que os parâmetros aceitos dos métodos (println()e accept()). Como o método println()pode aceitar qualquer coisa (está sobrecarregado para todas as primitivas e para quaisquer objetos), em vez de uma expressão lambda, podemos passar forEach()apenas uma referência ao método println(). Em seguida, forEach()ele pegará cada elemento da coleção e passará diretamente para o método println(). Para aqueles que estão encontrando isso pela primeira vez, por favor note Por favor, note que não chamamos o método System.out.println()(com pontos entre as palavras e com colchetes no final), mas sim passamos a referência para este método em si.
strings.forEach(System.out.println());
teremos um erro de compilação. Porque antes da chamada forEach()o Java vai ver que está sendo chamado System.out.println(), vai entender que está sendo retornado voide vai tentar voidpassar isso para forEach()o objeto do tipo que está esperando ali Consumer.

Sintaxe para usar referências de métodos

É bem simples:
  1. Passando uma referência para um método estáticoNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. Passando uma referência a um método não estático usando um objeto existenteNameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. Passamos uma referência a um método não estático usando a classe na qual tal método é implementadoNameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            users.forEach(User::print);
        }
    
        private static class User {
            private String name;
    
            private User(String name) {
                this.name = name;
            }
    
            private void print() {
                System.out.println(name);
            }
        }
    }
  4. Passando um link para o construtor NameКласса::new
    Usar links de método é muito conveniente quando existe um método pronto com o qual você está completamente satisfeito e deseja usá-lo como retorno de chamada. Neste caso, em vez de escrever uma expressão lambda com o código desse método, ou uma expressão lambda onde simplesmente chamamos este método, simplesmente passamos uma referência a ele. Isso é tudo.

Diferença interessante entre classe anônima e expressão lambda

Em uma classe anônima, a palavra-chave thisaponta para um objeto dessa classe anônima. E se usarmos thisdentro de um lambda, teremos acesso ao objeto da classe de enquadramento. Onde realmente escrevemos esta expressão. Isso acontece porque as expressões lambda, quando compiladas, tornam-se um método privado da classe onde são escritas. Eu não recomendaria usar esse “recurso”, pois tem um efeito colateral que contradiz os princípios da programação funcional. Mas esta abordagem é bastante consistente com OOP. ;)

De onde obtive as informações ou o que mais ler

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION