Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 36 37 38 39 40 41 42 43 44 ... 132

Также обратите особое внимание на порядок вызова завершающих методов для базового класса и объектов-членов в том случае, если они зависят друг от друга. В основном нужно следовать тому же принципу, что использует компилятор С++ при вызове деструкторов: сначала провести завершающие действия для вашего класса в последовательности, обратной порядку их создания. (Обычно для этого требуется, чтобы элементы базовых классов продолжали существовать.) Затем вызываются завершающие методы из базовых классов, как и показано в программе.

Во многих случаях завершающие действия не являются проблемой; достаточно дать сборщику мусора выполнить свою работу. Но уж если понадобилось провести их явно, сделайте это со всей возможной тщательностью и вниманием, так как в процессе сборки мусора трудно в чем-либо быть уверенным. Сборщик мусора вообще может не вызываться, а если он начнет работать, то объекты будут уничтожаться в произвольном порядке. Лучше не полагаться на сборщик мусора в ситуациях, где дело не касается освобождения памяти. Если вы хотите провести завершающие действия, создайте для этой цели свой собственный метод и не полагайтесь на метод finalize().

Сокрытие имен

Если какой-либо из методов базового класса Java был перегружен несколько раз, переопределение имени этого метода в производном классе не скроет ни одну из базовых версий (в отличие от С++). Поэтому перегрузка работает вне зависимости от того, где был определен метод — на текущем уровне или в базовом классе:

//: reusing/Hide java

// Перегрузка имени метода из базового класса

// в производном классе не скроет базовую версию метода.

import static net.mindview.util.Print.*:

class Milhouse {}

class Bart extends Homer { void doh(Milhouse m) {

print("doh(Milhouse)");

}

}

public class Hide {

public static void main(String[] args) { Bart b = new BartO; b doh(l); b doh('x'); b.doh(l.Of); b doh(new MilhouseO);

}

} /* Output. doh(float) doh(char) doh(float) doh(Milhouse) *///:-

Мы видим, что все перегруженные методы класса Homer доступны классу Bart, хотя класс Bart и добавляет новый перегруженный метод (в С++ такое действие спрятало бы все методы базового класса). Как вы увидите в следующей главе, на практике при переопределении методов гораздо чаще используется точно такое же описание и список аргументов, как и в базовом классе. Иначе легко можно запутаться (и поэтому С++ запрещает это, чтобы предотвратить совершение возможной ошибки).

В Java SE5 появилась запись @0verride; она не является ключевым словом, но может использоваться так, как если бы была им. Если вы собираетесь переопределить метод, используйте @0verride, и компилятор выдаст сообщение об ошибке, если вместо переопределения будет случайно выполнена перегрузка:

//• reusing/Lisa java // {CompileTimeError} (Won't compile)

