Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Классы-адаптеры и внутренние классы как альтернатива множественному наследованию.
Большинство интерфейсов имеет больше, чем один метод. Даже если необходимо использовать только один метод, он все равно должен определить (пустыми) все остальные. Для сокращения кода используются адаптеры и внутренние классы. Классы-адаптеры удобны для интерфейсов, имеющих более одного метода, поскольку определение всех функций для интерфейсов не относится к классам. Как известно, в Java запрещено множественное наследование от классов, в отличие от интерфейсов. Ниже приведен фрагмент кода с внутренним классом:
public class MyClass extends Applet { ... // Конструктор MyClass() { addMouseListener(new MyAdapter()); } ... // Внутренний класс, расширяющий адаптер. class MyAdapter extends MouseAdapter { public void mouseClicked(MouseEvent e) { ...//Код обработки события. } } }
Можно еще больше сократить код, если использовать анонимное (без имени) определение внутреннего класса следующим образом. public class MyClass extends Applet { ... // Конструктор MyClass() { addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { ...//Код обработки события } }); } ... }
Наследование через понижающее и повышающее преобразования типов
Наследования классов предусматривает возможности преобразования типов между суперклассом и подклассом. Различаются два вида преобразований типов — upcasting и downcasting. Повышающее преобразование (upcasting) — это преобразование от типа порожденного класса (от подкласса) к базовому (суперклассу). Такое преобразование допустимо всегда. На него нет никаких ограничений и для его проведения не требуется применять никаких дополнительных синтаксических конструкций. Это связано с тем, что объект подкласса всегда в себе содержит как свою часть объект суперкласса. Понижающее преобразование (downcasting) — это преобразование от суперкласса к подклассу. Такое преобразование имеет ряд ограничений. Во-первых, оно может задаваться только явно при помощи операции преобразования типов, например,
B b1 = (B) a;
Во вторых, при преобразовании должен использоваться соответствующий тип. Если это не так, то возникает исключение ClassCastException в процессе выполнения программы. При понижающем преобразовании часто применяется операция instanceof. Эта операция проверяет, имеет ли ее левый операнд класс, заданный правым операндом. Например, if ( a instanceof B ) b1 = (B)a; Пример, сочетающий в себе преобразования типов и instanceof:
Рассмотрим иерархию классов Issue — печатное издание, Book — книга, Newspaper - газета, Journal — журнал, где Book, Newspaper и Journal являются наследниками класса Issue. public class Issue { String name; public Issue(String name) { this.name = name; } public void printName(PrintStream out) { out.println(" Наименование: " ); out.println(name); } ... }
public class Book extends Issue { String authors; public Book(String name, String authors) { super(name); this.authors = authors; }
public void printAuthors(PrintStream out) { out.println(" Авторы: " ); out.println(authors); } ... } А где-то в программе присутствует такой фрагмент: Issue[] catalog = new Issue[] { new Journal(" Play Boy" ), new Newspaper(" Спид Инфо" ), new Book(" Война и мир", " Л.Толстой" ) }; ... for(int i = 0; i < catalog.length; i++) { if ( catalog[i] instanceof Book ) ((Book) catalog[i]).printAuthors(System.out); catalog[i].printName(System.out); }
Порождается каталог (массив печатных изданий), причем каждое из печатных изданий каталога может быть как книгой, так и газетой или журналом. При построении массива выполняется приведение к базовому типу (upcasting). Далее в цикле мы печатаем информацию из каталога. Причем, для книг кроме наименования печатается еще и список авторов. Для этого с использованием операции instanceof проверяется тип печатного издания, а при самой печати списка авторов элемент каталога преобразуется к типу Book. Если этого не сделать, транслятор выдаст ошибку, т.к. метод printAuthors(...) есть только в классе Book, но не в классе Issue.
Запрет наследования или переопределения с помощью ключевого слова final Если нужно запретить переопределение (overriding) метода во всех порожденных классах, то этот метод можно описать как final. Кроме того, ключевое слово final может применяться к классам. Это означает, что данный класс не может быть унаследован другим классом.
БИЛЕТ №8 7. Понятие инкапсуляции. Использование инкапсуляции в Java. Инкапсуляция- это механизм, который связывает код вместе с обрабатываемыми им данными и сохраняет их в безопасности как от внешнего влияния, так от ошибочного использования. Можно представить инкапсуляция как " защитную оболочку", которая предохраняет код и данные от произвольного доступа из других кодов, опреедленных вне этой оболочки. Доступ к коду и данным внутри этой оболочки строго контролируется через хорошо определенный интерфейс.Основой инкапсуляции в языке java явл. класс. Класс определяет стрруктуру и поведения(данные и код) некоторого набора объектов. Каждый объект заданного класса содержит как структуру(данные), так и поведение, определяемые классом (как если бы они были проштампованы некторым шаблонам в форме класса). По этой причине об объекте иногда говорят как об экземпляре класса. Таким образом, класс- это логическая конструкция, а объект- это физическая реальность. При составлении класса код и данные необходимо специфицировать. Все эти элементы называют членами класса. Члены-переменные или переменные экземпляра являются данными. Члены методы или просто методы – код, оперирующий этими данными. Цель класса – инкапсуляция сложности. Для этого у методов и переменных внутри класса могут быть модификаторы доступа (public, private). Инкапсуляция означает, что данные объекта недоступны его клиентам непосредственно. Вместо этого они инкапсулируются — скрываются от прямого доступа извне. Инкапсуляция предохраняет данные объекта от нежелательного доступа, позволяя объекту самому управлять доступом к своим данным.
Модификаторы доступа Модификаторы доступа можно рассматривать как с позиции инкапсуляции так и наследования. Если рассматривать с позиции инкапсуляции, то модификаторы доступа позволяют ограничить нежелательный доступ к членам класса извне. Открытые члены класса составляют внешнюю функциональность, которая доступна другим классам. Закрытыми (private) обычно объявляются независимые от внешнего функционала члены, а также вспомогательные методы, которые являются лишь деталями реализации и неуниверсальны по своей сути. Благодаря сокрытию реализации класса можно менять внутреннюю логику отдельного класса, не меняя код остальных компонентов системы. Очень часто программистами используется доступ к свойствам класса только через его методы (принцип bean классов), который позволяет валидировать значения полей, так как прямое обращение к свойствам отслеживать крайне сложно, а значит им могут присваиваться некорректные значения на этапе выполнения программы. Такой принцип относится к управлению инкапсулированными данными и позволяет быстро изменить способ хранения данных. Если данные станут храниться не в памяти, а в файлах или базе данных, то потребуется изменить лишь ряд методов одного класса, а не вводить эту функциональность во все части системы. Программный код, написанный с использованием этого принципа легче отлаживать. Для того чтобы узнать, в какой момент времени и кто изменил свойство интересующего нас объекта, достаточно добавить вывод отладочной информации в тот метод объекта, посредством которого осуществляется доступ к свойству этого объекта. При использовании прямого доступа к свойствам объектов программисту бы пришлось добавлять вывод отладочной информации во все участки кода, где используется интересующий нас объект. БИЛЕТ №9 9.Использование абстрактных классов В ряде ситуаций нужно будет определять суперкласс, который объявляет структуру определенной абстракции без предоставления полной реализации каждого метода. То есть иногда придется создавать суперкласс, определяющий только обобщенную форму, которую будут совместно использовать все его подклассы, добавляя необходимые детали. Такой класс определяет сущность методов, которые должны реализовать подклассы. Например, такая ситуация может возникать, когда суперкласс не в состоянии создать полноценную реализацию метода. Именно такая ситуация имела место в классе Figure в предыдущем примере. Определение метода area () — просто шаблон. Он не будет вычислять и отображать площадь объекта какого-либо типа. Как вы убедитесь в процессе создания собственных библиотек классов, отсутствие полного определения метода в контексте суперкласса — не столь уж редкая ситуация. Эту проблему можно решать двумя способами. Один из них, как было показано в предыдущем примере, — просто вывод предупреждающего сообщения. Хотя этот подход и полезен в определенных ситуациях — например, при отладке, — обычно он не годится. Могут существовать методы, которые должны быть переопределены подклассом, чтобы подкласс имел какой-либо смысл. Рассмотрим класс Triangle. Он лишен всякого смысла, если метод area () не определен. В этом случае необходим способ убедиться в том, что подкласс действительно переопределяет все необходимые методы. В Java для этого служит абстрактный метод. Потребовать, чтобы определенные методы переопределялись подклассом, можно с использованием указания модификатора типа abstract. Иногда такие методы называют относящимися к компетенции подкласса, поскольку в суперклассе для них никакой реализации не предусмотрено. Таким образом, подкласс должен переопределять эти методы — он не может просто использовать версию, определенную в суперклассе. Для объявления абстрактного метода используют следующую общую форму. abstract тип 'Имя { список_параметров); Как видите, в этой форме тело метода отсутствует. Любой класс, который содержит один или более абстрактных методов, должен быть также объявлен как абстрактный. Для этого достаточно поместить ключевое слово abstract перед ключевым словом class в начале объявления класса. Абстрактный класс не может содержать какие-то объекты. То есть абстрактный класс не может быть непосредственно конкретизирован с помощью оператора new. Такие объекты были бы бесполезны, поскольку абстрактный класс определен не полностью. Нельзя также объявлять абстрактные конструкторы или абстрактные статические методы. Любой подкласс абстрактного класса должен либо реализовать все абстрактные методы суперкласса, либо сам быть объявлен абстрактным. Ниже приведен простой пример класса, содержащего абстрактный метод, и класса, который реализует этот метод. // Простой пример применения абстракции, abstract class А {
abstract void callmeO; // абстрактные классы все же могут содержать конкретные методы void callmetooO { System.out.println(" Это конкретный метод." ); } } class В extends А { void callmeO { System.out.printIn(" Реализация метода callme класса В." ); } } class AbstractDemo { public static void main(String args[]) { В b = new В(); b.callme(); b.callmetoo(); } } Обратите внимание на то, что в этой программе класс А не содержит объявлений каких-либо объектов. Как уже было сказано, конкретизация абстрактного класса невозможна. И еще один нюанс: класс А реализует конкретный метод callmetoo (). Это вполне допустимо. Абстрактные классы могут содержать любое необходимое количество конкретных реализаций. Хотя абстрактные классы не могут быть использованы для конкретизации объектов, их можно применять для создания ссылок на объекты, поскольку в Java полиморфизм времени выполнения реализован с использованием ссылок на суперкласс. Поэтому должна существовать возможность создания ссылки на абстрактный класс, которая может использоваться для указания на объект подкласса. Применение этого свойства показано в следующем примере. Используя абстрактный класс, можно усовершенствовать созданный ранее класс Figure. Поскольку понятие площади неприменимо к неопределенной двухмерной фигуре, следующая версия программы объявляет метод area () внутри класса Figure как abstract. Конечно, это означает, что все классы, производные от класса Figure, должны переопределять метод area (). // Использование абстрактных методов и классов, abstract class Figure { double diml; double dim2; Figure(double a, double b) { diml = a; dim2 = b; } // теперь метод area является абстрактным abstract double area(); } class Rectangle extends Figure { Rectangle(double a, double b) { super(a, b);
} // переопределение метода area для четырехугольника double area() { System.out.println(" В области четырехугольника." ); return diml * dim2; } } class Triangle extends Figure { Triangle(double a, double b) { super(a, b); } // переопределение метода area для четырехугольника double area() { System.out.println(" В области треугольника." ); return diml * dim2 / 2; } } class AbstractAreas { public static void main(String args[]) { // Figure f = new Figure(10, 10); // теперь недопустимо Rectangle r = new Rectangle(9, 5); Triangle t = new Triangle(10, 8); Figure figref; // этот оператор допустим, // никакой объект не создается figref = г; System.out.println(" Площадь равна " + figref.area()); figref = t; System.out.println(" Площадь равна " + figref.area()); } } Как видно из комментария внутри метода main (), объявление объектов типа Figure более недопустимо, поскольку теперь этот класс является абстрактным. И все подклассы класса Figure должны переопределять метод area (). Чтобы убедиться в этом, попытайтесь создать подкласс, который не переопределяет метод area (). Это приведет к ошибке времени компиляции. Хотя создание объекта типа Figure недопустимо, можно создать ссылочную переменную типа Figure. Переменная figref объявлена как ссылка на тип Figure, т.е. ее можно использовать для ссылки на объект любого класса, производного от класса Figure. Как мы уже поясняли, поиск версий переопределенных методов во время выполнения осуществляется за счет ссылки на суперкласс. Использование ключевого слова final в сочетании с наследованием Существует три способа использования ключевого слова final. Первый способ — его можно применять для создания эквивалента именованной константы. Это применение было описано в предыдущей главе. Остальные два применения относятся к наследованию. Давайте рассмотрим их. Использование ключевого слова final для предотвращения переопределения Хотя переопределение методов — одно из наиболее мощных средств Java, в некоторых случаях его желательно избегать. Чтобы запретить переопределение метода, в начале его объявления необходимо указать ключевое слово f inal. Методы, объявленные как final, переопределяться не могут. Следующий фрагмент кода иллюстрирует это применение ключевого слова final. Полиморфизм Полиморфизм (от греч. “много форм”) — свойство, которое позволяет использовать один и тот же интерфейс для общего класса действий. Конкретное действие определяется конкретным характером ситуации. Рассмотрим стек (представляющий собой список типа “последним вошел, первым вышел”). Предположим, программе требуются стеки трех типов: для целочисленных значений, для значений с плавающей точкой и для символов. Алгоритм реализации каждого из этих стеков остается неизменным, несмотря на различие хранящихся в них данных. В не объектно-ориентированном языке пришлось бы создавать три различных набора подпрограмм стека, каждый из которых должен был бы иметь отдельное имя. Однако в Java, благодаря полиморфизму, можно определить общий набор подпрограмм стека, использующих одни и те же имена. В более общем виде концепцию полиморфизма часто выражают фразой “один интерфейс, несколько методов”. Это означает, что можно спроектировать общий интерфейс для группы связанных между собой действий. Такой подход позволяет уменьшить сложность программы, поскольку один и тот же интерфейс используется для указания общего класса действий. Выбор же конкретного действия (т.е. метода), применимого к каждой ситуации, — задача компилятора. Программисту не нужно осуществлять этот выбор вручную. Необходимо лишь помнить об общем интерфейсе и применять его. Если продолжить аналогию с собаками, можно сказать, что собачье обоняние — полиморфное свойство. Если собака ощутит запах кошки, она залает и погонится за ней. Если собака ощутит запах своего корма, у нее начнется слюноотделение, и она поспешит к своей миске. В обеих ситуациях действует одно и то же чувство обоняния. Различие в том, что издает запах, т.е. в типе данных, воздействующих на нос собаки! Эту же общую концепцию можно реализовать в Java применительно к методам внутри программы. БИЛЕТ №10 class Base { void method2() { class A { // нормальный класс static class B { class C { void f() { void g() { В этом примере есть несколько типов вложенных и внутренних классов. Вложенный класс, не объявленный статическим, называется внутренним классом. В данном коде B является вложенным классом, а C - вложенным и внутренним классом. Главной целью данной статьи являются анонимные классы. Вы можете понять суть анонимных классов, изучив приведенный выше пример. Основная особенность - анонимный класс не имеет имени. Анонимный класс является подклассом существующего класса (в примере Base) или реализации интерфейса. Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально. Например, в следующем фрагменте кода объявляются два различных анонимных класса: Base bref1 = new Base() { Base bref2 = new Base() { Каждый анонимный класс объявляется внутри выражения. Интерфейсы Ключевое слово interface осуществляет, на шаг дальше, концепцию, реализованную в abstract. Вы можете думать, что это просто чисто abstract класс. Он позволяет создателю заложить форму (структуру) класса: имена методов, списки аргументов, возвращаемые типы, но только не тела методов. Interface также может содержать поля, но все они будут, хотя и косвенно static и final. Interface предоставляет только форму, образ, но не предоставляет его реализацию. Interface " говорит": " Все классы, реализующие этот особый интерфейс будут выглядеть одинаково". Поэтому, любой код, использующий interface знает, какой из методов может быть вызван для этого interface, впрочем, это все. Так что interface используется в качестве установления " протокола" между классами. (Некоторые ООЯ имеют даже встроенное ключевое слово protocol, делающее то же самое действие.) Что бы создать interface, используйте ключевое слово interface вместо ключевого слова class. Как и у класса, Вы можете добавить ключевое слово public до interface (но только если этот интерфейс определен в файле с тем же именем) или оставить его пустым, тогда он станет " friendly" и его можно будет использовать только членам одного с ним пакета. Для создания класса согласованного с особенным interface (или группой interface-ов) используйте ключевое слово implements. Тем самым Вы объявляете " Interface это на что похож мой класс, а теперь я скажу, как он должен работать." Все остальное, кроме этого, выглядит, как наследование. Диаграмма для примера с инструментами: Как только Вы примените interface, то этот класс сразу же становится обычным и в последствии он может быть расширен обычным способом. Вы можете выбрать явно объявления методов в interface как public. Но они таковыми являются, даже если Вы этого и не объявляете. Так что, когда Вы реализуете interface, методы из него должны быть определены как public. В противном случае, они будут по умолчанию friendly и Вы будете ограничены в доступе к ним во время наследования, поскольку доступ будет запрещен компилятором. Это Вы можете увидеть в измененном примере Instrument. Заметьте, что каждый метод в interface строго определен, только так компилятор и позволяет делать. В дополнение, ни один из методов в Instrument не определен как public, но они автоматически public по любому: //: c08: music5: Music5.java// Интерфейсы.import java.util.*; interface Instrument { // Константа времени компиляции: int i = 5; // static & final // Не могут быть получены определения методов: void play(); // автоматически public String what(); void adjust(); } class Wind implements Instrument { public void play() { System.out.println(" Wind.play()" ); } public String what() { return " Wind"; } public void adjust() {}} class Percussion implements Instrument { public void play() { System.out.println(" Percussion.play()" ); } public String what() { return " Percussion"; } public void adjust() {}} class Stringed implements Instrument { public void play() { System.out.println(" Stringed.play()" ); } public String what() { return " Stringed"; } public void adjust() {}} class Brass extends Wind { public void play() { System.out.println(" Brass.play()" ); } public void adjust() { System.out.println(" Brass.adjust()" ); }} class Woodwind extends Wind { public void play() { System.out.println(" Woodwind.play()" ); } public String what() { return " Woodwind"; }} public class Music5 { // Не беспокойтесь о типе, добавленные типы // продолжают работать правильно: static void tune(Instrument i) { //... i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Приведение к базовому типу во время добавления в массив: orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); }} ///: ~Этот кусок кода работает точно так же. Не имеет значения, если Вы приводите к базовому типу, к обычному классу Instrument, abstract классу Instrument, или к интерфейсу Instrument. Поведение остается одно и то же. В частности, Вы можете видеть в методе tune( ), что в нем нет никаких доказательств того, что Instrument это обычный класс или abstract класс или же интерфейс. Это и есть цель: каждый подход (принцип) дает программисту различные варианты контроля над путем создания и использования объектов.
БИЛЕТ №11 Шаблонные функции Простой пример шаблонной функции: Type square (Type a){ Type b; b = a*a; return b; } int x = 5; int i; i = square(5); float y = 0.5; float f; f = square(y);Если бы мы создавали функции по старинке, то тогда бы пришлось писать две разные функции: для типа int и для типа float. А если бы понадобилась такая же функция, использующая другие типы, пришлось бы заново писать и её. Используя шаблоны, можно ограничиться только одним экземпляром функции, оставив всю грязную работу компилятору. Вместо использования какого-то определённого типа, в функции используется параметрический тип (или по другому - аргумент шаблона). Здесь я обозвал параметрический тип идентификатором Type. В функции этот идентификатор встречается три раза: возвращаемое значение, аргумент функции и определение переменной b. То есть Type используется как любой обычный тип. Но чтобы код заработал, перед функцией нужно добавить следующую строку (я показал несколько вариантов синтаксиса, все они рабочие): template < class Type> Type square (Type a) template < class Type> Type square (Type a) template< class Type > Type square (Type a) template < class Type > Type square (Type a)Итак, перед функцией должно стоять ключевое слово template (шаблон), а в угловых скобках нужно указать имя параметрического типа с ключевым словом class. Вместо ключевого слова class можно использовать type - в общем-то никакой разницы. Идентификатор параметрического типа тоже может быть любым. Мы часто будем пользоваться вот такими: TypeA, TypeB, Datatype, T. Важное замечание: У шаблонных функций должен быть аргумент, чтобы компилятор мог определить какой именно тип использовать. В шаблонах можно использовать несколько параметрических типов, и конечно же можно смешивать параметрические типы со стандартными (только нужно позаботиться о правильном приведении типов). Приведу пример в котором используется два параметрических типа TypeA, TypeB и базовый тип int: template < class TypeA, class TypeB> TypeB example_function (TypeA a, TypeB b){ int x = 5; b = a + x; return b; }Но шаблонные функции - не самое интересное, что мы сегодня рассмотрим. Шаблонные классы В общем-то шаблонные классы создаются почти так же как и шаблонные функции - перед именем класса записывается ключевое слово template. Шаблонные классы рассмотрим на примере стека: template < class Type> class stack{private: int top; Type s[10]; public: stack (): top(0) {} void push(Type var) { top++; s[top] = var; } Type pop(); }; template < class Type> Type stack< Type>:: pop(){ Type var = s[top]; top--; return var; }Здесь мы определили стек из десяти элементов. Эти элементы могут быть какого угодно типа, об этом чуть-чуть ниже. Единственное на что хочу обратить ваше внимание: определение функций push и pop. Функция push определена внутри класса, а функция pop - снаружи. Для всех функции объявлённых за пределами класса, нужно обязательно указывать ключевое слово template. Выражение перед именем функции совпадает с тем, которое указывается перед именем класса. Теперь посмотрим как работать с шаблонными классами: stack< int> s1; stack< float> s2; s1.push(3); s1.push(2); s1.pop(); s2.push(0.5);При создании объекта, после имени класса нужно поставить угловые скобки, в которых указать нужный тип. После этого объекты используются так, как мы привыкли. У шаблонных классов есть одна потрясающая особенность - кроме стандартных типов, они могут работать и с пользовательскими. Рассмотрим небольшой пример. Для этого определим простой класс warrior: class warrior{public: int health; warrior (): health(0) {}}; stack< warrior> s; warrior w1; warrior w2; warrior w3; s.push(w1); s.push(w3); s.pop();class Base { void method2() { class A { // нормальный класс static class B { class C { void f() { void g() { В этом примере есть несколько типов вложенных и внутренних классов. Вложенный класс, не объявленный статическим, называется внутренним классом. В данном коде B является вложенным классом, а C - вложенным и внутренним классом. Главной целью данной статьи являются анонимные классы. Вы можете понять суть анонимных классов, изучив приведенный выше пример. Основная особенность - анонимный класс не имеет имени. Анонимный класс является подклассом существующего класса (в примере Base) или реализации интерфейса. Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально. Например, в следующем фрагменте кода объявляются два различных анонимных класса: Base bref1 = new Base() { Base bref2 = new Base() { Каждый анонимный класс объявляется внутри выражения. Интерфейсы Ключевое слово interface осуществляет, на шаг дальше, концепцию, реализованную в abstract. Вы можете думать, что это просто чисто abstract класс. Он позволяет создателю заложить форму (структуру) класса: имена методов, списки аргументов, возвращаемые типы, но только не тела методов. Interface также может содержать поля, но все они будут, хотя и косвенно static и final. Interface предоставляет только форму, образ, но не предоставляет его реализацию. Interface " говорит": " Все классы, реализующие этот особый интерфейс будут выглядеть одинаково". Поэтому, любой код, использующий interface знает, какой из методов может быть вызван для этого interface, впрочем, это все. Так что interface используется в качестве установления " протокола" между классами. (Некоторые ООЯ имеют даже встроенное ключевое слово protocol, делающее то же самое действие.) Что бы создать interface, используйте ключевое слово interface вместо ключевого слова class. Как и у класса, Вы можете добавить ключевое слово public до interface (но только если этот интерфейс определен в файле с тем же именем) или оставить его пустым, тогда он станет " friendly" и его можно будет использовать только членам одного с ним пакета. Для создания класса согласованного с особенным interface (или группой interface-ов) используйте ключевое слово implements. Тем самым Вы объявляете " Interface это на что похож мой класс, а теперь я скажу, как он должен работать." Все остальное, кроме этого, выглядит, как наследование. Диаграмма для примера с инструментами: Как только Вы примените interface, то этот класс сразу же становится обычным и в последствии он может быть расширен обычным способом. Вы можете выбрать явно объявления методов в interface как public. Но они таковыми являются, даже если Вы этого и не объявляете. Так что, когда Вы реализуете interface, методы из него должны быть определены как public. В противном случае, они будут по умолчанию friendly и Вы будете ограничены в доступе к ним во время наследования, поскольку доступ будет запрещен компилятором. Это Вы можете увидеть в измененном примере Instrument. Заметьте, что каждый метод в interface строго определен, только так компилятор и позволяет делать. В дополнение, ни один из методов в Instrument не определен как public, но они автоматически public по любому: //: c08: music5: Music5.java// Интерфейсы.import java.util.*; interface Instrument { // Константа времени компиляции: int i = 5; // static & final // Не могут быть получены определения методов: void play(); // автоматически public String what(); void adjust(); } class Wind implements Instrument { public void play() { System.out.println(" Wind.play()" ); } public String what() { return " Wind"; } public void adjust() {}} class Percussion implements Instrument { public void play() { System.out.println(" Percussion.play()" ); } public String what() { return " Percussion"; } public void adjust() {}} class Stringed implements Instrument { public void play() { System.out.println(" Stringed.play()" ); } public String what() { return " Stringed"; } public void adjust() {}} class Brass extends Wind { public void play() { System.out.println(" Brass.play()" ); } public void adjust() { System.out.println(" Brass.adjust()" ); }} class Woodwind extends Wind { public void play() { System.out.println(" Woodwind.play()" ); } public String what() { return " Woodwind"; }} public class Music5 { // Не беспокойтесь о типе, добавленные типы // продолжают работать правильно: static void tune(Instrument i) { //... i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Приведение к базовому типу во время добавления в массив: orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); }} ///: ~Этот кусок кода работает точно так же. Не имеет значения, если Вы приводите к базовому типу, к обычному классу Instrument, abstract классу Instrument, или к интерфейсу Instrument. Поведение остается одно и то же. В частности, Вы можете видеть в методе tune( ), что в нем нет никаких доказательств того, что Instrument это обычный класс или abstract класс или же интерфейс. Это и есть цель: каждый подход (принцип) дает программисту различные варианты контроля над путем создания и использования объектов.
БИЛЕТ №12 Обработка исключений В этой главе обсуждается используемый в Java механизм обработки исключений. Исключение в Java — это объект, который описывает исключительное состояние, возникшее в каком-либо участке программного кода. Когда возникает исключительное состояние, создается объект класса Exception. Этот объект пересылается в метод, обрабатывающий данный тип исключительной ситуации. Исключения могут возбуждаться и «вручную» для того, чтобы сообщить о некоторых нештатных ситуациях.
Основы К механизму обработки исключений в Java имеют отношение 5 ключевых слов: — try, catch, throw, throws и finally. Схема работы этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw) исключение, которое в зависимости от его типа вы можете перехватить (catch) или передать умалчиваемому (finally) обработчику. Ниже приведена общая форма блока обработки исключений. try { // блок кода } catch (ТипИсключения1 е) { // обработчик исключений типа ТипИсключения1 } catch (ТипИсключения2 е) { // обработчик исключений типа ТипИсключения2 throw(e) // повторное возбуждение исключения } finally { } Типы исключений В вершине иерархии исключений стоит класс Throwable. Каждый из типов исключений является подклассом класса Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Один из них — класс Ехception — используется для описания исключительных ситуации, которые должны перехватываться программным кодом пользователя. Другая ветвь дерева подклассов Throwable — класс Error, который предназначен для описания исключительных ситуаций, которые при обычных условиях не должны перехватываться в пользовательской программе.
Неперехваченные исключения Объекты-исключения автоматически создаются исполняющей средой Java в результате возникновения определенных исключительных состояний. Например, очередная наша программа содержит выражение, при вычислении которого возникает деление на нуль.
class Exc0 { public static void main(string args[]) { int d = 0; int a = 42 / d; } }
Вот вывод, полученный при запуске нашего примера. С: \> java Exc0 java.lang.ArithmeticException: / by zero at Exc0.main(Exc0.java: 4)
Обратите внимание на тот факт что типом возбужденного исключения был не Exception и не Throwable. Это подкласс класса Exception, а именно: ArithmeticException, поясняющий, какая ошибка возникла при выполнении программы. Вот другая версия того же класса, в которой возникает та же исключительная ситуация, но на этот раз не в программном коде метода main.
class Exc1 { static void subroutine() { int d = 0; int a = 10 / d; } public static void main(String args[]) { Exc1.subroutine(); } }
Вывод этой программы показывает, как обработчик исключений исполняющей системы Java выводит содержимое всего стека вызовов. С: \> java Exc1 java.lang.ArithmeticException: / by zero at Exc1.subroutine(Exc1.java: 4) at Exc1.main(Exc1.java: 7)
try и catch Для задания блока программного кода, который требуется защитить от исключений, используется ключевое слово try. Сразу же после try-блока помещается блок catch, задающий тип исключения которое вы хотите обрабатывать.
class Exc2 { public static void main(String args[]) { try { int d = 0; int a = 42 / d; } catch (ArithmeticException e) { System.out.println(" division by zero" ); } } } Целью большинства хорошо сконструированных catch-разделов должна быть обработка возникшей исключительной ситуации и приведение переменных программы в некоторое разумное состояние — такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение – division by zero).
Несколько разделов catch В некоторых случаях один и тот же блок программного кода может возбуждать исключения различных типов. Для того, чтобы обрабатывать подобные ситуации, Java позволяет использовать любое количество catch-разделов для try-блока. Наиболее специализированные классы исключений должны идти первыми, поскольку ни один подкласс не будет достигнут, если поставить его после суперкласса. Следующая программа перехватывает два различных типа исключений, причем за этими двумя специализированными обработчиками следует раздел catch общего назначения, перехватывающий все подклассы класса Throwable.
class MultiCatch { public static void main(String args[]) { try { int a = args.length; System.out.println(" a = " + a); int b = 42 / a; int c[] = { 1 }; c[42] = 99; } catch (ArithmeticException e) { System.out.println(" div by 0: " + e); } catch(ArrayIndexOutOfBoundsException e) { System.out.println(" array index oob: " + e); } } }
Популярное:
|
Последнее изменение этой страницы: 2016-07-14; Просмотров: 870; Нарушение авторского права страницы