Зачем использовать ключевое слово final при создании анонимных классов
Перейти к содержимому

Зачем использовать ключевое слово final при создании анонимных классов

  • автор:

 

Anonymous Classes

Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.

This section covers the following topics:

Declaring Anonymous Classes

While local classes are class declarations, anonymous classes are expressions, which means that you define the class in another expression. The following example, HelloWorldAnonymousClasses , uses anonymous classes in the initialization statements of the local variables frenchGreeting and spanishGreeting , but uses a local class for the initialization of the variable englishGreeting :

Syntax of Anonymous Classes

As mentioned previously, an anonymous class is an expression. The syntax of an anonymous class expression is like the invocation of a constructor, except that there is a class definition contained in a block of code.

Consider the instantiation of the frenchGreeting object:

The anonymous class expression consists of the following:

The new operator

The name of an interface to implement or a class to extend. In this example, the anonymous class is implementing the interface HelloWorld .

Parentheses that contain the arguments to a constructor, just like a normal class instance creation expression. Note: When you implement an interface, there is no constructor, so you use an empty pair of parentheses, as in this example.

A body, which is a class declaration body. More specifically, in the body, method declarations are allowed but statements are not.

Because an anonymous class definition is an expression, it must be part of a statement. In this example, the anonymous class expression is part of the statement that instantiates the frenchGreeting object. (This explains why there is a semicolon after the closing brace.)

Accessing Local Variables of the Enclosing Scope, and Declaring and Accessing Members of the Anonymous Class

Like local classes, anonymous classes can capture variables; they have the same access to local variables of the enclosing scope:

An anonymous class has access to the members of its enclosing class.

An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final.

Like a nested class, a declaration of a type (such as a variable) in an anonymous class shadows any other declarations in the enclosing scope that have the same name. See Shadowing for more information.

Anonymous classes also have the same restrictions as local classes with respect to their members:

You cannot declare static initializers or member interfaces in an anonymous class.

An anonymous class can have static members provided that they are constant variables.

Note that you can declare the following in anonymous classes:

Extra methods (even if they do not implement any methods of the supertype)

However, you cannot declare constructors in an anonymous class.

Examples of Anonymous Classes

Anonymous classes are often used in graphical user interface (GUI) applications.

Consider the JavaFX example HelloWorld.java (from the section Hello World, JavaFX Style from Getting Started with JavaFX). This sample creates a frame that contains a Say ‘Hello World’ button. The anonymous class expression is highlighted:

In this example, the method invocation btn.setOnAction specifies what happens when you select the Say ‘Hello World’ button. This method requires an object of type EventHandler<ActionEvent> . The EventHandler<ActionEvent> interface contains only one method, handle. Instead of implementing this method with a new class, the example uses an anonymous class expression. Notice that this expression is the argument passed to the btn.setOnAction method.

Because the EventHandler<ActionEvent> interface contains only one method, you can use a lambda expression instead of an anonymous class expression. See the section Lambda Expressions for more information.

Anonymous classes are ideal for implementing an interface that contains two or more methods. The following JavaFX example is from the section Customization of UI Controls. The highlighted code creates a text field that only accepts numeric values. It redefines the default implementation of the TextField class with an anonymous class by overriding the replaceText and replaceSelection methods inherited from the TextInputControl class.

Final переменная для анонимного класса

Нужно использовать переменную a в анонимном классе, компилятор говорит объявить её как final .

Почему она должна быть final ?

Это ограничение, которое ввели разработчики спецификации: при создании анонимного класса происходит захват объектов по значению, а не по ссылке. В анонимном классе создаются закрытые поля, в которые копируются значения переданных переменных. То есть по сути реализована эмуляция замыканий.

Добавление модификатора final как бы говорит программисту о том, что при использовании такого рода замыканий не будут наблюдаться побочные эффекты (представьте, что вы передали в созданный объект анонимного класса переменную, а затем изменили её «снаружи» — вы в праве ожидать, что это изменение отразится и внутри замыкания, но это не так в силу пресловутой эмуляции; однако изменение внутреннего состояния mutable объектов будет видно и там и там).

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