class Lisa extends Homer {

(^Override void doh(Milhouse m) {

System out println("doh(Milhouse)");

}

class Homer {

char doh(char с) {

print("doh(char)"); return 'd';

}

float doh(float f) {

print("doh(float)"); return l.Of,

Композиция в сравнении с наследованием

И композиция, и наследование позволяют вам помещать подобъекты внутрь вашего нового класса (при композиции это происходит явно, а в наследовании — опосредованно). Вы можете поинтересоваться, в чем между ними разница и когда следует выбирать одно, а когда — другое.

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

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

// reusing/Car.java

// Композиция с использованием открытых объектов

// двигатель

class Engine {

public void startO {} // запустить public void rev() {} // переключить public void stopO {} // остановить

}

// колесо

class Wheel {

public void inflate(int psi) {} // накачать

}

// окно

class Window {

public void rollupO {} // поднять public void rolldownO {} // опустить

}

// дверь

class Door {

public Window window = new WindowO; // окно двери public void openО {} // открыть public void closeO {} // закрыть

}

// машина

public class Car {

public Engine engine = new EngineO; public Wheel[] wheel = new Wheel[4], public Door

left = new DoorO,

right = new DoorO: // двухдверная машина

public CarO {

for (int i =0; i <4; i++)

wheel[i] = new Wheel О;

}

public static void main(String[] args) { Car car = new CarO; car 1 eft.window.rollup(); car.wheel[0].inflate(72);

}

} /// -

Так как композиция объекта является частью проведенного анализа задачи (а не просто частью реализации класса), объявление членов класса открытыми (public) помогает программисту-клиенту понять, как использовать класс, и облегчает создателю класса написание кода. Однако нужно все-таки помнить, что описанный случай является специфическим и в основном поля класса следует объявлять как private.

При использовании наследования вы берете уже существующий класс и создаете его специализированную версию. В основном это значит, что класс общего назначения адаптируется для конкретной задачи. Если чуть-чуть подумать, то вы поймете, что не имело бы смысла использовать композицию машины и средства передвижения — машина не содержит средства передвижения, она сама есть это средство. Взаимосвязь «является» выражается наследованием, а взаимосвязь «имеет» описывается композицией.

protected

После знакомства с наследованием ключевое слово protected наконец-то обрело смысл. В идеале закрытых членов private должно было быть достаточно. В реальности существуют ситуации, когда вам необходимо спрятать что-либо от окружающего мира, тем не менее оставив доступ для производных классов.

Ключевое слово protected — дань прагматизму. Оно означает: «Член класса является закрытым (private) для пользователя класса, но для всех, кто наследует от класса, и для соседей по пакету он доступен». (В Java protected автоматически предоставляет доступ в пределах пакета.)

Лучше всего, конечно, объявлять поля класса как private — всегда стоит оставить за собою право изменять лежащую в основе реализацию. Управляемый доступ наследникам класса предоставляется через методы protected:

// reusing/Ore java

// Ключевое слово protected

import static net.mindview.util.Print.*;

class Villain {

private String name;

protected void set(String nm) { name = nm; } public Villain(String name) { this.name = name, } public String toStringO {

return "Я объект Villain и мое имя " + name;

}

public class Ore extends Villain { private int orcNumber, public Orc(String name, int orcNumber) { super(name);

this.orcNumber = orcNumber;

}

public void change(String name, int orcNumber) {

set(name); // Доступно, так как объявлено protected this.orcNumber = orcNumber;

}

public String toStringO {

return "Ore " + orcNumber + ": " + super.toString().

}

public static void main(String[] args) {

Ore ore = new ОгсС'Лимбургер". 12); print(orc);

огс.сЬапдеСБоб"* 19); print(orc);

}

} /* Output:

Ore 12: Я объект Villain и мое имя Лимбургер

Ore 19: Я объект Villain и мое имя Боб

*///-

Как видите, метод change() имеет доступ к методу set(), поскольку тот объявлен как protected. Также обратите внимание, что метод toString() класса Ore определяется с использованием версии этого метода из базового класса.

Восходящее преобразование типов

Самая важная особенность наследования заключается вовсе не в том, что оно предоставляет методы для нового класса, — наследование выражает отношения между новым и базовым классом. Ее можно выразить .следующим образом: «Новый класс имеет тип существующего класса».

Данная формулировка — не просто причудливый способ описания наследования, она напрямую поддерживается языком. В качестве примера рассмотрим базовый класс с именем Instrument для представления музыкальных инструментов и его производный класс Wind. Так как наследование означает, что все методы базового класса также доступны в производном классе, любое сообщение, которое вы в состоянии отправить базовому классу, можно отправить и производному классу. Если в классе Instrument имеется метод play(), то он будет присутствовать и в классе Wind. Таким образом, мы можем со всей определенностью утверждать, что объекты Wind также имеют тип Instrument. Следующий пример показывает, как компилятор поддерживает такое понятие:

//: reusing/Wind.java

// Наследование и восходящее преобразование.

class Instrument {

public void playO {} static void tune(Instrument i) { // ...

i .playO:

}

}

// Объекты Wind также являются объектами Instrument, // поскольку они имеют тот же интерфейс: public class Wind extends Instrument {

public static void main(String[] args) { Wind flute = new WindО.

Instrument.tune(flute); // Восходящее преобразование

}

} ///:-

Наибольший интерес в этом примере представляет метод tune(), получающий ссылку на объект Instrument. Однако в методе Wind.main() методу tune() передается ссылка на объект Wind. С учетом всего, что говорилось о строгой проверке типов в Java, кажется странным, что метод с готовностью берет один тип вместо другого. Но стоит вспомнить, что объект Wind также является объектом Instrument, и не существует метода, который можно вызвать в методе tune() для объектов Instrument, но нельзя для объектов Wind. В методе tune() код работает для Instrument и любых объектов, производных от Instrument, а преобразование ссылки на объект Wind в ссылку на объект Instrument называется восходящим преобразованием типов (upcasting).

1 ... 36 37 38 39 40 41 42 43 44 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий