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

Является ли циклическая зависимость между анонимным классом и родительским классом неправильно?

У меня есть следующий фрагмент кода:

public class Example {

private Integer threshold;

private Map<String, Progress> history;

protected void activate(ComponentContext ctx) {
    this.history = Collections.synchronizedMap(new LinkedHashMap<String, Progress>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Progress> entry) {
            return size() > threshold;
        }
    });
  }
}

Theres - циклическая зависимость между анонимным классом LinkedHashMap и классом Example. Это нормально или нет? Почему нет? Это будет хорошо восстановлено сборщиком мусора?

4b9b3361

Ответ 1

Это нормально или нет?

Это совершенно нормально.

threshold - это поле, поэтому его можно ссылаться из анонимного класса без каких-либо проблем. (Если бы threshold была локальной переменной, это должно было быть (эффективно) окончательным.)

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

Будет ли он хорошо восстановлен сборщиком мусора?

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

В этом случае это означает, что если вы утечка ссылок на карту history, экземпляры Example не будут GC'ed.


Связанные примечания:

  • Учитывая, что вы используете synchronizedMap, похоже, что вы работаете над многопоточной программой. Если это так, вам нужно быть осторожным в вопросах синхронизации и видимости для поля threshold.

  • Если возможно, попробуйте сделать поле threshold окончательным

  • Другой вариант - создать именованный класс для LinkedHashMap и включить threshold в качестве поля в этом классе.

Ответ 2

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

Относительно (отсутствия) "запаха дизайна", если этот объект анонимного класса полностью инкапсулирован в класс Example, не имеет отличительного смысла без его охватывающего контекста и не просачивается где-либо вне класса Example нет ничего плохого в ссылках полей охватывающего класса. Вы просто используете этот внутренний класс для группировки некоторой логики.

Если, однако, этот объект выходит из закрывающего объекта (например, вы возвращаете его через getter), вы должны либо запретить это, либо реорганизовать его в статический внутренний класс, который получает threshold в качестве параметра. Этот внутренний объект содержит ссылку на охватывающий объект и может удерживать его от GC, что вызывает утечку памяти.

Ответ 3

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

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

Если вы, тем не менее, или кто-то другой модифицирует ваш код, чтобы открыть ваш личный:

private Map<String, Progress> history;

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

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

Еще одна проблема, о которой я могу думать, на мой взгляд, циклическая зависимость довольно сложна для unit test, поскольку вы создаете очень высокий уровень связи между объектами.

В общем, вам следует избегать циклической зависимости, если у вас нет оснований для этого, например, для реализации кругового связанного списка.

Ответ 4

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

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

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

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

Ответ 5

Мне не нравится ваше решение (даже если я согласен, что это может сработать):

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

  • ваш класс Пример не должен реализовывать карту или расширять LinkedHashMap, потому что метод активации не уточняет LinkedHashMap или Map, а использует понятия Maps.

1 + 2 = > проблема зачатия.