Java8 Lambdas vs анонимные классы


111

Поскольку Java8 была недавно выпущена и ее новые лямбда-выражения выглядят действительно круто, мне было интересно, означает ли это кончину анонимных классов, к которым мы так привыкли.

Я немного исследовал это и нашел несколько классных примеров того, как лямбда-выражения будут систематически заменять эти классы, например метод сортировки Collection, который используется для получения анонимного экземпляра Comparator для выполнения сортировки:

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?

РЕДАКТИРОВАТЬ

Тот же вопрос, но в противоположном направлении, каковы преимущества использования Lambdas вместо анонимных классов, поскольку Lambdas можно использовать только с интерфейсами с одним методом, эта новая функция только ярлык используется только в нескольких случаях или действительно полезна?


5
Конечно, для всех тех анонимных классов, которые предоставляют методы с побочными эффектами.
tobias_k

11
Просто для вашей информации вы также можете построить компаратор как:, Comparator.comparing(Person::getFirstName)if getFirstName()будет возвращающим методом firstName.
skiwi

1
Или анонимные классы с несколькими методами, или ...
Марк Роттевил

1
У меня возникает соблазн проголосовать за закрытие как слишком широкое, особенно из-за дополнительных вопросов после EDIT .
Марк Роттевил

Ответы:


108

Анонимный внутренний класс (AIC) может использоваться для создания подкласса абстрактного класса или конкретного класса. AIC также может предоставлять конкретную реализацию интерфейса, включая добавление состояния (полей). На экземпляр AIC можно ссылаться, используя thisего тела методов, поэтому для него могут быть вызваны дополнительные методы, его состояние может изменяться с течением времени и т. Д. Ничего из этого не применимо к лямбдам.

Я предполагаю, что в большинстве случаев AIC используются для реализации отдельных функций без сохранения состояния и поэтому могут быть заменены лямбда-выражениями, но есть и другие варианты использования AIC, для которых лямбда-выражения не могут использоваться. AIC здесь, чтобы остаться.

ОБНОВИТЬ

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


1
Лямбды могут иметь состояние. В этом плане я не вижу разницы между лямбдами и AIC.
nosid

1
@nosid AIC, как и экземпляры любого класса, могут содержать состояние в полях, и это состояние доступно (и потенциально может изменяться) для любого метода класса. Это состояние существует до тех пор, пока объект не будет обработан сборщиком мусора, т. Е. Он имеет неопределенный размер, поэтому он может сохраняться при вызовах методов. Единственное состояние с неопределенной протяженностью лямбда-выражений фиксируется в момент обнаружения лямбда-выражения; это состояние неизменяемо. Локальные переменные в лямбда-выражении изменяемы, но они существуют только во время выполнения лямбда-вызова.
Стюарт Маркс

1
@nosid А, взлом одноэлементного массива. Только не пытайтесь использовать свой счетчик из нескольких потоков. Если вы собираетесь выделить что-то в куче и зафиксировать это в лямбде, вы также можете использовать AIC и добавить поле, которое вы можете изменять напрямую. Такой способ использования лямбды может работать, но зачем беспокоиться, если можно использовать реальный объект?
Стюарт Маркс

2
AIC создаст файл, похожий на этот, A$1.classа Lambda - нет. Могу я добавить это в Разницу?
Асиф Муштак,

2
@UnKnown В основном это проблема реализации; это не влияет на то, как можно программировать с AIC и лямбдами, о чем в основном и идет этот вопрос. Обратите внимание, что лямбда-выражение действительно создает класс с именем вроде LambdaClass$$Lambda$1/1078694789. Однако этот класс генерируется «на лету» метафакторией лямбда-выражения, а не by javac, поэтому соответствующего .classфайла нет. Опять же, это проблема реализации.
Стюарт Маркс

60

Lambdas - отличная функция, но работает только с типами SAM. То есть взаимодействует только с одним абстрактным методом. Он не сработает, если ваш интерфейс будет содержать более одного абстрактного метода. Вот здесь и пригодятся анонимные классы.

Итак, мы не можем просто игнорировать анонимные классы. И, к вашему сведению, ваш sort()метод можно упростить, пропустив объявление типа для p1и p2:

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

Вы также можете использовать здесь ссылку на метод. Либо вы добавляете compareByFirstName()метод в Personкласс, и используете:

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

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

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

4
Я знал, но предпочитаю длинный вариант с точки зрения удобочитаемости, потому что в противном случае было бы сложно узнать, откуда берутся эти переменные.
Амин Абу-Талеб

@ AminAbu-Taleb Почему это сбивает с толку. Это допустимый синтаксис Lambda. Типы в любом случае предполагаются. В любом случае, это личный выбор. Вы можете указать типы явным образом. Без вопросов.
Рохит Джайн

1
Есть еще одно тонкое различие между лямбдами и анонимными классами: анонимные классы можно напрямую аннотировать с помощью новых аннотаций типов Java 8 , например new @MyTypeAnnotation SomeInterface(){};. Это невозможно для лямбда-выражений. Подробнее см. Мой вопрос здесь: Аннотирование функционального интерфейса лямбда-выражения .
Balder

36

Производительность лямбда с анонимными классами

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

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

Лямбда-выражения отличаются при генерации байт-кода, они более эффективны, используются инструкции invokedynamic, которые идут с JDK7.

Для лямбда-выражений эта инструкция используется для задержки преобразования лямбда-выражения в байт-код до времени выполнения. (инструкция будет активирована только в первый раз)

В результате лямбда-выражение станет статическим методом (созданным во время выполнения). (Есть небольшая разница с состояниями и состояниями с полным состоянием, они разрешаются через сгенерированные аргументы метода)


Каждой лямбде также нужен новый класс, но он создается во время выполнения, поэтому в этом смысле лямбда-выражения не более эффективны, чем анонимные классы. Лямбды создаются через, invokedynamicчто обычно медленнее, чем invokespecialпри создании новых экземпляров анонимных классов. Таким образом, в этом смысле лямбды тоже медленнее (однако JVM invokedynamicбольшую часть времени может оптимизировать вызовы).
ЖекаКозлов 02

3
@AndreiTomashpolskiy 1. Будьте вежливы. 2. Прочтите этот комментарий разработчика компилятора: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov 03

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

@AndreiTomashpolskiy I / O не обязательно медленный
ZhekaKozlov 05

14

Различают следующие отличия:

1) Синтаксис

Лямбда-выражения выглядят аккуратно по сравнению с анонимным внутренним классом (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 и this при использовании внутри анонимного внутреннего класса и лямбда-выражения. В случае анонимного внутреннего класса это ключевое слово относится к локальной области видимости, а ключевое слово super относится к суперклассу анонимного класса. В то время как в случае лямбда-выражения это ключевое слово относится к объекту включающего типа, а 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) Производительность

Во время выполнения анонимные внутренние классы требуют загрузки класса, выделения памяти и инициализации объекта и вызова нестатического метода, в то время как лямбда-выражение - это чистая операция времени компиляции и не требует дополнительных затрат во время выполнения. Таким образом, производительность лямбда-выражения лучше по сравнению с анонимными внутренними классами. **

** Я понимаю, что это не совсем так. Пожалуйста, обратитесь к следующему вопросу для получения подробной информации. Лямбда против анонимной производительности внутреннего класса: снижение нагрузки на 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.