Почему это не реализовано до сих пор? Для такого рода реализации необходимо внести серьезное изменение в саму архитектуру JVM. Такого рода изменения достаточно сложно внедрить по определенным причинам:

  • потребуется много усилий для внесения изменений не только со стороны Oracle, но и со стороны сторонних разработчиков (дебаггеры, обфускаторы, IDE, компиляторы и т.д.).
  • Внесение такого изменения может привести к утрате обратной совместимости, что отразится на множестве уже существующих приложений.
  • Изменение может отразиться на других языках/платформах, завязанных на среде JVM.

В Java 8 ввели понятие effectively final — это переменные, которые не имеют модификатора final , но могут использоваться в анонимных классах и лямбда-выражениях. Переменная будет effectively final , если после её определения она не изменяется. Соответственно на Java 8 ваш код будет скомпилирован.

Уровень 23. Ответы на вопросы к собеседованию по теме уровня

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

Как правильно создать объект внутреннего класса?

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

Как правильно создать объект вложенного класса?

Синтаксис создания объекта вложенного класса:

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

Статические методы/переменные объявлять во внутреннем классе (не вложенном) нельзя.

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

Назовите три любых внутренних класса?

  1. private static class Holder —вложенный класс HashMap из java.util .
  2. В интерфейсе Map есть interface Entry<K,V> , который опять же в HashMap и реализуется в другом вложенном классе static class Entry<K,V> implements Map.Entry<K,V> .
  3. private static class IntegerCache в классе Integer .

Как внутренние классы решают проблему множественного наследования в Java?

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

Чем отличаются анонимные классы, созданные на основе интерфейса и на основе класса?

  • При объявлении класса будет создан новый класс, производный от указанного класса при использовании в качестве базы другого класса, или реализующий интерфейс при использовании в качестве базы интерфейса.
  • При инициализации будет создан новый объект и на него будет возвращения ссылка: анонимный класс является конкретным.

Таким образом, единственное отличие в анонимных классах, созданных на основе интерфейса и класса, заключается в количестве абстракных методов, которые необходимо реализовать.

Инициализации анонимного класса на основе интерфейса потребует реализации каждого метода, в то время как при инициализации анонимного класса на основе абстрактного/конкретного класса позволит изменить поведение за счет перегрузки методов и потребует реализации абстракных методов.

Анонимные классы

Если вы много программировали на языке Java, вы могли заметить, что можно объявить классы, вложенные в другие классы. В этой статье рассматривается один из особых типов вложенности, называемый «анонимным классом». Для начала взглянем на следующий простой пример:

  static class B <
  > // статический вложенный класс

 

  class C <
  > // внутренний класс

В этом примере есть несколько типов вложенных и внутренних классов. Вложенный класс, не объявленный статическим, называется внутренним классом. В данном коде B является вложенным классом, а C — вложенным и внутренним классом.

Главной целью данной статьи являются анонимные классы. Вы можете понять суть анонимных классов, изучив приведенный выше пример. Основная особенность — анонимный класс не имеет имени. Анонимный класс является подклассом существующего класса (в примере Base) или реализации интерфейса.

Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально. Например, в следующем фрагменте кода объявляются два различных анонимных класса:

Каждый анонимный класс объявляется внутри выражения.

Типичный пример применения

Рассмотрим типичную ситуацию, в которой вы могли бы применить анонимный класс:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class AnonDemo2 <
  public static void main ( String args []) <

    // создать JFrame и добавить к нему перехватчик
    // событий для обработки закрытия окна

    // создать JPanel  и добавить к фрейму

    JPanel panel = new JPanel () ;
    panel.setPreferredSize ( new Dimension ( 300 , 300 )) ;
    frame.getContentPane () .add ( panel ) ;

Этот пример отображает JPanel на экране. К объекту JFrame добавляется перехватчик событий, который завершает приложение при закрытии окна пользователем.

WindowsListener является интерфейсом. Класс реализации должен определить все методы, указанные в интерфейсе. WindowAdapter реализует интерфейс, используя фиктивные методы, например:

