Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии 


Тема 3.12 Использование ключевого слова final




 

Какие бы богатые возможности ни представляли наследование и переопределение методов, в некоторых случаях бывает необходимо запретить их. Предположим, например, что вы создаете класс, в котором инкапсулированы средства управления некоторым устройством. Помимо прочего, в этом классе содержатся метод, инициализирующий данное устройство. Предположим также, что инициализационная процедура может быть составлена так, что пользователю будут доступны некоторые секретные данные, хранящиеся на устройстве. В этой ситуации вам потребуется запретить переопределение инициализационного метода. Язык Java предоставляет средства, позволяющие без труда запретить переопределение метода или наследование класса. Решает эту задачу ключевое слово final.

 

Тема 3.13 Предотвращение переопределения методов

 

Для того чтобы предотвратить переопределение метода, надо при его объявлении указать модификатор final. Методы, определенные таким образом, нельзя переопределять. Ниже приведен фрагмент кода, иллюстрирующий использование ключевого слова final.

Листинг 3.20

public class A {

public final void meth() {

System.out.println("This is a final method.");

}

}

 

public class B extends A {

publicvoidmeth() { // Ошибка! Переопределить метод невозможно

System.out.println("Illegal!");

}

}

 

Поскольку метод meth () объявлен как final, его нельзя переопределить в классе В. Если вы попытаетесь сделать это, то возникнет ошибка при компиляции программы.

Тема 3.14 Предотвращение наследования

 

Предотвратить наследование класса можно, указав в определении класса ключевое слово final. При этом считается, что данное ключевое слово было применено ко всем методам класса. Очевидно, что не имеет никакого смысла применять final к абстрактным классам. Ведь абстрактный класс не завершен по определению, и объявленные в нем методы должны быть реализованы в подклассах.

Ниже приведен пример класса, подклассы которого создавать запрещено.

