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

Содержание

1 Введение
2 Типичный пример применения
3 Сортировка списка с использованием анонимных классов
4 Примеры использования
5 Ссылки

Введение

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

class Base {
 
void method1() {
  }

 
void method2() {
  }
}

class A { // нормальный класс

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

 
class C {
  }
// внутренний класс

 
void f() {
   
class D {
    }
// локальный внутренний класс
 
}

 
void g() {
   
// анонимный класс
   
Base bref = new Base() {
     
void method1() {
      }
    }
;
 
}
}

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

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

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

Base bref1 = new Base() {
   
void method1() {}
}
;

Base bref2 =
new Base() {
   
void method1() {}
}
;

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

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

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

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

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

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

   
JFrame frame = new JFrame("AnonDemo2");
    frame.addWindowListener
(new WindowAdapter() {
     
public void windowClosing(WindowEvent e) {
       
System.exit(0);
     
}
    })
;

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

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

   
// отобразить фрейм

   
frame.pack();
    frame.setVisible
(true);
 
}
}

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

import java.util.*;

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));

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

   
Collections.sort(list);
    System.out.println
(list);

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

   
Collections.sort(list, new Comparator() {
     
public int compare(Object o1, Object o2) {
       
int a = ((Integer) o1).intValue();
       
int b = ((Integer) o2).intValue();
       
return a < b ? 1 : a == b ? 0 : -1;
     
}
    })
;
    System.out.println
(list);
 
}
}

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

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

class MyComparator implements Comparator {
       
...
   
}

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

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

[-59, 37, 83]
[
83, 37, -59]

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

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

class A {
 
int afield;

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

 
A(int afield) {
   
this.afield = afield;
 
}

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

 
int getValue() {
   
return afield;
 
}
}

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

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

    // вызвать конструктор суперкласса
   
return new A(10) {
     
int bfield = 20;
     
int cfield;

     
{
       
cfield = 30;
     
}

     
int getValue() {
       
return afield + bfield + cfield + dlocal;
     
}
    }
;
 
}

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

    System.out.println
(anonref.getValue());
 
}
}

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

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

new A(10) {...}

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

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

int bfield = 20;

и

{
   
cfield = 30;
}

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

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

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

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

Ссылки

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

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

Теги: java