Основы объектно-ориентированного программирования
Интерфейс Figure определяет геометрическую фигуру, которая характеризуется периметром и площадью. При этом конкретными реализациями этой абстрактной идеи могут быть "нульугольник" (пустое множество), "одноугольник" (точка), "двуугольник" (отрезок) и многоугольник. Каждый из этих классов имеет свою особую внутреннюю структуру: "одноугольник" содержит координаты его единственной точки, "двуугольник" — двух его концевых точек, а класс Polygon (многоугольник) вообще выведен из класса Deq, что позволяет ему хранить координаты всех его вершин. Каждый из этих классов обеспечивает свои варианты реализации для всех методов, имеющихся в описании интерфейса.
Когда в программе, работающей с объектом типа Figure, вызывается метод area() для этого объекта, то реально произойдет вызов метода именно того класса, к которому принадлежит данный объект. Это гарантируется с помощью специального механизма, известного как динамический поиск метода. Подобное свойство имеет, к сожалению, и негативную сторону — динамический поиск метода медленнее непосредственного вызова. По этой причине там, где это возможно, программист должен дать компилятору право применить непосредственный вызов метода.
Динамический поиск метода никогда не используется в следующих ситуациях:
- для методов, объявленных с ключевым словом static ;
- для методов, объявленных с ключевым словом private ;
- для методов, объявленных с ключевым словом final ;
- для всех методов final -класса.
Поясним смысл этих и некоторых других ключевых слов языка Java, которые были использованы в приведенных выше примерах. Ключевое слово final, примененное к методу, запрещает переопределять его в выведенных классах. Применение его к полю данных превращает эти данные в неизменяемые (константу). Особенно часто для этой цели используется комбинация двух ключевых слов — static final. Будучи примененным к описанию класса, final запрещает использование данного класса в качестве базового.
Квалификаторы доступа private, protected и public определяют доступность данных или метода, реализуя возможность инкапсуляции. Только методы, описанные с использованием ключевого слова public, являются общедоступными. Именно они определяют интерфейс объекта. Все внутренние данные объекта, как правило, описываются с квалификатором private, что запрещает работу с ними извне объекта. Ключевое слово protected обеспечивает работу с так описанными компонентами методам выведенных классов. Во всех рассмотренных выше примерах интерфейс объекта (его public -компоненты и методы) был отделен пустой строкой от компонент, являющихся его внутренней реализацией ( private и protected данные и методы).
Теперь мы в состоянии полностью объяснить программу "Здравствуй, мир!", с которой в самом начале книги началось знакомство с языком Java.
Текст программы
public class Hello { public static void main(String[] args) { System.out.println("Здравствуй, мир!"); } }
Общедоступный ( public ) статический ( static ) метод main класса Hello, с которого начинается выполнение программы, получает в качестве аргумента массив строк args, не возвращая после своего завершения никакого результата (являясь void -методом). Выполнение main сводится к вызову метода println с аргументом "Здравствуй, мир!" для объекта, на который ссылается поле (компонента) out объекта System.
Контейнеры
ООП предоставляет программисту богатейшие возможности по созданию новых типов данных, часть из которых была проиллюстрирована в предыдущей секции. Сейчас мы рассмотрим еще один важный механизм — применение контейнеров.
Определение 10.7. Контейнерные классы (container classes) — классы, которые используются как структуры данных, содержащие набор элементов.
Пакет java.util предоставляет интерфейс Enumeration и классы Vector, Stack, Dictionary, Hashtable и BitSet, предназначенные для решения сформулированной задачи при программировании на языке Java. По целому ряду причин мы не будем использовать классы этого пакета в нашем курсе.
Стандартная библиотека (STL) языка C++ содержит более широкий набор так называемых стандартных контейнеров. Используемые нами в данной секции имена классов и методов будут в значительной мере напоминать имена из этой стандартной библиотеки, более подробное знакомство с которой предусмотрено в рамках последующего курса "Методы хранения и обработки информации".
Определение 10.8. Основными контейнерами являются вектор (vector), перечисление (enumeration), динамический вектор (dynamic vector), стек (stack), очередь (queue), дек (deq), множество (set), одно- и двусвязные списки (lists).
Из приведенного выше перечня выделяются вектор и перечисление — это структуры, не являющиеся динамическими. Последнее означает, что их размер определяется в момент создания и не может быть изменен в дальнейшем.
Использование векторов (так называют массивы) нам уже хорошо знакомо, а пример работы с перечислением приведен ниже. В нем применяется интерфейс Enumeration, очень похожий на тот, что определен в пакете java.util. Этот интерфейс задает новый тип объекта — контейнер, который содержит внутри себя набор других объектов и позволяет извлекать из него эти объекты один за другим (в неизвестном порядке), предварительно выясняя, осталось ли в нем что-либо.
Пример интерфейса
interface Enumeration { // Есть ли еще элементы? public boolean hasMoreElements(); // Взять следующий элемент. public Object nextElement(); } // Реализация перечисления для работы с целыми числами. class Enum implements Enumeration { // Вектор, в котором хранятся числа. private int[] values; // Количество уже извлеченных чисел. private int current; // Конструктор. public Enum(int[] values) { this.values = values; current = 0; } public boolean hasMoreElements() { return current < values.length; } public Object nextElement() { return new Integer(values[current++]); } // Тестовая программа для работы с перечислением. public static void main(String[] args) { int[] val = new int[7]; for (int i=0; i<7; i++) val[i] = 17 + i; Enum e = new Enum(val); while (e.hasMoreElements()) { System.out.println( ((Integer)(e.nextElement())) .intValue() ); } } }
Важным моментом здесь является то, что целые числа в языке Java не являются объектами, а из перечисления Enumeration, как это следует из его интерфейса, должны извлекаться объекты. Справиться с этой проблемой помогает класс Integer, определенный в пакете java.lang, обеспечивающий преобразование целого числа в объект "целое число" и обратно. При извлечении очередного элемента с помощью метода nextElement из целого числа, хранящегося в массиве values, конструируется объект Integer, который и возвращается в качестве результата.
Приведенная выше тестовая программа, иллюстрирующая работу с перечислением, создает контейнер, содержащий 7 целых чисел (начиная с 17), а затем последовательно извлекает их. До тех пор, пока в контейнере еще содержатся элементы, осуществляются следующие действия: извлекается очередной объект, который преобразуется к типу Integer, а затем определяется его значение, которое и печатается.
Пакет java.util содержит класс Vector, реализующий динамический вектор. Он несколько более функционален и универсален, чем тот интерфейс, к рассмотрению которого мы сейчас приступим, однако для первого знакомства с данным контейнерным типом наш вариант предпочтительнее. Динамический вектор отличается от обычного тем, что его размер может произвольным образом изменяться в процессе работы с ним.
Пример интерфейса
interface Vector { // Конструктор. public Vector(); // Построить перечисление из вектора. public Enumeration elements(); // Пуст ли вектор? public boolean isEmpty(); // Сделать вектор пустым. public void removeAllElements(); // Получить значение элемента. public Object elementAt(int index); // Изменить значение элемента. public void setElementAt(Object obj, int index); // Удалить элемент. public void removeElementAt(int index); // Добавить элемент в заданную позицию. public void insertElementAt(Object obj, int index); // Добавить элемент в конец вектора. public void addElement(Object obj); }
Этот интефейс содержит метод elements(), возвращающий контейнер типа Enumeration, что позволяет работать с динамическим вектором, как с перечислением. Назначение остальных методов вполне ясно из комментариев к ним.
Отметим, что элементами этого контейнера могут быть произвольные объекты. В него можно поместить, например, несколько элементов типа Enumeration, несколько строк (тип String ) и векторов (тип Vector ). Единственное, о чем должен заботиться программист при работе с данным контейнером, — это преобразование извлекаемого объекта к его реальному типу.