22

22


Списки аргументов переменной длины



Синтаксис второй формы предоставляет удобный синтаксис создания и вызова методов с эффектом, напоминающим списки аргументов переменной длины языка С.



Такой список способен содержать неизвестное заранее количество аргументов неизвестного типа. Так как абсолютно все классы унаследованы от общего кор¬невого класса Object, можно создать метод, принимающий в качестве аргумента массив Object, и вызывать его следующим образом:



//. initialization/VarArgs.java



// Использование синтаксиса массивов



// для получения переменного списка параметров.



class А { int i; }



public class VarArgs {



static void printArray(Object[] args) { for(Object obj • args)



System.out print(obj + " "); System out printlnO,



}



public static void main(String[] args) { printArray(new Object[]{



new Integer(47), new Float(3 14), new Double(ll.ll)



}).



printArray(new 0bject[]{"pa3", "два", "три" }); pri ntArray (new Object[]{new AO, new AO, new AO});



}



} /* Output: (Sample) 47 3.14 11.11 раз два триА@1а46еЗОА@3е25а5A@19821f*///:-



Видно, что метод print() принимает массив объектов типа Object, перебирает его элементы и выводит их. Классы из стандартной библиотеки Java при печати выводят осмысленную информацию, однако объекты классов в данном примере выводят имя класса, затем символ @ и несколько шестнадцатеричных цифр. Таким образом, по умолчанию класс выводит имя и адрес объекта (если только вы не переопределите в классе метод toString() — см. далее).



До выхода Java SE5 переменные списки аргументов реализовывались имен¬но так. В Java SE5 эта долгожданная возможность наконец-то была добавлена в язык — теперь для определения переменного списка аргументов может ис¬пользоваться многоточие, как видно в определении метода printArray:



//: initialization/NewVarArgs java // Создание списков аргументов переменной длины // с использованием синтаксиса массивов.



public class NewVarArgs {



static void printArray(Object... args) { for(Object obj • args)



System out.print(obj + " "); System.out.printlnO;



}



public static void main(String[] args) {



// Можно передать отдельные элементы printArray(new Integer(47), new Float(3 14), new Doubledl. 11));



printArray(47. 3 14F, 11.11): printArray("раз", "два", "три"): printArray(new АО. new АО, new АО): // Или массив.



printArray((Object[])new Integer[]{ 1, 2, 3, 4 }); printArray(), // Пустой список тоже возможен



}



} /* Output- (lb% match) 47 3 14 11.11 47 3 14 11.11 раз два триA@lbab50aА@сЗс749A@150bd4d12 3 4 *///•-



Резюме



Такой сложный механизм инициализации, как конструктор, показывает, на¬сколько важное внимание в языке уделяется инициализации. Когда Бьерн Страуструп разрабатывал С++, в первую очередь он обратил внимание на то, что низкая продуктивность С связана с плохо продуманной инициализацией, которой была обусловлена значительная доля ошибок. Аналогичные проблемы возникают и при некорректной финализации. Так как конструкторы позволяют гарантировать соответствующие инициализацию и завершающие действия по очистке (компилятор не позволит создать объект без вызова конструктора), тем самым обеспечивается полная управляемость и защищенность программы.



В языке С++ уничтожение объектов играет очень важную роль, потому что объекты, созданные оператором new, должны быть соответствующим образом разрушены. В Java память автоматически освобождается сборщиком мусора, и аналоги деструкторов обычно не нужны. В таких случаях сборщик мусора Java значительно упрощает процесс программирования и к тому же добавляет так необходимую безопасность при освобождении ресурсов. Некоторые сбор¬щики мусора могут проводить завершающие действия даже с такими ресурсами, как графические и файловые дескрипторы. Однако сборщики мусора добавля¬ют издержки во время выполнения программы, которые пока трудно реально оценить из-за сложившейся исторически медлительности интерпретаторов Ja¬va. И хотя в последнее время язык Java намного улучшил свою производитель¬ность, проблема его «задумчивости» все-таки наложила свой отпечаток на воз¬можность решения языком некоторого класса задач.



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




Управление доступом

В
ажнейшим фактором объектно-ориентированной разработки является отде­ление переменных составляющих от постоянных.



Это особенно важно для библиотек. Пользователь{программист-клиент)библиотеки зависит от неизменности некоторого аспекта вашего кода. С другой стороны, создатель библиотеки должен обладать достаточной свободой для проведения изменений и улучшений, но при этом изменения не должны нару­шить работоспособность клиентского кода.



Желанная цель может быть достигнута определенными договоренностями: Например, программист библиотеки соглашается не удалять уже существую­щие методы класса, потому что это может нарушить структуру кода программи- ста-клиента. В то же время обратная проблема гораздо острее. Например, как создатель библиотеки узнает, какие из полей данных используются программи- стом-клиентом? Это же относится и к методам, являющимся только частью реализации класса, то есть не предназначенным для прямого использования программистом-клиентом. А если создателю библиотеки понадобится удалить старую реализацию и заменить ее новой? Изменение любого из полей класса может нарушить работу кода программиста-клиента. Выходит, у создателя биб­лиотеки «связаны руки», и он вообще ничего не вправе менять.



