JavaRush /Java Blog /Random EN /Popular about lambda expressions in Java. With examples a...
Стас Пасинков
Level 26
Киев

Popular about lambda expressions in Java. With examples and tasks. Part 2

Published in the Random EN group
Who is this article for?
  • For those who read the first part of this article;

  • for those who think they already know Java Core quite well, but have no idea about lambda expressions in Java. Or, perhaps, you have already heard something about lambdas, but without details.

  • for those who have some understanding of lambda expressions, but using them is still scary and unusual.

Access to external variables

Will this code compile with an anonymous class?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
No. The variable countermust be final. Or not necessarily final, but in any case, it cannot change its value. The same principle is used in lambda expressions. They have access to all variables that are "visible" to them from where they are declared. But the lambda should not change them (assign a new value). True, there is an option to bypass this limitation in anonymous classes. You just need to create a reference type variable and change the internal state of the object. In this case, the variable itself will point to the same object, and in this case, you can safely specify it as final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Here we have a variable counteris a reference to an object of type AtomicInteger. And to change the state of this object, the method is used incrementAndGet(). The value of the variable itself does not change while the program is running and always points to the same object, which allows us to declare a variable immediately with the keyword final. The same examples, but with lambda expressions:
int counter = 0;
Runnable r = () -> counter++;
Will not compile for the same reason as the anonymous class option: counterit must not change while the program is running. But like this - everything is fine:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
This also applies to method calls. From inside the lambda expression, you can not only access all the "visible" variables, but also call those methods that you have access to.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Although the method staticMethod()is private, it is "available" to be called inside the method main(), so it is also available to be called from inside the lambda that is created in the method main.

The moment of execution of the lambda expression code

This question may seem too simple to you, but it should be asked: when will the code inside the lambda expression be executed? At the time of creation? Or at the moment when (it is still unknown where) it will be called? It's pretty easy to check.
System.out.println("Запуск программы");

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

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

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

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

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

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Output on display:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
It can be seen that the code of the lambda expression was executed at the very end, after the thread was created and only when the program execution process reached the actual execution of the run(). And not at the time of its announcement. By declaring a lambda expression, we have only created an object of the type Runnableand described the behavior of its method run(). The method itself was launched much later.

Method References?

Not directly related to lambdas themselves, but I think it would be logical to say a few words about it in this article. Let's say we have a lambda expression that doesn't do anything special, it just calls some method.
x -> System.out.println(x)
A certain one was handed over to him х, and it simply called System.out.println()and transferred it there х. In this case, we can replace it with a link to the method we need. Like this:
System.out::println
Yes, no brackets at the end! More complete example:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
In the last line, we use a method forEach()that takes an interface object Consumer. This is again a functional interface with only one void accept(T t). Accordingly, we write a lambda expression that takes one parameter (since it is typed in the interface itself, we do not specify the parameter type, but indicate that we will call it. In the body of the lambda expression, we write the code that will be executed when the method is х)called accept()Here we simply display what is in the variable х... The method itself forEach()goes through all the elements of the collection, calls the Consumermethod passed to it by the interface object (our lambda)accept(), where it passes each element from the collection. As I said, we can replace such a lambda expression (just calling another method) with a reference to the method we need. Then our code will look like this:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
The main thing is that the accepted parameters of the methods (println()and accept()). Since the method println()can take anything (it is overloaded for all primitives and for any objects, we can instead pass a lambda expression to forEach()just a reference to the method println(). Then forEach()it will take each element of the collection and pass it directly to the method println(). For those who encounter this for the first time, please note Please note that we do not call the method System.out.println()(with dots between words and with parentheses at the end), but we pass the reference to this method itself.
strings.forEach(System.out.println());
we will have a compilation error. Since before the call forEach(), Java will see that it is being called System.out.println(), understand what is being returned voidand will try voidto pass this to forEach(), which is waiting for an object of type Consumer.

Syntax for using Method References

It's pretty simple:
  1. Passing a reference to a static methodNameКласса:: 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. Passing a reference to a non-static method using an existing objectNameПеременнойСОбъектом:: 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. We pass a reference to a non-static method using the class in which such a method is implementedNameКласса:: 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. Passing a reference to the constructor NameКласса::new
    Using method references is very convenient when you have a ready-made method that suits you perfectly, and you would like to use it as a callback. In this case, instead of writing a lambda expression with the code of that method, or a lambda expression where we simply call this method, we simply pass a reference to it. And that's it.

An interesting difference between an anonymous class and a lambda expression

In an anonymous class, the keyword thisrefers to an object of that anonymous class. And if used thisinside a lambda, we will get access to the object of the framing class. The one where we actually wrote this expression. This is because lambda expressions are compiled into a private method of the class where they are written. I would not recommend using this "feature" because it has a side effect, which is contrary to the principles of functional programming. But this approach is quite consistent with OOP. ;)

Where did I get the information or what else to read

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION