Класи Java8 Lambdas vs Anonymous


111

Оскільки Java8 нещодавно був випущений, і його абсолютно нові лямбдаські вирази виглядають справді крутими, мені було цікаво, чи означає це смерть класів Anonymous, до яких ми так звикли.

Я трохи досліджував це і знайшов кілька цікавих прикладів того, як вирази Lambda систематично замінюватимуть такі класи, такий метод сортування Collection, який використовував для отримання анонімного екземпляра компаратора для виконання сортування:

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

Тепер це можна зробити за допомогою лямбда:

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

І виглядає напрочуд лаконічно. Отже, моє запитання: чи є якась причина продовжувати використовувати ці класи в Java8 замість Lambdas?

EDIT

Це ж питання, але в зворотному напрямку, які переваги використання Lambdas замість Anonymous класів, оскільки Lambdas можна використовувати лише з інтерфейсами одного методу, чи ця нова функція є лише ярликом, який використовується лише в кількох випадках, чи це дійсно корисно?


5
Звичайно, для всіх тих анонімних класів, які надають методи побічних ефектів.
tobias_k

11
Тільки для вашої інформації ви також можете побудувати компаратор як:, Comparator.comparing(Person::getFirstName)якщо getFirstName()це був би метод повернення firstName.
skiwi

1
Або анонімні заняття кількома методами, або ...
Марк Ротвевель

1
Мені спокушається проголосувати за занадто широкий, особливо через додаткові запитання після EDIT .
Марк Ротвевель

1
Приємна поглиблена стаття на цю тему: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Відповіді:


108

Анонімний внутрішній клас (AIC) може бути використаний для створення підкласу абстрактного класу або конкретного класу. AIC також може забезпечити конкретну реалізацію інтерфейсу, включаючи додавання стану (полів). Екземпляр АПК можна посилатись на використання thisв його органах методів, тому в подальшому можуть бути застосовані подальші методи, його стан може бути мутованим з часом тощо. Жодне з них не стосується лямбдів.

Я здогадуюсь, що більшість застосувань AIC полягають у наданні безрегулярних реалізацій одиничних функцій і тому їх можна замінити лямбда-виразами, але є й інші способи використання AIC, для яких лямбда не можуть бути використані. АПК тут залишилися.

ОНОВЛЕННЯ

Ще одна відмінність між AIC і лямбда-виразами полягає в тому, що AIC вводять нову сферу застосування. Тобто імена вирішуються із суперклассів та інтерфейсів AIC і можуть затінювати імена, які зустрічаються у лексично оточуючому середовищі. Для лямбдів усі назви розв’язуються лексично.


1
Лямбди можуть мати стан. У цьому плані я не бачу різниці між Ламбдасом та AIC.
носід

1
@nosid AIC, як і екземпляри будь-якого класу, можуть містити стан у полях, і цей стан доступний (і потенційно може змінюватися) будь-яким методом класу. Цей стан існує до тих пір, поки об'єкт не буде GC'd, тобто він має невизначений ступінь, тому він може зберігатись у викликах методів. Єдиний стан з невизначеною мірою лямбда є захопленим під час зустрічі лямбда; цей стан непорушний. Локальні змінні в межах лямбда змінюються, але вони існують лише під час виклику лямбда.
Стюарт відзначає

1
@nosid Ах, одноелементний масив зламається. Просто не намагайтеся використовувати свій лічильник з декількох потоків. Якщо ви збираєтесь виділити щось на купі і зафіксувати це в лямбда, ви можете також скористатися AIC та додати поле, яке ви можете безпосередньо мутувати. Використання лямбда таким чином може спрацювати, але навіщо турбуватися, коли ти можеш використовувати справжній об’єкт?
Стюарт відзначає

2
AIC створить файл на зразок цього, A$1.classале Lambda цього не зробить. Чи можу я додати це в різниці?
Асиф Муштак

2
@UnK known Це, головним чином, питання щодо впровадження; це не впливає на те, як одна програма з АПК проти лямбда, про що в основному йдеться в цьому питанні. Зауважте, що лямбда-вираз дійсно генерує клас з такою назвою LambdaClass$$Lambda$1/1078694789. Однак цей клас генерується на ходу метамфабрикою лямбда, а не тим javac, тому немає відповідного .classфайлу. Знову ж таки, це є проблемою щодо впровадження.
Стюарт Маркс

60

Лямбди, хоча і чудова функція, працюватимуть лише з типами SAM. Тобто інтерфейси лише з одним абстрактним методом. Це не вдасться, як тільки ваш інтерфейс містить більше 1 абстрактного методу. Саме тут будуть корисні анонімні заняття.

Отже, ні, ми не можемо просто ігнорувати анонімні класи. І просто FYI, ваш sort()метод можна спростити, пропустивши декларацію типу для p1та p2:

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

Тут також можна скористатися посиланням на метод. Або ви додаєте compareByFirstName()метод у Personкласі та використовуєте:

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

або додати поглинач для firstName, безпосередньо отримати Comparatorз Comparator.comparing()методу:

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
Я знав, але віддаю перевагу довгому з точки зору читабельності, бо в іншому випадку можна заплутати, щоб дізнатися, звідки беруться ці змінні.
Амін Абу-Талеб