Для решения проблемы в Java определеныспецификаторы доступа(access specifiers), при помощи которых создатель библиотеки указывает, что доступно программисту-клиенту, а что нет. Уровни доступа (от полного до минимально­го) задаются следующими ключевыми словами: public, protected, доступ в преде­лах пакета (не имеет ключевого слова) и private. Из предыдущего абзаца может возникнуть впечатление, что создателю библиотеки лучше всего хранить все как можно «секретнее», а открывать только те методы, которые, по вашему мнению, должен использовать программист-клиент. И это абсолютно верно, хотя и выглядит непривычно для людей, чьи программы на других языках (в особенности это касается С) «привыкли» к остутствию ограничений. К кон­цу этой главы вы наглядно убедитесь в полезности механизма контроля досту­па в Java.



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



Пакет как библиотечный модуль



Пакет содержит группу классов, объединенных в одномпространстве имен.Например, в стандартную поставку Java входит служебная библиотека, оформленная в виде пространства имен java,util. Один из классов java.util назы­вается ArrayList. Чтобы использовать класс в программе, можно использовать его полное имя java. util. Array List. Впрочем, полные имена слишком громоздки, поэтому в программе удобнее использовать ключевое слово import. Если вы со­бираетесь использовать всего один класс, его можно указать прямо в директиве import:



// access/Singlelmport.java



import java.util ArrayList,



public class Singlelmport {



public static void main(String[] args) {



ArrayList list = new java.util .ArrayListO:



}



} ///.-



Теперь к классу ArrayList можно обращаться без указания полного имени, но другие классы пакета java.util останутся недоступными. Чтобы импортировать все классы, укажите * вместо имени класса, как это делается почти во всех при­мерах книги:



import java.util *,



Механизм импортирования обеспечивает возможность управления про­странствами имен. Имена членов классов изолируются друг от друга. Метод f() класса А не конфликтует с методом f() с таким же определением (списком аргу­ментов) класса В. А как насчет имен классов? Предположим, что класс Stack создается на компьютере, где кем-то другим уже был определен класс с именем Stack. Потенциальные конфликты имен — основная причина, по которой так важны управление пространствами имен в Java и возможность создания уни­кальных идентификаторов для всех классов.



До этого момента большинство примеров книги записывались в отдельных файлах и предназначались для локального использования, поэтому на имена пакетов можно было не обращать внимания. (В таком случае имена классов размещаются в «пакете по умолчанию».) Конечно, это тоже решение, и такой подход будет применяться в книге, где только возможно. Но, если вы создаете библиотеку или программу, использующую другие программы Java на этой же машине, стоит подумать о предотвращении конфликтов имен.



Файл с исходным текстом на Java часто называюткомпилируемым модулем.Имя каждого компилируемого модуля должно завершаться суффиксом .java, а внутри него может находиться открытый (public) класс, имеющий то же имя, что и файл (с заглавной буквы, но без расширения .java). Любой компилируе­мый модуль может содержать не болееодногооткрытого класса, иначе компи­лятор сообщит об ошибке. Остальные классы модуля, если они там есть, скры­ты от окружающего мира — они не являются открытыми (public) и считаются «вспомогательными» по отношению к главному открытому классу.



В результате компиляции для каждого класса, определенного в файле .java, создается класс с тем же именемГно с расширением .class. Таким образом, при компиляции нескольких файлов .java может появиться целый ряд файлов с рас­ширением .class. Если вы программировали на компилируемом языке, то, на­верное, привыкли к тому, что компилятор генерирует промежуточные файлы (обычно с расширением OBJ), которые затем объединяются компоновщиком для получения исполняемого файла или библиотеки. Java работает не так. Ра­бочая программа представляет собой набор однородных файлов .class, которые объединяются в пакет и сжимаются в файл JAR (утилитой Java jar). Интерпре­татор Java отвечает за поиск, загрузку и интерпретацию
[1]
этих файлов.



Библиотека также является набором файлов с классами. В каждом файле имеется один public-класс с любым количеством классов, не имеющих специфи­катора public. Если вы хотите объявить, что все эти компоненты (хранящиеся в отдельных файлах .java и .class) связаны друг с другом, воспользуйтесь клю­чевым словом package.



Директива packageдолжнанаходиться в первой незакомментированной строке файла. Так, команда.



package access;



означает, что данный компилируемый модуль входит в библиотеку с именем access. Иначе говоря, вы указываете, что открытый класс в этом компилируе­мом модуле принадлежит имени mypackage и, если кто-то захочет использовать его, ему придется полностью записать или имя класса, или директиву import с access (конструкция, указанная выше). Заметьте, что по правилам Java имена пакетов записываются только строчными буквами.



Предположим, файл называется MyClass.java. Он может содержать один и только один открытый класс (public), причем последний должен называться MyClass (с учетом регистра символов):



//: access/mypackage/MyClass java



package access.mypackage;



public class MyClass {11...



} ///.-



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



//: access/QualifiedMyClass java



public class QualifiedMyClass {



public static void main(String[] args) { access mypackage.MyClass m =



new access mypackage.MyClass();



}



} ///:-



С ключевым словом import решение выглядит гораздо аккуратнее:

Report Page