Как правило, необходимость во вложенных классах возникает только в тех случаях, когда внешний класс служит заменой модуля процедурного языка программирования. В этом случае обычные классы приходится вкладывать во внешний класс, и они становятся вложенными.
Внутренний класс задаётся так же, как вложенный, но только без модификатора static перед именем этого класса:
class ИмяВнешнегоКласса{
тело внешнего класса
class ИмяВнутреннегоКласса{
тело внутреннего класса
}
продолжение тела внешнего класса
}
Для внутренних классов экземпляры создаются через имя объекта внешнего класса, что принципиально отличает их от обычных и вложенных классов.
Синтаксис таков:
Сначала идёт создание экземпляра внешнего класса:
ИмяВнешнегоКласса имяОбъекта = new ИмяВнешнегоКласса(параметры);
Затем создаётся нужное число экземпляров внутреннего класса:
ИмяВнешнегоКласса.ИмяВнутреннегоКласса имя1 =
имяОбъекта.new ИмяВнутреннегоКласса(параметры);
ИмяВнешнегоКласса.ИмяВнутреннегоКласса имя2 =
имяОбъекта.new ИмяВнутреннегоКласса(параметры);
и так далее.
Достаточно часто из внутреннего класса необходимо обратиться к объекту внешнего класса. Такое обращение идёт через имя внешнего класса и ссылку this на текущий объект:
ИмяВнешнегоКласса.this
- это ссылка на внешний объект (его естественно назвать родительским объектом). А доступ к полю или методу внешнего объекта в этом случае, естественно, идёт так:
ИмяВнешнегоКласса.this.имяПоля
ИмяВнешнегоКласса.this.имяМетода(список параметров).
К сожалению, в Java, в отличие от языка JavaScript, нет зарезервированного слова parent для обращения к родительскому объекту. Будем надеяться, что в дальнейшем в java будет введён этот гораздо более читаемый и удобный способ обращения к родителю.
Пример работы с внутренними классами:
package java_gui_example;
public class OuterClass {
int a=5;
public OuterClass() {
}
public class InnerClass{
int x=1,y=1;
public class InnerClass2 {
int z=0;
InnerClass2(){
System.out.println("InnerClass2 object created");
};
void printParentClassNames(){
System.out.println("InnerClass.this.x="+InnerClass.this.x);
System.out.println("OuterClass.this.a="+OuterClass.this.a);
}
}
}
InnerClass inner1;
InnerClass.InnerClass2 inner2;
public void createInner() {
inner1=this.new InnerClass();
inner2=inner1.new InnerClass2();
System.out.println("inner1 name="+inner1.getClass().getName());
System.out.println("inner1 canonical name="+
inner1.getClass().getCanonicalName());
}
}
Если в приложении задать переменную типа OuterClass и создать соответствующий объект
OuterClass outer1=new OuterClass();
то после этого можно создать объекты внутренних классов:
outer1.createInner();
Доступ к внешним объектам иллюстрируется при вызове метода
outer1.inner2.printParentClassNames();
Заметим, что при создании внутреннего класса в приложении, а не в реализации класса OuterClass, вместо
InnerClass inner1=this.new InnerClass();
и
InnerClass.InnerClass2 inner2= inner1.new InnerClass2();
придётся написать
OuterClass.InnerClass inner3=outer1.new InnerClass();
OuterClass.InnerClass.InnerClass2 inner4=inner3.new InnerClass2();
Необходимость во внутренних классах обычно возникает в случаях, когда внешний класс описывает сложную систему, состоящую из частей, каждая из которых, в свою очередь, является системой, очень тесно связанной с внешней. Причём может существовать несколько экземпляров внешних систем. Для такого варианта агрегации идеология внутренних классов подходит очень хорошо.
Никаких особенностей в применении локальных классов нет, за исключением того, что область существования их и их экземпляров ограничена тем блоком, в котором они заданы. Пример использования локального класса:
class LocalClass1 {
public LocalClass1(){
System.out.println("LocalClass1 object created");
}
};
LocalClass1 local1=new LocalClass1();
Этот код можно вставить в любой метод. Например, в обработчик события нажатия на кнопку. Конечно, данный пример чисто иллюстративный.
Анонимные (anonimous) классы и обработчики событий
Анонимный (безымянный) класс объявляется без задания имени класса и переменных данного безымянного типа – задаётся только конструктор класса вместе с его реализацией. У анонимного класса может быть только один экземпляр, причём он создаётся сразу при объявлении класса. Поэтому перед объявлением анонимного класса следует ставить оператор new. Анонимный класс должен быть наследником какого-либо класса или интерфейса, и соответствующий тип должен быть указан перед списком параметров конструктора.
Синтаксис задания анонимного класса таков:
new ИмяПрародителя(список параметров конструктора) {
тело конструктора
}
Как уже говорилось, анонимные классы обычно используют в обработчиках событий, причём сама необходимость в таких классах, по мнению автора, вызвана неудачной организацией в Java работы с обработчиками событий.
Пример использования анонимного класса в “слушателе” события (о них речь пойдёт в следующем параграфе):
addMouseMotionListener(
new java.awt.event.MouseMotionAdapter(){
public void mouseDragged(java.awt.event.MouseEvent e){
System.out.println("Mouse dragged at: x="+
e.getX()+" y="+e.getY()
);
}
}
);
Анонимные (anonimous) классы и слушатели событий (listeners)
Событие в Java (будем называть его программным событием, или, сокращённо, просто событием) – это объект, возникающий при наступлении какого-либо события в реальном мире при взаимодействии с ним компьютера (будем называть его физическим событием). Например, физическим событием может быть нажатие на клавишу клавиатуры. При наступлении некоторых физических событий возникают программные события – создаются объекты, имеющие тип, зависящий от того, какое событие наступило. Обработчики событий – подпрограммы, которые выполняют некоторый код при наступлении программного события. Например, код, который будет выполнен при нажатии пользователем на кнопку jButton1 во время работы приложения.
В Java к каждому объекту, поддерживающему работу с неким событием, могут добавляться слушатели (listeners) событий этого типа – объекты-обработчики событий. Они являются экземплярами специальных классов Listeners, в которых заданы методы, реагирующие на соответствующие типы событий.
Классы и интерфейсы для работы с событиями заданы в пакетах java.awt, java.awt.event и javax.swing.event.
Важнейшие типы событий:
В пакете java.awt:
java.awt.AWTEvent – абстрактный класс, прародительский для всех классов событий.
В пакете java.awt.event:
ActionEvent – событие действия (как правило, нажатие).
AdjustmentEvent – изменение значения в линии прокрутки (для компонентов с линией прокрутки).
ComponentEvent – компонент переместился, изменил размер или видимость (visibility) -показался или был скрыт.
ContainerEvent – содержимое компонента-контейнера изменилось – какой-либо компонент был в него добавлен или из него убран.
FocusEvent – компонент получил или потерял фокус.
HierarchyEvent – изменение положения компонента в физической иерархии (иерархии агрегации). Например, удаление родительского компонента, смена компонентом родителя (перетаскивание с одного компонента на другой), и т.п.
InputEvent – произошло событие ввода. Базовый класс для классов событий ввода (KeyEvent, MouseEvent)
InputMethodEvent – произошло событие ввода. Содержит информацию об обрабатываемом тексте.
ItemEvent – событие, возникающее в случае, если пункт (item) был отмечен (selected) или с него была снята отметка (deselected).
KeyEvent – событие нажатия на клавишу.
MouseEvent – событие мыши.
PaintEvent – событие отрисовки. Служит для управления очередью событий и не может быть использовано для управления отрисовкой вместо методов paint или update.
TextEvent - событие, возникающее в случае, если текст в текстовом компоненте изменился.
WindowEvent – окно изменило статус (открылось, закрылось, максимизировалось, минимизировалось, получило фокус, потеряло фокус).
Также имеется большое количество событий в пакете javax.swing.event.
Для того, чтобы программа могла обработать событие какого-то типа, в приложение требуется добавить объект event listener (“слушатель события”) соответствующего типа. Этот тип - класс, который должен реализовать интерфейс слушателя, являющийся наследником интерфейса java.util.EventListener. Имя интерфейса слушателя обычно складывается из имени события и слова Listener.
Чтобы упростить реализацию интерфейсов, в Java для многих интерфейсов событий существуют так называемые адаптеры (adapters) – классы, в которых все необходимые методы интерфейсов слушателей уже реализованы в виде ничего не делающих заглушек. Так что в наследнике адаптера требуется только переопределение необходимых методов, не заботясь о реализации всех остальных. Перечислим важнейшие интерфейсы и адаптеры слушателей:
ActionEvent – ActionListener.
AdjustmentEvent – AdjustmentListener.
ComponentEvent – ComponentListener - ComponentAdapter.
ContainerEvent – ContainerListener - ContainerAdapter.
FocusEvent – FocusListener - FocusAdapter.
HierarchyEvent – HierarchyBoundsListener - HierarchyBoundsAdapter.
InputEvent – нет интерфейсов и адаптеров.
InputMethodEvent – InputMethodListener.
ItemEvent – ItemListener.
KeyEvent – KeyListener - KeyAdapter.
MouseEvent - MouseListener - MouseAdapter.
- MouseMotionListener - MouseMotionAdapter. По-английски motion – “движение”. Событие возникает при движении мыши.
- MouseWheelListener-MouseWheelAdapter. По-английски wheel – “колесо”. Событие возникает при прокручивании колёсика мыши.
PaintEvent – нет интерфейсов и адаптеров.
TextEvent – TextListener.
WindowEvent - WindowListener - WindowAdapter.
- WindowFocusListener. Событие возникает при получении или потере окном фокуса.
- WindowStateListener. Событие возникает при изменении состояния окна.
Все компоненты Swing являются потомками javax.swing.JComponent. А в этом классе заданы методы добавления к компоненту многих из упомянутых слушателей:
addComponentListener, addFocusListener и т.д. В классах компонентов, обладающих специфическими событиями, заданы методы добавления слушателей этих событий.
Повторим теперь код, приведённый в предыдущем параграфе, с разъяснениями:
addMouseMotionListener(
new java.awt.event.MouseMotionAdapter(){
public void mouseDragged(java.awt.event.MouseEvent e){
System.out.println("Mouse dragged at: x="+