Java. Полное руководство. 8-е издание. С. Н. Тригуб Перевод с английского и редакция
Скачать 25.04 Mb.
|
class Figure { double diml; double dim2; Figure(double a, double b) { diml = a ; dim2 = b; } double a r e a () Площадь фигуры не определена 1 2 Часть I. Язык Java Глава 8. Наследование 2 1 3 return 0; } > class Rectangle extends Figure { Rectangle(double a , double b) { super(a, b ) ; } // переопределение метода area для четырехугольника double a r e a () В области четырехугольника return diml * dim2; } } class Triangle extends Figure { Triangle(double a, double b) { super(a, b ) ; } // переопределение метода area для прямоугольного треугольника double a r e a () В области треугольника return diml * dim2 / 2; } } class FindAreas { public static void main(String a r g s []) { Figure f = new Figure(10, 10); Rectangle r = new Rectangle(9, 5); Triangle t = new Triangle(10, 8); Figure figref; figref = Площадь равна " + f igref . area О ) ; figref = Площадь равна " + f igref. area О) ; } figref = Площадь равна " + figref.a r e a (Эта программа создает следующий вывод. В области четырехугольника. Площадь равна В области треугольника. Площадь равна Область фигуры не определена. Площадь равна Двойственный механизм наследования и полиморфизма времени выполнения позволяет определить единый интерфейс, используемый несколькими различными, но сходными классами объектов. В данном случае, если объект является производным от класса F ig u re , его площадь можно вычислять, вызывая метод a r e a (). Интерфейс выполнения этой операции остается неизменным, независимо от типа фигуры Часть I. Язык Использование абстрактных классов В ряде ситуаций нужно будет определять суперкласс, который объявляет структуру определенной абстракции без предоставления полной реализации каждого метода. То есть иногда придется создавать суперкласс, определяющий только обобщенную форму, которую будут совместно использовать все его подклассы, добавляя необходимые детали. Такой класс определяет сущность методов, которые должны реализовать подклассы. Например, такая ситуация может возникать, когда суперкласс не в состоянии создать полноценную реализацию метода. Именно такая ситуация имела место в классе F i g u r e в предыдущем примере. Определение метода a r e a () — просто шаблон. Он не будет вычислять и отображать площадь объекта какого-либо типа. Как вы убедитесь в процессе создания собственных библиотек классов, отсутствие полного определения метода в контексте суперкласса — не столь уж редкая ситуация. Эту проблему можно решать двумя способами. Один из них, как было показано в предыдущем примере, — просто вывод предупреждающего сообщения. Хотя этот подходи полезен в определенных ситуациях — например, при отладке, — обычно он не годится. Могут существовать методы, которые должны быть переопределены подклассом, чтобы подкласс имел какой-либо смысл. Рассмотрим класс T r i a n g l e . Он лишен всякого смысла, если метод a r e a () не определен. В этом случае необходим способ убедиться в том, что подкласс действительно переопределяет все необходимые методы. В Java для этого служит абстрактный метод. Потребовать, чтобы определенные методы переопределялись подклассом, можно с использованием указания модификатора типа a b s t r a c t . Иногда такие методы называют относящимися к компетенции подкласса, поскольку в суперклассе для них никакой реализации не предусмотрено. Таким образом, подкласс должен переопределять эти методы — он не может просто использовать версию, определенную в суперклассе. Для объявления абстрактного метода используют следующую общую форму тип Имя { список_параметров ) Как видите, в этой форме тело метода отсутствует. Любой класс, который содержит один или более абстрактных методов, должен быть также объявлен как абстрактный. Для этого достаточно поместить ключевое слово a b s t r a c t перед ключевым словом c l a s s вначале объявления класса. Абстрактный класс не может содержать какие-то объекты. То есть абстрактный класс не может быть непосредственно конкретизирован с помощью оператора new. Такие объекты были бы бесполезны, поскольку абстрактный класс определен не полностью. Нельзя также объявлять абстрактные конструкторы или абстрактные статические методы. Любой подкласс абстрактного класса должен либо реализовать все абстрактные методы суперкласса, либо сам быть объявлен абстрактным. Ниже приведен простой пример класса, содержащего абстрактный метод, и класса, который реализует этот метод Простой пример применения абстракции abstract class А { abstract void callme(); // абстрактные классы все же могут содержать конкретные методы void callmetooO Это конкретный метод Глава 8. Наследование 2 1 5 class В extends А { void callme() Реализация метода callme класса В AbstractDemo { public static void main(String a r g s []) В b = new В (); b .callme() Обратите внимание на то, что в этой программе класс Ане содержит объявлений каких-либо объектов. Как уже было сказано, конкретизация абстрактного класса невозможна. И еще один нюанс класс А реализует конкретный метод c a l l m e t o o (). Это вполне допустимо. Абстрактные классы могут содержать любое необходимое количество конкретных реализаций. Хотя абстрактные классы не могут быть использованы для конкретизации объектов, их можно применять для создания ссылок на объекты, поскольку в Java полиморфизм времени выполнения реализован с использованием ссылок на супер класс. Поэтому должна существовать возможность создания ссылки на абстрактный класс, которая может использоваться для указания на объект подкласса. Применение этого свойства показано в следующем примере. Используя абстрактный класс, можно усовершенствовать созданный ранее класс F i g u r e . Поскольку понятие площади неприменимо к неопределенной двухмерной фигуре, следующая версия программы объявляет метод a r e a () внутри класса F i g u r e как a b s t r a c t . Конечно, это означает, что все классы, производные от класса F i g u r e , должны переопределять метод a r e a (). // Использование абстрактных методов и классов abstract class Figure { double diml; double dim2; Figure(double a, double b) { diml = a ; dim2 = b; } // теперь метод area является абстрактным abstract double a r e a (); } class Rectangle extends Figure { Rectangle(double a, double b) { super(a, b ) ; } // переопределение метода area для четырехугольника double a r e a () В области четырехугольника return diml * dim2; } } class Triangle extends Figure { Triangle(double a, double b) { 2 1 6 Часть I. Язык Java super(a, b ) ; } // переопределение метода area для четырехугольника double a r e a () В области треугольника return diml * dim2 / 2; } } class AbstractAreas { public static void main(String a r g s []) { // Figure f = new Figure(10, 10); // теперь недопустимо Rectangle r = new Rectangle(9, 5); Triangle t = new Triangle(10, 8); Figure figref; // этот оператор допустим никакой объект не создается = г; System.out.println("Площадь равна " + figref.a r e a ()); figref = Площадь равна " + f igref . area О) Как видно из комментария внутри метода m a i n ( ) , объявление объектов типа F i g u r e более недопустимо, поскольку теперь этот класс является абстрактным. И все подклассы класса F i g u r e должны переопределять метод a r e a (). Чтобы убедиться в этом, попытайтесь создать подкласс, который не переопределяет метод a r e a () . Это приведет к ошибке времени компиляции. Хотя создание объекта типа F i g u r e недопустимо, можно создать ссылочную переменную типа F igure. Переменная f i g r e f объявлена как ссылка на тип F i g ure, те. ее можно использовать для ссылки на объект любого класса, производного от класса F igure. Как мы уже поясняли, поиск версий переопределенных методов вовремя выполнения осуществляется за счет ссылки на суперкласс. Использование ключевого слова f i n a l в сочетании с наследованием Существует три способа использования ключевого слова final. Первый способ — его можно применять для создания эквивалента именованной константы. Это применение было описано в предыдущей главе. Остальные два применения относятся к наследованию. Давайте рассмотрим их. Использование ключевого слова f i n a l для предотвращения переопределения Хотя переопределение методов — одно из наиболее мощных средств Java, вне которых случаях его желательно избегать. Чтобы запретить переопределение метода, вначале его объявления необходимо указать ключевое слово f inal. Методы, объявленные как final, переопределяться не могут. Следующий фрагмент кода иллюстрирует это применение ключевого слова final. Глава 8. Наследование 2 1 7 class А { final void meth() Это метод final."); } } class В extends A { void meth() { // ОШИБКА Этот метод не может быть переопределен. System.out.println("Не допускается!"); } } Поскольку метод m eth () объявлен как f i n a l , он не может быть переопределен в классе В. Попытка выполнить это переопределение приведет к ошибке времени компиляции. Иногда методы, объявленные как f i n a l , могут способствовать увеличению производительности программы. Компилятор вправе встраивать вызовы этих методов, поскольку он знает, что они не будут переопределены подклассом. Часто при вызове небольшого финального метода компилятор Java может встраивать код виртуальной машины подпрограммы непосредственно в скомпилированный код вызывающего метода, тем самым снижая значительные накладные расходы системных ресурсов, связанные с вызовом метода. Встраивание финальных методов в вызывающий код — лишь потенциальная возможность. Обычно Java разрешает вызовы методов динамически, вовремя выполнения. Такой подход называют поздним связыванием Однако поскольку финальные методы не могут переопределяться, обращение к такому методу может быть разрешено вовремя компиляции. Этот подход называют ранним связыванием. Использование ключевого слова f i n a l для предотвращения наследования Иногда необходимо предотвратить наследование класса. Для этого вначале объявления класса следует поместить ключевое слово f i n a l . Объявление класса финальным неявно объявляет финальными и все его методы. Как легко догадаться, одновременное объявление класса как a b s t r a c t и f i n a l недопустимо, поскольку абстрактный класс принципиально является незавершенными только его подклассы предоставляют полную реализацию методов. Ниже приведен пример финального класса class А { / / . . . } // Следующий класс недопустим В extends А { // ОШИБКА Класс Ане может иметь подклассы / . . Как видно из комментария, класс Вне может происходить от класса А, поскольку класс А объявлен финальным Класс O b je c t 2 1 8 Часть I. Язык В Java определен один специальный класс — Object. Все остальные классы являются подклассами этого класса. То есть Ob j ect — суперкласс всех остальных классов. Это означает, что ссылочная переменная класса Ob j ect может ссылаться на объект любого другого класса. Кроме того, поскольку массивы реализованы в виде классов, переменная класса Obj ect может ссылаться также на любой массив. Класс Object определяет методы, описанные в табл. 8.1, которые доступны в любом объекте. Таблица 8.1. Методы класса o b je c t Метод Назначение Object c l o n e () b oolean equals(Object object) void finalize () Class> g e t C l a s s () int h a s h C o d e () v oid n o t i f y () void n o t i f y A l l O String t o S t r i n g O void w a i t () void w a i t (1ong миллисекунд миллисекунд наносекунд Создает новый объект, не отличающийся от кло нируемого Определяет, равен ли один объект другому Вызывается перед удалением неиспользуемого объекта Получает класс объекта вовремя выполнения Возвращает хеш-код, связанный с вызывающим объектом Возобновляет выполнение потока, который ожидает вызывающего объекта Возобновляет выполнение всех потоков, которые ожидают вызывающего объекта Возвращает строку, которая описывает объект Ожидает другого потока выполнения Методы getClass ( ) , notify ( ) , notifyAll () и wait () объявлены как fi nal. Остальные методы можно переопределять. Эти методы описаны в других главах книги. Однако обратите внимание на два метода equals () и toString ( Метод equals () сравнивает два объекта. Если объекты равны, он возвращает значение true, если нет — false. Точное определение равенства зависит от типа сравниваемых объектов. Метод toString () возвращает строку, которая содержит описание объекта, по отношению к которому он вызван. Кроме того, этот метод автоматически вызывается при выводе объекта с помощью метода print ln () . Многие классы переопределяют этот метод. Это позволяет им приспосабливать описание специально для создаваемых ими объектных типов. Последний момент обратите внимание на необычный синтаксис в типе возвращаемого значения метода getClassO. Это имеет отношение к обобщениям Java, которые описываются в главе 14. ГЛАВА ** * Ил «додоли *•><* ъщт ** Пакеты и интерфейсы В этой главе рассматриваются две наиболее новаторские концепции языка Java: пакеты и интерфейсы. Пакеты — это контейнеры классов. Они используются для сохранения изоляции пространства имен класса. Например, пакет позволяет создать класс по имени List, который можно хранить в отдельном пакете, небес покоясь о возможных конфликтах с другим классом List, хранящимся в каком-то другом месте. Пакеты хранятся в иерархической структуре и явно импортируются в определения новых классов. В предшествующих главах было описано использование методов для определения интерфейса к данным класса. С помощью ключевого слова interface Java позволяет полностью абстрагировать интерфейс от его реализации. Используя это ключевое слово, можно указать набор методов, которые могут быть реализованы одним или несколькими классами. В действительности сам по себе интерфейс не определяет никакой реализации. Хотя они подобны абстрактным классам, интерфейсы предоставляют дополнительную возможность один класс может реализовать более одного интерфейса. И наоборот, класс может наследоваться только от одного суперкласса (абстрактного или не абстрактного). Пакеты Ранее для всех примеров классов мы использовали имена из одного пространства имен. Это означает, что во избежание конфликта имен для каждого класса нужно было указывать уникальное имя. По истечении некоторого времени при отсутствии какого-либо способа управления пространством имен может возникнуть ситуация, когда выбор удобных описательных имен отдельных классов станет затруднительным. Кроме того, требуется также какой-нибудь способ обеспечения того, чтобы выбранное имя класса было достаточно уникальными не конфликтовало с именами классов, выбранными другими программистами. (Представьте себе небольшую группу программистов, спорящих о том, кто имеет право использовать имя “Foobar” в качестве имени класса. Или вообразите себе все сообщество Интернета, спорящее о том, кто первым назвал класс “Espresso”.) К счастью, язык Java предоставляет механизм разделения пространства имен на более удобные для управления фрагменты. Этим механизмом является пакет, который одновременно используется и как механизм присвоения имени механизм управления видимостью. Внутри пакета можно определить классы, недоступные коду вне этого пакета. Можно также определить члены класса, которые видны только другим членам этого же пакета. Такой механизм позволяет классам располагать полными сведениями друг о друге, ноне предоставлять эти сведения остальному миру Часть I. Язык Определение пакета Создание пакета является простой задачей достаточно включить команду package в качестве первого оператора исходного файла Java. Любые классы, объявленные внутри этого файла, будут принадлежать указанному пакету. Оператор package определяет пространство имен, в котором хранятся классы. Если оператор package отсутствует, имена классов помещаются в используемый по умолчанию пакет без имени. (Именно поэтому до сих пор нам ненужно было беспокоиться об определении пакетов) Хотя для коротких примеров программ пакет, используемый по умолчанию, вполне подходит, он не годится для реальных приложений. В большинстве случаев для кода придется определять пакет. Оператор package имеет следующую общую форму пакет bbЗдесь пакет задает имя пакета. Например, показанный ниже оператор создает пакет My Package. package Для хранения пакетов система Java использует каталоги файловой системы. Например, файлы .class любых классов, объявленных в качестве составной части пакета MyPackage, должны храниться в каталоге MyPackage. Помните, что регистр символов имеет значение, а имя каталога должно в точности совпадать с именем пакета. Один и тот же оператор package может присутствовать в более чем одном файле. Этот оператор просто указывает пакет, к которому принадлежат классы, определенные в данном файле. Он не препятствует тому, чтобы иные классы в других файлах были частью этого же пакета. Большинство пакетов, используемых в реальных программах, распределено по множеству файлов. Язык Java позволяет создавать иерархию пакетов. Для этого применяется точечный оператор. Оператор многоуровневого пакета имеет следующую общую форму пакет [. пакет [. пакетЗ ] |