Инициализация полей при наследовании классов

Уточним определение инициализации объектов в случае наследования классов.

Все действия по инициализации выполняются этап за этапом в порядке наследования классов, т.е. сначала п.1 для класса Object , потом п.1 для следующего класса и т.д., потом п.2 в том же порядке и т.д.

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

Рассмотрим абстрактный пример.

class A { ... A() {...} ...} class B extends A { ... B() { ... } ...}

где-то

B b = new B();

При этом сначала выполнится конструктор A(), потом конструктор B().

В данном примере класс A имеет конструктор по умолчанию. Но, что делать, если базовый класс, например, не имеет конструктора по умолчанию, или же нужно, чтобы при создании объекта отработал не конструктор по умолчанию, а какой-либо другой конструктор.

В Java эта ситуация предусмотрена. Используя ключевое слово super, можно в конструкторе порожденного класса вызвать нужный конструктор базового класса.

Пример

class X { X(int a) { ... } ...} class Y extends X { Y() { super(0); ... } ...}

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

Пример

class Base { int x = 1; long y; Base(long y) { this.y = y; } Base() { this(0); // вызов конструктора Base(long y) } public long f() { return x*y; }} class Derived extends Base { String name = ""; Derived(String name, long par) { super(par); // вызов конструктора Base(long y) this.name = name; } public long g(int r) { return r+super.f(); // вызов метода f() класса Base } public long f() { x++; return 2*y; }}

Рассмотрим подробно, из каких полей и методов состоит класс Derived. Из класса Base в него вошли поля x и y и метод f(). Плюс в нем определено поле name и методы g(...) и f(), причем метод f() переопределяет одноименный метод класса Base.

В нашем примере, в методе g(...), требуется вызвать не метод f() класса Derived, а метод f() класса Base. При этом применяется ключевое слово super. Без него вызвался бы метод f() класса Derived.

Контрольная задача

Рассмотрим пример использования класса Derived. Требуется определить, что произойдет при выполнении такого фрагмента.

Derived d = new Derived("test", 10);long c = d.g(5);long p = d.f();

В Java нет множественного наследования

В отличие от C++ в Java нет множественного наследования. Т.е. у класса может быть только один базовый класс. Соответственно отношение наследования формирует строгую иерархию классов - иерархию наследования (дерево классов). Это хорошо видно на примере документации.

Откроем документацию по стандартной библиотеке Java. Вверху любой страницы имеется банер с гиперссылками. Кликнем на ссылке "Tree". Мы увидим дерево классов стандартной библиотеки Java.

Практическая работа

Вернемся к примеру Dialog1. Его можно реализовать иначе, если использовать аппарат наследования.

// Dialog2.java// 2-й пример визульного приложения на Java. import java.awt.*;import java.awt.event.*;import javax.swing.*; public class Dialog2 extends JFrame { Dialog2() { super("Первое визуальное приложение"); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { } setSize(300, 200); Container c = getContentPane(); c.add(new JLabel("Hello, привет")); WindowListener wndCloser = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(wndCloser); setVisible(true); } public static void main(String[] args) { new Dialog2(); }}

Этот пример более соответствует стилю Java, чем Dialog1.

Рассмотрим более сложный пример.

// Dialog3.java// Визульное приложения с текстовой областью. import java.awt.*;import java.awt.event.*;import javax.swing.*; public class Dialog3 extends JFrame { JTextArea txt; Dialog3() { super("Визульное приложения с текстовой областью"); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { } setSize(400, 200); Container c = getContentPane(); c.add(new JLabel("Hello, привет"), BorderLayout.NORTH); // 0 txt = new JTextArea(5, 30); // 1 JScrollPane pane = new JScrollPane(txt); // 2 c.add(pane, BorderLayout.CENTER); // 3 WindowListener wndCloser = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(wndCloser); setVisible(true); } public void test() { txt.append("Первая строка\n"); txt.append("Вторая строка\n"); } public static void main(String[] args) { Dialog3 d = new Dialog3(); d.test(); }}

Этот пример требует некоторых пояснений по принципам организации диалога в Java. В отличие от других языков, в частности VB, в Java все визуальные компоненты масштабируемы. Поэтому при их добавлении на окно приложения нельзя указать их координаты и размеры. Вместо этого используется понятие Layout'а ("разместитель", компоновщик). В этом примере задействован BorderLayout (он является Layout'ом по умолчанию для JFrame). Потом мы подробнее познакомимся с этим понятием. В данном случае просто разберем, что обеспечивает BorderLayout и как с ним работать.

Компоновщик BorderLayout разбивает область окна (панели) на следующие части.

Рисунок 1.

Он позволяет добавлять компоненты в любую из этих частей. При добавлении (метод add(...)) нужно указать в какую часть панели мы добавляем компоненту (см.строки 0 и 3).

Класс JTextArea позволяет создать многострочную область ввода/вывода данных. Для того, чтобы эта область была скроллируемой, дополнительно используется JScrollPane, внутрь которого помещается объект txt класса JTextArea.