public abstract class WindowAdapter implements WindowListener,
    WindowStateListener, WindowFocusListener <
  public void windowOpened ( WindowEvent e ) <
  >

  public void windowClosing ( WindowEvent e ) <
  >

  public void windowClosed ( WindowEvent e ) <
  >

  public void windowIconified ( WindowEvent e ) <
  >

  public void windowDeiconified ( WindowEvent e ) <
  >

  public void windowActivated ( WindowEvent e ) <
  >

  public void windowDeactivated ( WindowEvent e ) <
  >

  public void windowStateChanged ( WindowEvent e ) <
  >

  public void windowGainedFocus ( WindowEvent e ) <
  >

  public void windowLostFocus ( WindowEvent e ) <
  >
>

Демонстрационное приложение AnonDemo2 должно переопределить только один из этих методов, а именно — windowClosing. То есть, приложение наследует класс адаптера и переопределяет один метод. Подкласс используется в приложении только один раз и имеет очень простую логику. Вот почему анонимный класс является хорошим выбором в данном случае. Анонимный класс расширяет класс WindowAdapter, переопределяя один метод. WindowAdapter, в свою очередь, реализует класс WindowListener, используя фиктивные, ничего не выполняющие методы.

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

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

public class AnonDemo3 <
  public static void main ( String args []) <

    // создать ArrayList и добавить в него
    // несколько объектов Integer

    List list = new ArrayList () ;
    list.add ( new Integer ( 37 )) ;
    list.add ( new Integer ( — 59 )) ;
    list.add ( new Integer ( 83 )) ;

    // отсортировать список обычным способом (по возрастанию)

    // отсортировать список по убыванию,
    // используя функцию, реализованную объектом
    // при помощи анонимного класса

Программа выполняет первую сортировку очевидным способом. Для того чтобы выполнить сортировку по убыванию программа должна определить объект функции Comparator.Этот объект реализует логику сравнения для сортировки объектов Integer в убывающем порядке.

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

и реализовать логику сортировки только один раз.

Программа отображает следующую информацию:

Примеры использования

Давайте рассмотрим последний пример, в котором демонстрируется несколько приемов использования анонимных классов:

  // установить значение afield

  // получить значение afield

public class AnonDemo4 <
  static A createAnon () <
    final int dlocal = 40 ;

    // возвратить из метода f() экземпляр
    // анонимного класса, порожденного из A

  public static void main ( String args []) <
    A anonref = createAnon () ;

В этом примере метод createAnon объявляет анонимный класс и возвращает ссылку типа суперкласс (A) на экземпляр анонимного класса. Это означает, что экземпляр анонимного класса может быть использован вне объявляющего его контекста (createAnon). Далее вызывается метод getValue объектной ссылки на анонимный класс.

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

автоматически вызывается конструктор суперкласса.

Инициализация экземпляра анонимного класса происходит обычным путем, то есть

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

Пример AnonDemo4 имеет одну необычную особенность. Переменная dlocal объявляется как финальная. Если убрать ключевое слово final из объявления — компилятор выдаст ошибку. Почему? Потому что есть возможность, и это демонстрирует данная программа, обратиться к анонимному объекту вне контекста, в котором он был объявлен. Если сделать такое обращение, какое значение будет иметь переменная dlocal, учитывая, что это локальная переменная, объявленная в методе createAnon? Это классический вопрос программирования, возникающий при обращении к неверному фрейму стека.

Для решения этой проблемы локальная переменная должна быть финальной, то есть связанной с определенным значением, которое может быть использовано вместо самой переменной (dlocal). То есть, вместо использования «dlocal» используется значение «40».

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

Ссылки

Дополнительная информация об анонимных классах находится в разделе 5.4 «Анонимные внутренние классы» учебника «Язык программирования Java(tm)» Арнольда, Гослинга и Холмса (Arnold, Gosling, Holmes).

Также обратитесь к разделу «Преимущество статических классов-членов над нестатическими» в книге «Эффективное руководство по языку программирования Java» Джошуа Блоха (Joshua Bloch).

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *