25

25


// initialization/ArraysOfPrimitives.java
// Массивы простейших типов.
import static net mindview.util.Print.*;
public class ArraysOfPrimitives {
public static void main(String[] args) { int:: al = { 1. 2, 3. 4, 5 }: int[] a2; a2 = al.
for(int i = 0; i < a2.length, i++)
a2[i] = a2[i] + 1; for(int i = 0; i < al.length; i++)
print("al[" + i +"]=" + al[i]);
}
} /* Output: al[0] = 2 al[l] = 3 al[2] = 4 al[3] = 5 al[4] = 6 *///:-
Массив al инициализируется набором значений, в то время как массив а2 — нет; присваивание по ссылке а2 присваивается позже — в данном случае при¬сваивается другой массив.
Все массивы (как массивы примитивов, так и массивы объектов) содержат поле> которое можно прочитать (но не изменить!) для получения количества элементов в массиве. Это поле называется length. Так как в массивах Java, С и С++ .нумерация элементов начинается с нуля, последнему элементу массива соответстйует индекс length—1. При выходе за границы массива С и С++ не пре¬пятствуют «прогулкам в памяти» программы, что часто приводит к печальным последствиям. Но Java защищает вас от таких проблем — при выходе за рамки массива происходит ошибка времени исполнения (исключение, тема главы 10) .
А если во время написания программы вы не знаете, сколько элементов вам понадобится в новом массиве? Тогда просто используйте new для создания его элементов. В следующем примере new работает, хотя в программе создается массив примитивных типов (оператор new неприменим для создания примити¬вов вне массива):
//: initialization/ArrayNew.java // Создание массивов оператором new. import java util.*;
import static net.mindview util.Print *;
public class ArrayNew {
public static void main(String[] args) { int[] a.
Random rand = new Random(47); a = new int[rand.nextlnt(20)]; print("Длина a = " + a length), print(Arrays.toString(a));
}
} /* Output- Длина a = 18
[0, 0. 0, 0. 0, 0, 0, 0, 0, 0. 0, 0. 0. 0. 0, 0. 0. 0] *///-
Размер массива выбирается случайным образом, с использованием метода Random.nextlnt(), генерирующего число от нуля до переданного в качестве аргу¬мента значения. Так как размер массива случаен, очевидно, что создание масси¬ва происходит во время исполнения программы. Вдобавок, результат работы программы позволяет убедиться в том, что элементы массивов простейших ти¬пов автоматически инициализируются «пустыми» значениями. (Для чисел и символов это ноль, а для логического типа boolean — false.)
Метод Arrays.toString(), входящий в стандартную библиотеку java.util, выдает печатную версию одномерного массива.
Конечно, в данном примере массив можно определить и инициализировать в одной строке:
int[] а = new int[rand.nextlnt(20)],
Если возможно, рекомендуется использовать именно такую форму записи. При создании массива непримитивных объектов вы фактически создаете массив ссылок. Для примера возьмем класс-обертку Integer, который является именно классом, а не примитивом:
//: initialization/ArrayClassObj java // Создание массива непримитивных объектов import java.util.*;
import static net.mindview util.Print.*,
public class ArrayClassObj {
' public static void main(String[] args) { Random rand = new Random(47); Integer[] a = new Integer[rand.nextlnt(20)]; print("длина a = " + a.length); for(int i = 0; i < a.length; i++)
a[i] = rand.nextlnt(500); // Автоматическая упаковка
print(Arrays.toString(a)),
}
} /* Output (пример) длина а = 18
[55. 193. 361. 461. 429. 368, 200. 22. 207, 288. 128. 51. 89. 309. 278. 498, 361. 20] *///-
Здесь даже после вызова new для создания массива
Integer[] а = new Integer[rand nextlnt(20)];
мы имеем лишь массив из ссылок — до тех пор, пока каждая ссылка не будет инициализирована новым объектом Integer (в данном случае это делается по¬средством автоупаковки):
a[i] = rand.nextlnt(500);
Если вы забудете создать объект, то получите исключение во время выпол¬нения программы, при попытке чтения несуществующего элемента массива.
Массивы объектов также можно инициализировать списком в фигурных скобках. Существует две формы синтаксиса:
//• i niti ali zati on/ArrayInit java // Инициализация массивов import java.util *;
public class Arraylnit {
public static void main(String[] args) { Integer[] a = {
new Integer(l), new Integer(2), 3, // Autoboxing
}:
Integer[] b = new Integer[]{ new Integer(1), new Integer(2), 3. // Автоматическая упаковка
}:
System. out. pri ntl n (Arrays. toStri ng (a)); System.out println(Arrays.toString(b));
}
} /* Output- [1. 2. 3] [1. 2. 3] *///:-
В обоих случаях завершающая запятая в списке инициализаторов не обяза¬тельна (она всего лишь упрощает ведение длинных списков).
Первая форма полезна, но она более ограничена, поскольку может использо¬ваться только в точке определения массива. Вторая форма может использовать¬ся везде, даже внутри вызова метода.
Списки аргументов переменной длины
Синтаксис второй формы предоставляет удобный синтаксис создания и вызова методов с эффектом, напоминающим списки аргументов переменной длины языка С.
Такой список способен содержать неизвестное заранее количество аргументов неизвестного типа. Так как абсолютно все классы унаследованы от общего кор¬невого класса 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.

Report Page