- •ОГЛАВЛЕНИЕ
- •ВВЕДЕНИЕ
- •ГЛАВА 1. ПРИНЦИПЫ ООП. КЛАССЫ И ОБЪЕКТЫ
- •1.1 Основные принципы ООП
- •1.2 Достоинства ООП
- •1.3 Недостатки ООП
- •1.4 Классы и объекты
- •1.5 Члены класса
- •1.6 Модификаторы объявления класса
- •1.7 Пакеты
- •1.8 Понятие имени
- •1.9 Понятие модуля компиляции
- •1.10 Поля
- •1.11 Управление доступом
- •1.12 Создание объектов
- •1.13 Конструкторы
- •1.14 Блоки инициализации
- •1.15 Статическая инициализация
- •1.16 Методы
- •1.16.1 Модификаторы методов
- •1.16.2 Статические методы
- •1.16.3 Вызов метода
- •1.16.4 Выполнение метода и возврат из него
- •1.16.5 Параметры метода
- •1.16.6 Применение методов для управления доступом
- •1.16.7 Ключевое слово this
- •1.16.8 Перегруженные методы
- •1.16.9 Метод main
- •1.16.10 Методы native
- •ГЛАВА 2. ОСНОВЫ ЛЕКСИКИ
- •2.1 Комментарии
- •2.2 Служебные слова
- •2.3 Идентификаторы
- •2.4 Литералы
- •2.4.1 Целочисленные литералы
- •2.4.2 Литералы с плавающей точкой
- •2.4.3 Логические литералы
- •2.4.4 Символьные литералы
- •2.4.5 Строковые литералы
- •2.5 Операторы
- •2.6 Разделители
- •2.7 Переменные
- •2.8 Простые типы
- •2.8.1 Целочисленные типы
- •byte
- •short
- •long
- •2.8.2 Числовые типы с плавающей точкой
- •float
- •double
- •2.8.3 Приведение типа
- •2.8.4 Автоматическое преобразование типов в выражениях
- •2.8.5 Символьный тип
- •2.8.6 Логический тип
- •2.9 Массивы
- •2.9.1 Многомерные массивы
- •2.9.2 Инициализация массивов
- •2.10 Операторы
- •2.10.1 Арифметические операторы
- •Операторы арифметических действий
- •Оператор деления по модулю
- •Операторы арифметических действий с присваиванием
- •Целочисленная арифметика
- •Арифметика с плавающей запятой
- •2.10.2 Инкремент и декремент
- •2.10.3 Целочисленные битовые операторы
- •Операторы битовой арифметики
- •Оператор побитового сдвига влево
- •Операторы побитового сдвига вправо
- •Операторы битовой арифметики с присваиванием
- •2.10.4 Операторы сравнения
- •2.10.5 Логические операторы
- •2.10.6 Оператор проверки соответствия типа
- •2.10.7 Условный тернарный оператор
- •2.10.8 Приоритеты операторов
- •2.11 Управление выполнением метода
- •2.11.1 Завершение работы метода
- •2.11.2 Ветвление
- •2.11.3 Циклы
- •while
- •do-while
- •2.11.4 Прерывание блоков инструкций
- •2.11.5 Переход на следующий виток цикла
- •2.11.6 Блок переключателей
- •ГЛАВА 3. ИСКЛЮЧЕНИЯ
- •3.1 Общие сведения об исключениях
- •3.2 Инструкция throw
- •3.3 Предложения throws
- •3.4 Предложения throws и переопределение методов
- •3.5 Предложения throws и методы native
- •3.6 Блок try-catch-finally
- •ГЛАВА 4. НАСЛЕДОВАНИЕ
- •4.1 Расширенный класс. Конструкторы расширенных классов
- •4.2 Порядок выполнения конструкторов
- •4.3 Переопределение методов при наследовании
- •4.4 Сокрытие полей
- •4.5 Доступ к унаследованным членам класса
- •4.6 Возможность доступа и переопределение
- •4.7 Сокрытие статических членов
- •4.8 Служебное слово super
- •4.9 Совместимость и преобразование типов
- •4.9.1 Совместимость
- •4.9.2 Явное преобразование типов
- •4.10 Проверка типа
- •4.11 Завершённые методы и классы
- •4.12 Абстрактные методы и классы
- •4.13 Класс Object
- •4.13.1 Метод сравнения объектов
- •4.13.2 Метод вычисления хеш-кода
- •4.13.3 Метод клонирования объектов
- •4.13.4 Метод получения ссылки на описание класса
- •4.13.5 Метод завершения работы объекта
- •4.13.6 Метод получения строкового представления
- •ГЛАВА 5. ИНТЕРФЕЙСЫ
- •5.1 Пример простого интерфейса
- •5.2 Объявление интерфейса
- •5.3 Константы в интерфейсах
- •5.4 Методы в интерфейсах
- •5.5 Модификаторы в объявлениях интерфейсов
- •5.6 Расширение интерфейсов
- •5.7 Наследование и сокрытие констант
- •5.8 Наследование, переопределение и перегрузка методов
- •5.9 Пустые интерфейсы
- •5.10 Абстрактный класс или интерфейс?
- •СПИСОК СОКРАЩЕНИЙ
- •СПИСОК ЛИТЕРАТУРЫ
родительском интерфейсе, становятся, наряду с собственными методами и константами, частью контракта нового интерфейса SerializableRunnable. Наследуемые интерфейсы называют базовыми (superinterfaces) по отношению к новому интерфейсу, который, в свою очередь, является производным (subinterface), или расширенным, интерфейсом относительно базовых.
5.7 Наследование и сокрытие констант
Производный интерфейс наследует все константы, объявленные в базовых интерфейсах. Если в производном интерфейсе объявлена константа с тем же именем, что и унаследованная, то, независимо от их типов, новая константа «скрывает» старую (те же правила справедливы и в отношении унаследованных полей классов).
Ссылка на константу посредством простого имени в контексте производного интерфейса или класса, который реализует этот интерфейс, означает обращение к константе, принадлежащей производному, а не базовому интерфейсу. Константа, унаследованная от базового интерфейса, всё ещё доступна, если при ссылке на неё указывать полное имя, т.е. название интерфейса, сопровождаемое оператором точки и идентификатором самой константы. Точно так же обычно выполняется обращение к статическим членам классов.
Пример 75. Сокрытие констант при расширении интерфейсов interface X {
int VAL = 1;
}
interface Y extends X { int VAL = 2;
int SUM = VAL + X.VAL;
}
Интерфейс Y содержит объявления двух констант – VAL и SUM. Чтобы обратиться к скрытой константе VAL, унаследованной из
168
интерфейса X, необходимо указать её полное имя – X.VAL. Во внешнем коде для ссылки на константы интерфейса Y можно использовать обычную форму, принятую при обращении к статическим членам класса – Y.VAL и Y.SUM. Разумеется, посредством выражения X.VAL вы получите доступикконстантеVAL интерфейса X.
Если интерфейс Y реализуется каким-либо классом, константы интерфейса Y с точки зрения этого класса будут выглядеть как члены класса. Например, в контексте класса, объявление которого выглядит как class Z implements Y {}, вполне допустимо следующее выражение:
System.out.println("Z.VAL=" + Z.VAL + "Z.SUM=" + Z.SUM);
Но при этом отсутствует возможность обращения посредством Z к X.VAL. Однако, если ссылка на объект Z существует, адресовать X.VAL удаётся с помощью оператора преобразования типов:
Z z = new Z();
System.out.println("z.VAL=" + z.VAL + ", ((Y)z).VAL=" + ((Y)z).VAL + ", ((X)z).VAL=" + ((X)z).VAL);
Результат работы кода будет выглядеть так:
z.VAL=2, ((Y)z).VAL=2, ((X)z).VAL=1
Мы вновь наблюдаем ту же картину, что и при использовании статических полей в расширенных классах. Таким образом, не имеет значения, откуда унаследовано статическое поле – из базового класса или базового интерфейса.
Если интерфейс наследует несколько констант с одним и тем же именем, использование этого имени без дополнительных разъяснений чревато недоразумениями и приводит к ошибке компиляции. Продолжим пример, касающийся интерфейсов X и Y, и предположим, что объявлены ещё два интерфейса (пример 76).
169
Пример 76. Проблема конфликта констант при множественном наследовании interface C {
String VAL = "Интерфейс C";
}
interface D extends X, C {}
Что теперь может означать выражение D.VAL – обращение к целочисленной константе X.VAL или строковой константе C.VAL? В подобных случаях мы обязаны явно оговаривать свои намерения – достаточно прямо написать X.VAL или С.VAL.
Класс, который реализует более одного интерфейса, либо расширяет базовый класс и при этом реализует интерфейсы, приводит нас к тем же трудностям, связанным с сокрытием данных и неоднозначностью имён, что и интерфейс, наследующий несколько других интерфейсов. Наличие в классе собственных статических полей обусловливает сокрытие одноимённых унаследованных полей базовых классов или интерфейсов, и обычные ссылки на такие поля будут выглядеть двусмысленно.
5.8 Наследование, переопределение и перегрузка методов
Производный интерфейс наследует все методы базовых интерфейсов. Если метод, объявленный в производном интерфейсе, обладает теми же сигнатурой и типом возвращаемого значения, что
иунаследованный метод, новое объявление переопределяет любое
ивсе аналогичные объявления унаследованных методов. Переопределение в интерфейсах, в отличие от переопределения в классах, не несёт какой-либо семантической нагрузки – интерфейс в результате наследования будет в итоге содержать одно объявление метода, и в любом классе, реализующем интерфейс, может присутствовать только одна реализация этого метода.
Если интерфейс наследует более одного метода с одной и той же сигнатурой, или класс реализует несколько интерфейсов, содержа-
170
щих метод с одинаковой сигнатурой, всё равно можно говорить о том, что существует только один такой метод – а именно тот, конкретный вариант кода которого в конечном итоге представлен в классе, реализующем интерфейс. В данном случае возможен кон-
фликт контрактов, если одноименные методы в различных интерфейсах несут различную семантическую нагрузку. Такая ошибка не может быть выявлена компилятором и относится к разряду серьёзных ошибок проектирования.
Как и при переопределении методов в процессе расширения класса, методу, переопределённому при наследовании интерфейса, не позволяется декларировать больше объявленных исключений, чем предусмотрено в исходном объявлении соответствующего метода. Если наследуются (без переопределения) два или более метода и их объявления различаются только предложением throws, при реализации этого метода должны учитываться все перечисленные в предложениях throws объявленные исключения.
Если в составе интерфейса объявлен метод с тем же именем, но иным списком параметров, нежели в унаследованном интерфейсе, имеет место перегрузка метода. Класс, реализующий интерфейс, должен предоставить конкретные варианты кода для каждой из перегруженных форм метода.
Если объявленный в интерфейсе метод отличается от унаследованного только типом возвращаемого значения, при компиляции будет выдано сообщение об ошибке.
5.9 Пустые интерфейсы
Некоторые интерфейсы не содержат объявлений каких-либо методов, а просто обозначают некоторое свойство или общий признак принадлежности будущих классов к некоторой группе. Примером такого интерфейса – их принято называть пустыми (empty), или
интерфейсами-маркерами (marker interface) – может служить
171