Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
GoslingJava2.doc
Скачиваний:
139
Добавлен:
23.02.2016
Размер:
2.39 Mб
Скачать

Глава 13 применение типов в программировании

Я завернусь в бумагу, Я намажусь клеем. Наклейте мне на голову марку! Я пришлю тебе себя по почте. Вуди Гатри, Почтовая песня

Типы в языке Java представлены классами. Почти для каждого из примитивных типов (int, boolean и т.д.) существует отдельный класс, известный под названием “оболочки” (wrapper); кроме того, имеется класс Class, представляющий типы классов и интерфейсов. Такие классы обладают следующими преимуществами:

  • Полезные статические методы для конкретного типа получают естественное “место жительства”. Например, методы для преобразования строки в float являются статическими методами класса Float.

  • То же самое справедливо и для описательных методов и полей. В каждом из классов для примитивных числовых типов присутствуют константы MIN_VALUE и MAX_VALUE; с помощью объекта Class можно получить доступ к методам, описывающим супертипы класса.

  • Для значений, относящихся к примитивным типам, можно создавать объекты-оболочки. Затем эти объекты используются в любом контексте, где требуется ссылка на класс Object. По этой причине классы для примитивных типов называются классами-оболочками .

Иерархия типов для этих классов выглядит следующим образом:

Классы для short или byte отсутствуют, поскольку эти типы используются главным образом из соображений экономии памяти. Все арифметические вычисления для short и byte производятся с выражениями типа int. Чтобы сохранить значение типа short или byte в объекте, пользуйтесь классом Integer. К сожалению, это также означает, что для типов byte и short отсутствуют константы MIN_VALUE и MAX_VALUE.

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

13.1. Класс Class

Для каждого класса и интерфейса в системе имеется представляющий его объект Class. Этот объект может использоваться для получения основных сведений о классе или интерфейсе и для создания новых объектов класса.

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

Существует два способа получить объект Class: запросить его у имеющегося объекта методом getClass или искать его по уточненному (включающему все имена пакетов) имени статическим методом Class.forName.

Простейшие методы Class предназначены для перемещения по иерархии типов. Приведенный ниже класс выводит такую иерархию для конкретного объекта Class:

