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 well, but have no idea about lambda expressions in Java. Or, perhaps, you’ve already heard something about lambdas, but without details.

  • for those who have some understanding of lambda expressions, but are still afraid and unusual to use them.

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 the variables that are "visible" to them from the place 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. It is enough just to create a variable of a reference type 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 indicate it as final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Here our 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++;
It does not compile for the same reason as the option with an anonymous class: counterit should 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 calling methods. From inside a lambda expression, you can not only access all “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 “accessible” to be called inside the method main(), so it is also accessible to call 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 is worth asking: when will the code inside the lambda expression be executed? At the moment of creation? Or at the moment when (still unknown where) it will be called? It's quite 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 lambda expression code was executed at the very end, after the thread was created and only when the program execution process reached the actual execution of the method run(). And not at all at the time of its announcement. By declaring a lambda expression, we 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, but just calls some method.
x -> System.out.println(x)
They handed him something х, and it simply called him System.out.println()and passed him there х. In this case, we can replace it with a link to the method we need. Like this:
System.out::println
Yes, without the parentheses 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));
On the last line we use a method forEach()that accepts an interface object Consumer. This is again a functional interface with only one method 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 indicate the type of the parameter, but indicate that it will be called х). 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 on the screen what is in the variable х. The method itself forEach()goes through all the elements of the collection, calls Consumerthe method of the interface object passed to it (our lambda) accept(), where it passes each element from the collection. As I already said, this is a lambda -expression (simply calling another method) we can replace 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 accept anything (it is overloaded for all primitives and for any objects), instead of a lambda expression, we can pass in 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 are encountering 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 brackets at the end), but rather we pass the reference to this method itself.
strings.forEach(System.out.println());
we will have a compilation error. Because before the call forEach(), Java will see that it is being called System.out.println(), it will understand that it is being returned voidand will try voidto pass this to forEach()the object of type that is waiting there 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 link to the constructor NameКласса::new
    Using method links is very convenient when there is a ready-made method that you are completely satisfied with, 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. That's all.

Interesting difference between anonymous class and lambda expression

In an anonymous class, the keyword thispoints to an object of that anonymous class. And if we use it thisinside a lambda, we will get access to the object of the framing class. Where we actually wrote this expression. This happens because lambda expressions, when compiled, become a private method of the class where they are written. I would not recommend using this “feature”, since it has a side effect, which contradicts the principles of functional programming. But this approach is quite consistent with OOP. ;)

Where did I get the information from or what else to read

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