25

25


//. access/Lunch.java // Спецификаторы доступа для классов. // Использование конструкторов, объявленных private, // делает класс недоступным при создании объектов.



class Soupl {



private SouplО {}



// (1) Разрешаем создание объектов в статическом методе: public static Soupl makeSoupO { return new SouplO;



}



}



class Soup2 {



private Soup2() {}



// (2) Создаем один статический объект и // по требованию возвращаем ссылку на него, private static Soup2 psl = new Soup2(): public static Soup2 accessO { return psl:



}



public void f() {}



}



// В файле может быть определен только один public-класс: public class Lunch {



void testPrivateO {



// Запрещено, т.к конструктор объявлен приватным: //! Soupl soup = new SouplO:



}



void testStaticO {



Soupl soup = Soupl.makeSoupO;



}



void testSingletonO {



Soup2.access О f();



}



)



До этого момента большинство методов возвращало или void, или один из примитивных типов, поэтому определение:



public static Soupl makeSoupO { return new SouplO:



} на первый взгляд смотрится немного странно. Слово Soupl перед именем мето­да (makeSbup) показывает, что возвращается методом. В предшествующих при­мерах обычно использовалось обозначение void, которое подразумевает, что ме­тод не имеет возвращаемого значения. Однако метод также может возвращать ссылку на объект; в данном случае возвращается ссылка на объект класса Soupl.



Классы Soupl H'Soup2 наглядно показывают, как предотвратить прямое соз­дание объектов класса, объявив все его конструкторы со спецификатором pri­vate. Помните, что без явного определения хотя бы одного конструктора компи­лятор сгенерирует конструктор по умолчанию (конструктор без аргументов). Определяя конструктор по умолчанию в программе, вы запрещаете его автома­тическое создание. Если конструктор объявлен со спецификатором private, ни­кто не сможет создавать объекты данного класса. Но как же тогда использовать этот класс? Рассмотренный пример демонстрирует два способа. В классе Soupl определяется статический метод, который создает новый объект Soupl и возвра­щает ссылку на него. Это бывает полезно в ситуациях, где вам необходимо про­вести некоторые операции над объектом перед возвратом ссылки на него, или при подсчете общего количества созданных объектов Soupl (например, для ог­раничения их максимального количества).



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



Резюме



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



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



В этой главе рассматривается процесс построения библиотек из классов; во-первых, механизм группировки классов внутри библиотеки и, во-вторых, механизм управления доступом к членам класса.



По оценкам проекты на языке С начинают «рассыпаться» примерно тогда, когда код достигает объема от 50 до 100 Кбайт, так как С имеет единое «про­странство имен»; в системе возникают конфликты имен, создающие массу не­удобств. В Java ключевое слово package, схема именования пакетов и ключевое слово import обеспечивают полный контроль над именами, так что конфликта имен можно легко избежать.



Существует две причины для ограничения доступа к членам класса. Пер­вая — предотвращение использования клиентами внутренней реализации класса, не входящей во внешний интерфейс. Объявление полей и методов со специфи­катором private только помогает пользователям класса, так как они сразу видят, какие члены класса для них важны, а какие можно игнорировать. Все это упро­щает понимание и использование класса.



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



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









[1]
Использовать Java-интерпретатор не обязательно. Существует несколько компиляторов, создаю­щих единый исполняемый файл.



[2]
На самом деле доступ private или protected могут иметьвнутренние классы,но это особый случай (см. главу 8).




Повторное использование классов

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



Повторное использование на базе копирования кода характерно для проце­дурных языков, подобных С, но оно работало не очень хорошо. Решение этой проблемы в 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 команда выполняется до проведе­ния какой-либо инициализации. Если инициализация в точке определения не выполняется, нет никаких гарантий того, что она будет выполнена перед от­правкой сообщения по ссылке объекта — кроме неизбежных исключений вре­мени выполнения.



При вызове метода toStringO в нем присваивается значение ссылке s4, чтобы все поля были должным образом инициализированы к моменту их использова­ния.



Синтаксис наследования



Наследование является неотъемлемой частью Java (и любого другого языка ООП). Фактически оно всегда используется при создании класса, потому что, даже если класс не объявляется производным от другого класса, он автоматиче­ски становится производным от корневого класса Java Object.

Report Page