publicfinal class А { . //

// Следующее определение класса недопустимо.

public class В extends А { // Ошибка! Подкласс класса А

// не может существовать.

// ...

Как видно из комментариев, недопустимо, чтобы класс В наследовал класс А определен как final.

Тема 3.15 Класс Object

 

В языке Java определен специальный класс Object. По умолчанию он считается суперклассом всех остальных классов. Другими словами, все классы являют подклассами Object. Это означает, что переменная типа Object может ссылаться на экземпляр любого класса. Более того, поскольку массивы реализованы в виде классов, переменная типа Object может также ссылаться на любой массив.

В классе Object определены перечисленные ниже методы, которые доступны в любом объекте.

Таблица 3.1 – Методы класса Object

Метод Назначение
Object clone() Создает новый объект, аналогичный объекту предназначенному для клонирования
boolean equals(Object объект) Определяет, эквивалентны ли объекты
void finalize () Вызывается перед тем, как неиспользованный объект будет удален процедурой "сборки мусора"
Class<? Extends Object> getClass() Позволяет определить класс объекта в процессе выполнения программы
int hashCode() Возвращает хэш-код, связанный с вызывающим объектом
void notify() Возобновляет работу потока, ожидающего оповещения от объекта
void notifyAll() Возобновляет работу всех потоков, ожидающих оповещения от объекта
String toString() Возвращает строку, описывающую объект
void wait() Ожидает выполнения другого потока
void wait(long миллисекунды)
voidwait(long миллисекунды, int наносекунды)

 

Методы getClass(), notify(), notifyAll(), и wait() объявлены final, остальные можно переопределять в подклассах.

Если при создании класса предполагается проверка логической эквива­лентности объектов, которая не выполнена в суперклассе, следует переоп­ределить два метода: equals(Objectob) и hashCode(). Кроме того, переопределение этих методов необходимо, если логика приложения предусматривает использование элементов в коллекциях. Метод equals() при сравнении двух объектов возвращает истину, если содержимое объектов эквивалентно, и ложь – в противном случае. При переопределении метода equals() должны выполняться соглашения, предусмотренные спецификацией языка Java, а именно:



· рефлексивность – объект равен самому себе;

· симметричность – если x.equals(y) возвращает значение true, то и y.equals(x) всегда возвращает значение true;

· транзитивность – если метод equals() возвращает значение true при сравнении объектов x и y, а также y и z, то и при сравнении x и z будет возвращено значение true;

· непротиворечивость – при многократном вызове метода для двух не подвергшихся изменению за это время объектов возвращаемое значение всегда должно быть одинаковым;

· ненулевая ссылка при сравнении с литералом null всегда возвращает значение false.

При создании информационных классов также рекомендуется переопределять методы hashCode() и toString(), чтобы адаптировать их действия для создаваемого типа.

Метод hashCode() переопределен, как правило, в каждом классе и возвращает число, являющееся уникальным идентификатором объекта, завися­щим в большинстве случаев только от значения объекта. Его следует переопре­делять всегда, когда переопределен метод equals(). Метод hashCode() возвращает хэш-код объекта, вычисление которого управляется следующими соглашениями:

· во время работы приложения значение хэш-кода объекта не изменяется, если объект не был изменен;

· все одинаковые по содержанию объекты одного типадолжны иметь одинаковые хэш-коды;

· различные по содержанию объекты одного типа могут иметь различные хэш-коды.

Один из способов создания правильного метода hashCode(), гарантирующий выполнение соглашений, приведен ниже, в примере #10.

Метод toString() следует переопределять таким образом, чтобы кроме стандартной информации о пакете (опционально), в котором находится класс, и самого имени класса (опционально), он возвращал значения полей объекта, вызвавшего этот метод (то есть всю полезную информацию объекта), вместо хэш-кода, как это делается в классе Object. Метод toString() класса Object возвращает строку с описанием объекта в виде:

getClass().getName() + '@' + Integer.toHexString(hashCode())

Метод вызывается автоматически, когда объект выводится методами println(), print() и некоторыми другими.

Объекты в методы передаются по ссылке, в результате чего в метод передается ссылка на объект, находящийся вне метода. Поэтому если в методе изменить значение поля объекта, то это изменение коснется исходного объекта. Во избежание такой ситуации для защиты внешнего объекта следует создать клон (копию) объекта в методе. Класс Object содержит protected-метод clone(), осуществляющий побитовое копирование объекта производного класса. Однако сначала необходимо переопределить метод clone() как public для обеспечения возможности вызова из другого пакета. В переопределенном методе следует вызвать базовую версию метода super.clone(), которая и выполняет собственно клонирование. Чтобы окончательно сделать объект клонируемым, класс должен реализовать интерфейс Cloneable. ИнтерфейсCloneable не содержит методов относится к помеченным (tagged) интерфейсам, а его реализация гарантирует, что метод clone() класса Object возвратит точную копию вызвавшего его объекта с воспроизведением значений всех его полей. В противном случае метод генерирует исключение CloneNotSupportedException. Следует отметить, что при использовании этого механизма объект создается без вызова конструктора. В языке C++ аналогичный механизм реализован с помощью конструктора копирования.

Так как объекты создаются динамически с помощью операции new, а унич­тожаются автоматически, то желательно знать механизм ликвидации объектов и способ освобождения памяти. Автоматическое освобождение памяти, занимаемой объектом, выполняется с помощью механизма “сборки мусора”. Когда никаких ссылок на объект не существует, то есть все ссылки на него вышли из области видимости программы, предполагается, что объект больше не нужен, и память, занятая объектом, может быть освобождена. “Сборка мусора” происходит нерегулярно во время выполнения программы. Форсировать “сборку мусора” невозможно, можно лишь “рекомендовать” ее выполнить вызовом метода System.gc() или Runtime.getRuntime().gc(), но виртуальная машина выполнит очистку памяти тогда, когда сама посчитает это удобным. Вызов метода System.runFinalization() приведет к запуску метода finalize() для объектов утративших все ссылки.

Иногда объекту нужно выполнять некоторые действия перед освобождением памяти. Например, освободить внешние ресурсы. Для обработки таких ситуаций могут применяться два способа: конструкция try-finally и механизм finalization. Конструкция try-finally является предпочтительной, абсолютно надежной и будет рассмотрена в девятой главе. Запуск механизма finalization определяется алгоритмом сборки мусора и до его непосредственного исполнения может пройти сколь угодно много времени. Из-за всего этого поведение метода
finalize()может повлиять на корректную работу программы, особенно при смене JVM. Если существует возможность освободить ресурсы или выполнить другие подобные действия без привлечения этого механизма, то лучше без него обойтись. Виртуальная машина вызывает этот метод всегда, когда она собирается уничтожить объект данного класса. Внутри метода finalize(), вызываемого непосредственно перед освобождением памяти, следует определить действия, которые должны быть выполнены до уничтожения объекта.

Метод finalize() имеет следующую сигнатуру:

protected void finalize(){

// кодзавершения

}

Ключевое слово protected запрещает доступ к finalize() коду, определенному вне этого класса. Метод finalize() вызывается только перед самой “сборкой мусора”, а не тогда, когда объект выходит из области видимости, то есть заранее невозможно определить, когда finalize() будет выполнен, и недоступный объект может занимать память довольно долго. В принципе этот метод может быть вообще не выполнен! Недопустимо в приложении доверять такому методу критические по времени действия по освобождению ресурсов.

В листинге 3.21 показан пример переопределения всех методов.

Листинг 3.21

public class Test implements Cloneable {

private int a;

private int b;

public Test() {

}

public Test(int a, int b) {

this.a = a;

this.b = b;

}

public int getA() {

return a;

}

public int getB() {

return b;

}

public void setA(int a) {

this.a = a;

}

public void setB(int b) {

this.b = b;

}

@Override

public String toString() {

return "Test{" + "a=" + a + ", b=" + b + '}';

}

@Override

public boolean equals(Object obj) {

if (obj == null) {

return false;

}

if (getClass() != obj.getClass()) {

return false;

}

final Test other = (Test) obj;

if (this.a != other.a) {

return false;

}

if (this.b != other.b) {

return false;

}

return true;

}

@Override

public int hashCode() {

int hash = 7;

hash = 97 * hash + this.a;

hash = 97 * hash + this.b;

return hash;

}

@Override

protected Object clone() {

try {

return super.clone();

} catch (CloneNotSupportedException ex) {

System.out.println("Ошибкаприклонированииобъекта");

}

return null;

}

@Override

protected void finalize() throws Throwable {

System.out.println("Объект будет удален: а=" + a + ", b=" + b);

}

}

 

public class Main {

public static void main(String[] args) {

Test ob = new Test();

ob.setA(1);

ob.setB(2);

Test ob2 = new Test(1, 2);

if (ob.equals(ob2)) {

System.out.println("объетыравны");

}

System.out.println(ob);

System.out.println(ob.hashCode());

System.out.println(ob2.hashCode());

Test ob3 = (Test) ob.clone();

System.out.println(ob3);

System.out.println(ob3.hashCode());

ob = null;

System.gc(); // просьба выполнить "сборку мусора"

}

}

 

В результате выполнения данной программы получим:

объеты равны

Test{a=1, b=2}

Test{a=1, b=2}

Объект будет удален: а=1, b=2

Тема 3.16 Интерфейсы

Объявление интерфейса.

В объектно-ориентированном программировании часто бывает полезно указать, что класс должен делать, но не определять, как это должно быть сделано. Пример такого подхода уже встречался вам при рассмотрении абстрактных методов. Абстрактный метод определяет сигнатуру метода, но не его реализацию. Подкласс должен реализовать абстрактные методы, присутствующие в суперклассе. Таким образом, абстрактный метод описывает интерфейс метода, но не дает его реализацию. Убедившись, что абстрактные классы и методы очень полезны, имеет смысл несколько развить эту тему. В языке Java имеется возможность полностью отделить интерфейс класса от его реализации. Для этой цели служит ключевое слово interface.

Синтаксически интерфейсы напоминают абстрактные классы. Однако в составе интерфейса не может быть определено тело ни одного из методов. Таким образом, интерфейс не содержит реализации ни одного метода. Он описывает, что должно быть сделано, но не поясняет, как. Если интерфейс определен, его можно реализовать в сколь угодно большом количестве классов. Справедливо и обратное: один класс может реализовать любое количество интерфейсов.

Для того чтобы реализовать интерфейс, класс должен определить методы, описанные в интерфейсе. Каждый класс может содержать собственную реализацию методов. Другими словами, два класса могут реализовать один и тот же интерфейс различными способами, но каждый из них должен поддерживать один и тот же набор Методов. Метод, имеющий сведения об интерфейсе, может использовать экземпляры класса, поскольку интерфейс позволяет выполнять с объектом все необходимые операции. Посредством ключевого слова interface Java позволяет полностью использовать принцип полиморфизма "один интерфейс – несколько методов".

Интерфейс представляется в следующем формате.

тип_доступа interface имя {

тип_возвращаемого_значения имя-метода_1 (список параметров);

тип_возвращаемого_значения имя-метода_2 (список параметров);

переменная_1 = значение;

переменная_2 = значение;

// …

тип_возвращаемого_значения имя-метода_N (список параметров);

переменная_N = значение;

}

В данном случае модификатор типа доступа может быть либо public, либо не использоваться вовсе. Если спецификатор отсутствует, применяется правило предусмотренное по умолчанию, т.е. интерфейс оказывается доступным только членам своего пакета. Ключевое слово public указывает на то, что интерфейс может использоваться из любого другого пакета. (Код интерфейса, объявленного как public, должен храниться в файле, имя которого совпадает с именем интерфейса.) Имя интерфейса должно представлять собой допустимый идентификатор.

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

Каждый класс, в определении которого указан интерфейс, должен реализовывать все методы, объявленные в составе интерфейса. Методы, объявленные в составе интерфейса, считаются общедоступными.

Переменные, объявленные в составе интерфейса, не являются переменными экземпляра. Считается, что перед ними указаны ключевые слова public, final и static; и они обязательно подлежат инициализации. Другими словами, в составе интерфейса недопустимы переменные, а могут присутствовать только константы.

Ниже приведен пример определения интерфейсаRadio. Предполагается, что данный интерфейс будет реализован классомHomeRadio.

Листинг 3.22

public interface Radio {

public void on();

public void off();

public void nextChannel();

public void previousChannel();

public void showChannel();

}

Данный интерфейс объявлен как public, следовательно, он может быть реализован классом, принадлежащим любому пакету.

 

Реализация интерфейсов

Единожды определенный интерфейс может быть реализован одним или несколькими классами. Для того чтобы реализовать интерфейс, в определении класса надо включить ключевое слово implements, а затем определить метод объявленные в составе интерфейса. Определение класса, реализующего интерфейс, выглядит следующим образом:

class тип_класса extends суперкласс implements интерфейс {

// тело класса

}

Если класс должен реализовать несколько интерфейсов, то имена интерфейсов указываются через запятую. Очевидно, что в данном случае ключевое словоextends и имя суперкласса могут отсутствовать.

Реализуемые методы интерфейса должны быть объявлены как public. Сигнатура реализованного метода должна полностью соответствовать сигнатуре, заявленной в составе интерфейса.

Ниже приведен пример класса HomeRadioреализующего интерфейс Radio.

Листинг 3.23

// РеализацияинтерфейсаRadio

public class HomeRadio implements Radio {

private int channel;

@Override

public void on() {

System.out.println("Радио включено");

}

@Override

public void off() {

System.out.println("Радио выключено");

}

@Override

public void nextChannel() {

this.channel++;

}

@Override

public void previousChannel() {

this.channel--;

}

@Override

public void showChannel() {

System.out.println("Текущийканал "+channel);

}

}

Обратите внимание на то, что все методы объявлены как public. Это необходимо, так как любой метод интерфейса считается общедоступным. Метод, к которому нельзя обратиться из других классов, можно попросту не включать в интерфейс. Ниже приведен код программы, демонстрирующей использование класса(листинг 3.24).

Листинг 3.24

publicclassMain {

public static void main(String[] args) {

HomeRadio radio = new HomeRadio();

radio.on();

radio.showChannel();

radio.nextChannel();

radio.showChannel();

radio.nextChannel();

radio.showChannel();

radio.previousChannel();

radio.showChannel();

radio.off();

}

}

В результате выполнения данной программы получим:

Радио включено

Текущий канал 0

Текущий канал 1

Текущий канал 2

Текущий канал 1

Радио выключено

Класс, реализующий интерфейс, может содержать дополнительные переменные и методы. Это допустимо, более того, именно так в большинстве случаев ступают разработчики. Например, в приведенном ниже варианте класса HomeRadioдобавлен конструктор, методы setChannel() и getChannel().

Листинг 3.25

public class HomeRadio implements Radio {

private int channel;

public HomeRadio(int channel) {

this.channel = channel;

}

public void setChannel(int channel) {

this.channel = channel;

}

public int getChannel() {

return channel;

}

@Override

public void on() {

System.out.println("Радиовключено");

}

@Override

public void off() {

System.out.println("Радиовыключено");

}

@Override

public void nextChannel() {

this.channel++;

}

@Override

public void previousChannel() {

this.channel--;

}

@Override

public void showChannel() {

System.out.println("Текущийканал "+channel);

}

}





Рекомендуемые страницы:


Читайте также:



Последнее изменение этой страницы: 2016-05-30; Просмотров: 408; Нарушение авторского права страницы


lektsia.com 2007 - 2021 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.059 с.) Главная | Обратная связь