Подтвердить что ты не робот

Какой вред в использовании анонимного класса?

Вопрос возник при чтении ответа на этот вопрос - Как присоединиться к двум спискам в java. Этот ответ дал решение

List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } };

Читая комментарии, пользователи заявили, что они были злыми и уродливыми и не должны использоваться в производстве.

Я хотел бы знать, какой вред в использовании этого? Почему это уродливо, зло или плохо для использования в производстве?


Примечание: задан вопрос как вопрос, потому что ссылка на запись слишком старая (2008), а ответчик отключен с нескольких месяцев.

4b9b3361

Ответ 1

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

Учитывая примерный фрагмент исходного кода:

public interface Inner {
    void innerAction();
}

public class Outer {

    public void methodInOuter() {}

    private Inner inner = new Inner() {
        public void innerAction() {
            // calling a method outside of scope of this anonymous class
            methodInOuter();  
        }
    }
}

Что происходит во время компиляции, заключается в том, что компилятор создает файл класса для нового анонимного подкласса Inner, который получает так называемое синтетическое поле со ссылкой на экземпляр класса Outer. Сгенерированный байт-код будет примерно эквивалентен примерно так:

public class Outer$1 implements Inner {

    private final Outer outer; // synthetic reference to enclosing instance

    public Outer$1(Outer outer) {
        this.outer = outer;
    }

    public void innerAction() {
        // the method outside of scope is called through the reference to Outer
        outer.methodInOuter();
    }
}

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

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

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

С другой стороны, я бы все же утверждал, что использование DBI в большинстве случаев по-прежнему не является необходимым. Что касается объединения списка, я бы создал простой метод многократного использования, который не только более безопасен, но и более кратким и понятным.

public static <T> List<T> join(List<? extends T> first, List<? extends T> second) {
    List<T> joined = new ArrayList<>();
    joined.addAll(first);
    joined.addAll(second);
    return joined;
}

И тогда код клиента будет просто:

List<String> newList = join(listOne, listTwo);

Дальнейшее чтение: fooobar.com/questions/2224/...

Ответ 2

Комментарии "уродливые" и "не использовать в производстве" относятся к конкретному использованию анонимных классов, а не к анонимным классам в целом.

Это конкретное использование присваивает newList анонимный подкласс ArrayList<String>, совершенно новый класс, созданный с единственной целью - а именно, инициализацию списка с содержимым двух конкретных списков. Это не очень читаемо (даже опытный читатель мог бы потратить несколько секунд на его выяснение), но что более важно, это может быть достигнуто без подкласса при одном и том же количестве операций.

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

Ответ 3

Это конкретное использование анонимных классов имеет несколько проблем:

  • Это малоизвестная идиома. Разработчики, которые этого не знают (или знают, что не используют его много), будут замедлены при чтении и/или изменении кода, который его использует.
  • он фактически неправильно использует языковой признак: вы не пытаетесь определить новый тип ArrayList, вам просто нужен список массивов с некоторыми существующими значениями в нем
  • он создает новый класс, который занимает ресурсы: дисковое пространство для хранения определения класса, время для синтаксического анализа/проверки/... it, permgen для хранения определения класса,...
  • даже если "реальный код" немного длиннее, его можно легко перенести в метод утилиты с меткой имени (joinLists(listOne, listTwo))

По-моему, № 1 является самой важной причиной, чтобы избежать этого, за которым следует №2. # 3 обычно не такая уж большая проблема, но ее нельзя забывать.

Ответ 4

Поскольку вам не нужен отдельный подкласс - вам просто нужно создать новый ArrayList обычного класса и addAll() оба списка в него.

Так же:

public static List<String> addLists (List<String> a, List<String> b) {
    List<String> results = new ArrayList<String>();
    results.addAll( a);
    results.addAll( b); 
    return results;
}

Зло создавать подкласс, который не нужен. Вам не нужно распространять или подклассировать поведение - просто измените значения данных.

Ответ 5

Это не плохой подход как таковой, скажем, в производительности или что-то в этом роде, но подход немного неясен, и когда вы используете что-то подобное, вы всегда (скажем, 99%) должны объяснить этот подход. Я считаю, что одна из главных причин не использовать этот подход и при наборе текста:

List<String> newList = new ArrayList<String>();
newList.addAll(listOne);
newList.addAll(listTwo);

немного больше печатает, его немного легче читать, что очень помогает в понимании или отладке кода.

Ответ 6

В вашем примере это действительно выглядит злым и уродливым, по крайней мере для меня - трудно понять, что происходит в коде. Но есть несколько моделей использования анонимных классов, к которым люди привыкли, потому что они видят их очень часто, например

    Arrays.sort(args, new Comparator<String>() {
        public int compare(String o1, String o2) {
            return  ... 
        }});

Я бы назвал это примером лучшей практики.