52

52


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

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

Второй способ гораздо интереснее. Новый класс создаетсякак специализа­цияуже существующего класса. Взяв существующий класс за основу, вы добав­ляете к нему свой код без изменения существующего класса. Этот механизм на­зываетсянаследованием(inheritance), и большую часть работы в нем совершает компилятор. Наследование является одним из «краеугольных камней» объект- но-ориентированного программирования; некоторые из его дополнительных применений описаны в главе 8.

у

и

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

Синтаксис композиции

До этого момента мы уже довольно часто использовали композицию — ссылка на внедряемый объект просто включается в новый класс. Допустим, вам пона­добился объект, содержащий несколько объектов String, пару полей примитив­ного типа и объект еще одного класса. Для не-примитивных объектов в новый класс включаются ссылки, а примитивы определяются сразу:

// reusing/SprinklerSystem java

// Композиция для повторного использования кода.

class WaterSource { private String s, WaterSourceO {

System out println( "WaterSourceO"); s = "сконструирован";

}

public String toStringO { return s; }

}

public class SprinklerSystem {

private String valvel. valve2, valve3, valve4, private WaterSource source = new WaterSourceO; private int i. private float f, public String toStringO { return

"valvel = " + valvel + " " + • "valve2 = " + valve2 + " " + "valve3 = " + valve3 + " " +

"valve4 = " + valve4 + "\n" +

••-j = - + -j + ■■ •• + -f = •• + f + •• " +

"source = " + source,

}

public static void main(String[] args) {

SprinklerSystem sprinklers = new SprinklerSystem(), System out println(sprinklers);

}

} /* Output- WaterSourceO

valvel = null valve2 = null valve3 = null valve4 = null i = 0 f = 0.0 source = сконструирован *///•-

В обоих классах определяется особый метод toString(). Позже вы узнаете, что каждый не-примитивный объект имеет метод toString(), который вызывается в специальных случаях, когда компилятор располагает не объектом, а хочет по­лучить его строковое представление в формате String. Поэтому в выражении из метода S р ri n klerSyste m.toStri n g ():

"source = " + source;

компилятор видит, что к строке "source = " «прибавляется» объект класса WaterSource. Компилятор не может это сделать, поскольку к строке можно «до­бавить» только такую же строку, поэтому он преобразует объект source в String, вызывая метод toString(). После этого компилятор уже в состоянии соеди­нить две строки и передать результат в метод System.out.println() (или стати­ческим методам print() и printnb(), используемым в книге). Чтобы подобное поведение поддерживалось вашим классом, достаточно включить в него ме­тод toString().

Примитивные типы, определенные в качестве полей класса, автоматически инициализируются нулевыми значениями, как упоминалось в главе 2. Однако ссылки на объекты заполняются значениями null, и при попытке вызова метода по такой ссылке произойдет исключение. К счастью, ссылку null можно вывес­ти без выдачи исключения.

Компилятор не создает объекты для ссылок «по умолчанию», и это логично, потому что во многих случаях это привело бы к лишним затратам ресурсов. Если вам понадобится проинициализировать ссылку, сделайте это самостоя­тельно:

•       в точке определения объекта. Это значит, что объект всегда будет ини­циализироваться перед вызовом конструктора;

•       в конструкторе данного класса;

•       непосредственно перед использованием объекта. Этот способ часто назы­ваютотложенной инициализацией.Он может сэкономить вам ресурсы в ситуациях, где создавать объект каждый раз необязательно и накладно;

•       с использованием инициализации экземпляров.

В следующем примере продемонстрированы все четыре способа:

//: reusing/Bath.java

// Инициализация в конструкторе с композицией.

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

class Soap {

private String s: SoapO {

printCSoapO"); s = "Constructed";

}

public String toStringO { return s: }

}

public class Bath {

private String // Инициализация в точке определения- si = "Счастливый", s2 = "Счастливый", s3. s4. private Soap castille; private int i; private float toy; public BathO {

print( В конструкторе BathO"), s3 = "Радостный"; toy = 3.14f;
ч
castille = new SoapO;

}

// Инициализация экземпляра-

{ i = 47; }

public String toStringO {

if(s4 == null) // Отложенная инициализация- s4 = "Радостный";

return

"si = " + si + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" +
H
i = " + i + "\n" + "toy = " + toy + "\n" + "castille = " + castille;

}

public static void main(String[] args) { Bath b = new Bath О; print(b);

}

} /* Output; В конструкторе Bath О SoapO

si = Счастливый s2 = Счастливый s3 = Радостный s4 = Радостный i = 47 toy = 3 14

castille = Сконструирован *///;-

Заметьте, что в конструкторе класса Bath команда выполняется до проведе­ния какой-либо инициализации. Если инициализация в точке определения не выполняется, нет никаких гарантий того, что она будет выполнена перед от­правкой сообщения по ссылке объекта — кроме неизбежных исключений вре­мени выполнения.