public class TypeDesc {

public static void main(String[] args) {

TypeDesc desc = new TypeDesc();

for (int i = 0; i << args.length; i++) {

try {

desc.printType(Class.forName(args[i]), 0);

} catch (ClassNotFoundException e) {

System.err.print(e); // сообщить об ошибке

}

}

// по умолчанию работать со стандартным выводом

public java.io.PrintStream out = System.out;

// используется в printType() для пометки имен типов

private static String[]

basic = { "class", "interface" },

extended = { "extends", "implements" };

public void printType(Class type, int depth) {

if (type == null) // супертип Object равен null

return;

// вывести тип

for (int i = o; i << depth; i++)

out.print(" ");

String[] labels = (depth == 0 ? basic : extended);

out.print(labels[type.isInterface() ? 1 : 0] + " ");

out.println(type.getName());

// вывести интерфейсы, реализуемые классом

Class[] interfaces = type.getInterfaces();

for (int i = o; i << interfaces.length; i++)

printType(interfaces[i], depth + 1);

// рекурсивный вызов для суперкласса

printType(type.getSuperclass(), depth + 1);

}

}

Данный пример просматривает имена, введенные в командной строке, и вызывает printType для каждого из них. Делать это необходимо в try-блоке на тот случай, если класс с заданным именем отсутствует. Ниже показан результат работы программы для класса java.util.Hashtable (используется полное уточненное имя, поскольку этого требует метод forName):

class java.util.Hashtable

implements java.lang.Cloneable

extends java.lang.Object

extends java.util.Dictionary

extends java.lang.Object

Далее в тексте программы следует объявление выходного потока. Для краткости мы объявили его открытым, но в реальном приложении он должен быть закрытым, а обращения к нему должны осуществляться только через методы доступа. Затем следует описание двух строковых массивов.

Метод printType выводит описание своего типа, а затем рекурсивно вызывает себя для распечатки свойств супертипов. Параметр depth показывает, на сколько уровней мы поднялись в иерархии типов; в зависимости от его значения каждая строка с описанием снабжается соответствующим отступом. С каждым новым уровнем рекурсии это значение увеличивается.

При выводе типа метод isInterface определяет, является ли тип интерфейсом. Результат вызова используется для выбора префикса— пояснительной надписи. В самом низу иерархии типов, где значение depth равно 0, выводятся надписи “class” и “interface”; типы, находящиеся выше в иерархии, расширяют или реализуют свои исходные типы, поэтому используются термины “extends” и “implements”. Именно для этого и создаются массивы Basic и Extended. После выбора нужного префикса имя типа выводится методом getName. Конечно, класс Class содержит метод toString, однако в этом методе уже использован префикс “class” или “interface”. Мы хотим сами контролировать префикс, и потому пришлось создать собственную реализацию метода.

После вывода описания типа метод printType осуществляет рекурсивный вызов себя самого. Сначала определяются все интерфейсы, реализуемые исходным типом. /Если тип, для которого выводится информация, представляет собой интерфейс, то он расширяет, а не реализует свои интерфейсы-супертипы. Тем не менее, учет таких подробностей привел бы к неоправданному усложнению кода./ Затем выводится расширяемый им суперкласс (если он существует). Постепенно метод доходит до объекта Class класса Object, который не реализует никаких интерфейсов и для которого метод getSuperClass возвращает null; на этом рекурсия завершается.

Упражнение 13.1

Модифицируйте TypeDesc, чтобы избежать вывода информации о классе Object. Эти сведения избыточны, поскольку каждый объект в конечном счете расширяет класс Object. Используйте ссылку на объект Class для типа Object.

Объект Class может воспользоваться методом newInstance для создания нового экземпляра (объекта) представляемого им типа. При этом вызывается безаргументный конструктор класса или возбуждается исключение NoSuch MethodError, если класс не имеет безаргументного конструктора. Если класс или безаргументный конструктор недоступны (не являются открытыми илинаходятся в другом пакете), возбуждается исключение IllegalAccessException. Если класс является абстрактным, или представляет собой интерфейс, или создание завершилось неудачно по какой-то другой причине, возбуждается исключение InstantiationException. Создавать новые объекты подобным образом оказывается удобно, когда вы хотите написать универсальный код и позволить пользователю задать нужный класс. Например, в программе тестирования алгоритмов сортировки, приведенной в разделе “Проектирование расширяемого класса”, пользователь мог ввести имя тестируемого класса и использовать его в качестве параметра для вызова forName. Если введенное имя класса окажется допустимым, можно вызвать метод newInstance для создания объекта этого типа. Метод main для универсального класса SortDouble выглядит следующим образом:

static double[] testData = { 0.3, 1.3e-2. 7.9. 3.17, );

public static void main(String[] args) {

try {

for (int arg = 0; arg << args.length; arg++) {

String name = args[arg];

Class classFor = Class.forName(name);

SortDouble sorter

= (SortDouble)classFor.newInstance();

SortMetrics metrics

= sorter.sort(testData);

System.out.println(name + ": " + metrics);

for (int i = 0; i << testData.length; i++)

System.out.println("\t" + testData[i]);

}

} catch (Exception e) {

System.err.print(e); // сообщить об ошибке

}

}

Этот метод почти совпадает с BubbleSortDouble.main, однако из него исключены все имена типов. Он применим к любому типу, объекты которого могут использоваться в качестве объектов SortDouble и который содержит безаргументный конструктор. Теперь нам не придется переписывать метод main для каждого алгоритма сортировки— универсальный main годится для всех случаев. Все, что нужно сделать,— выполнить команду

java SortDouble TestClass ...

для любого класса-сортировщика (наподобие BubbleSortDouble); класс будет загружен и выполнен.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]