@ AminAbu-Taleb Чому це буде заплутано. Це дійсний синтаксис лямбда. Типи все одно виводяться з висновку. У будь-якому випадку це особистий вибір. Типи можна надати явно. Немає питань.
Rohit Jain

1
Існує ще одна тонка різниця між лямбдами та анонімними класами: анонімні класи можна безпосередньо коментувати новими анотаціями типу Java 8 , як наприклад new @MyTypeAnnotation SomeInterface(){};. Для лямбда-виразів це неможливо. Детальніше див. Моє запитання тут: Анотація функціонального інтерфейсу Lambda Expression .
Бальдер

36

Виступ лямбда з класами Anonymous

Після запуску програми кожен файл класу повинен бути завантажений та перевірений.

Анонімні класи обробляються компілятором як новий підтип для даного класу або інтерфейсу, тому для кожного буде створений новий файл класу.

Лямбди відрізняються генеруванням байт-кодів, вони більш ефективні, використовуються викликова динамічна інструкція, що постачається з JDK7.

Для лямбдасів ця інструкція використовується для затримки перекладу лямбдаського вираження в байт-код до часу виконання. (інструкція буде запущена лише вперше)

У результаті вираз лямбда стане статичним методом (створеним під час виконання). (Існує невелика різниця зі статтями та державними випадками, вони вирішуються за допомогою створених аргументів методу)


Кожна лямбда також потребує нового класу, але вона генерується під час виконання, тому в цьому сенсі лямбда не є більш ефективними, ніж анонімні класи. Лямбди створюються за допомогою invokedynamicяких, як правило, повільніше, ніж invokespecialвикористовуються для створення нових примірників анонімних класів. Отже, в цьому сенсі лямбди теж повільніші (проте, JVM може оптимізувати invokedynamicдзвінки більшу частину часу).
ЖекаКозлов

3
@AndreiTomashpolskiy 1. Будьте ввічливі. 2. Прочитайте цей коментар інженера-компілятора: habrahabr.ru/post/313350/comments/#comment_9885460
Жека Козлов

@ZhekaKozlov, вам не потрібно бути інженером-компілятором, щоб читати вихідний код JRE та використовувати javap / debugger. Те, що вам не вистачає, полягає в тому, що генерація класу обгортки для лямбда-методу виконується повністю в пам'яті і коштує майже нічого, тоді як інстанціювання AIC передбачає вирішення та завантаження відповідного ресурсу класу (що означає системний виклик вводу-виводу). Таким чином, invokedynamicпокоління спеціальних класів швидко розвивається порівняно зі складеними анонімними класами.
Андрій Томашпольський

@AndreiTomashpolskiy I / O не обов’язково повільний
Жека Козлов

14

Існують такі відмінності:

1) Синтаксис

Лямбдаські вирази виглядають акуратно порівняно з 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) Сфера застосування

Анонімний внутрішній клас - це клас, що означає, що він має область для змінної, визначеної всередині внутрішнього класу.

В той час, як лямбда-вираз не є власною сферою дії, а є частиною об'єму, що охоплює.

Подібне правило стосується супер та цього ключового слова при використанні внутрішнього анонімного класу та лямбда-виразу. У разі анонімного внутрішнього класу це ключове слово відноситься до локальної області дії, а супер ключове слово - до суперкласу анонімного класу. У той час як у випадку вираження лямбда це ключове слово посилається на об'єкт типу, що обгороджує, а super посилатиметься на суперклас класу, що додає.

//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) Продуктивність

Під час виконання анонімні внутрішні класи вимагають завантаження класів, розподілу пам’яті та ініціалізації об'єкта та виклику нестатичного методу, тоді як лямбда-вираз є чистою роботою часу компіляції та не вимагає додаткових витрат під час виконання. Тож виконання лямбда-експресії краще порівняно з анонімними внутрішніми класами. **

** Я усвідомлюю, що цей пункт не зовсім вірний. Для детальної інформації зверніться до наступного питання. Lambda vs анонімні показники внутрішнього класу: зменшення навантаження на ClassLoader?


3

Лямбда в java 8 була представлена ​​для функціонального програмування. Де можна уникнути кодового коду. Я натрапив на цю цікаву статтю про лямбда.

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

Доцільно використовувати лямбда-функції для простої логіки. Якщо реалізація складної логіки за допомогою лямбда буде накладним налагодженням коду у разі видачі.


0

Анонімний клас залишається там, тому що лямбда хороший для функцій з єдиними абстрактними методами, але для всіх інших випадків анонімні внутрішні класи - ваш спаситель.


-1
  • лямбда-синтаксис не вимагає писати очевидний код, який може зробити висновок Java.
  • При використанні invoke dynamic, лямбда НЕ перетворюються назад в анонімних класів під час компіляції (Java не повинні проходити створення об'єктів, просто дбайте про підпис методу, можна прив’язати до методу без створення об'єкта
  • лямбда приділяє більше уваги тому, що ми хочемо зробити, а не тому, що ми маємо зробити, перш ніж ми зможемо це зробити
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.