Java 7

Хабибуллин Ильдар Шаукатович

Классы-оболочки и generics

 

Java — полностью объектно-ориентированный язык. Это означает, что все, что только можно, в Java представлено объектами.

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

Но и для этих типов в языке Java есть соответствующие классы — классы-оболочки (wrapper) примитивных типов. Конечно, они предназначены не для вычислений, а для действий, типичных при работе с классами, — создания объектов, преобразования типов объектов, получения численных значений объектов в разных формах и передачи объектов в методы по ссылке.

На рис. 4.1 показана одна из ветвей иерархии классов Java. Для каждого примитивного типа в пакете j ava. lang есть соответствующий класс. Числовые классы имеют общего предка — абстрактный класс Number, в котором описаны шесть методов, возвращающих числовое значение, содержащееся в классе, приведенное к соответствующему примитивному типу: byteValue(), doubleValue(), floatValue(), intValue(), longVaiue (), shortValue (). Эти методы переопределены в каждом из шести числовых классов-оболочек Byte, Short, Integer, Long, Float и Double. Имена классов-оболочек, за исключением класса Integer, совпадают с именами соответствующих примитивных типов, но начинаются с заглавной буквы.

Помимо метода сравнения объектов equals(), переопределенного из класса Object, все описанные в этой главе числовые классы, класс Character и класс Boolean имеют метод

Object—р Number-

- Boolean

-Character

-Class

■ — BigDecimal —Blglnteger

— Byte

— Double —Float —Integer

— Long

— Short

L Character.Subset-i— InputSubset

Character.UnicodeBlock

Рис. 4.1. Классы примитивных типов

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

□ 0, если сравниваемые значения равны;

□ отрицательное число (-1), если числовое значение в данном объекте меньше, чем в объекте-аргументе или, для класса Boolean, в данном объекте false, а в аргументе — true;

□ положительное число (+1), если числовое значение в данном объекте больше числового значения, содержащегося в аргументе или в данном объекте true, а в аргументе — false.

В каждом из этих классов есть статический метод

int compare(xxx a, xxx b);

который сравнивает значения двух чисел, символов или логических переменных a и b, заданных простыми типами boolean, byte, short, char, int, long, float, double, так же, как и метод compareTo (), и возвращает те же значения.

Еще один полезный статический метод

Xxx valueOf(xxx a);

в котором xxx — это один из простых типов boolean, byte, short, char, int, long, float, double, возвращает объект соответствующего типа. Документация настоятельно рекомендует применять этот метод для создания объектов из простых типов, а не конструктор соответствующего класса.

Что полезного можно найти в классах-оболочках?

Числовые классы

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

тип: Byte.parseByte(), Double.parseDouble(), Float.parseFloat(), Integer.parseInt(), Long.parseLong(), Short.parseShort ( ). Исходная строка типа String, как всегда в статических методах, служит параметром метода. Эти методы полезны при вводе данных в поля ввода, обработке аргументов командной строки, т. е. всюду, где числа представляются строками символов, состоящими из цифр со знаками плюс или минус и десятичной точкой.

В каждом из этих классов есть статические константы MAX_VALUE и MIN_VALUE, показывающие диапазон числовых значений соответствующих примитивных типов. В классах

Double и Float есть еще константы POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN, о которых шла речь в главе 1, и логические методы проверки isNaN ( ), isInfinite ( ).

Если вы хорошо знаете двоичное представление вещественных чисел, то можете воспользоваться статическими методами floatToIntBits ( ) и doubleToLongBits ( ), представляющими последовательность битов, из которых состоит двоичное представление вещественного числа, в виде целого числа типа int или long соответственно. Исходное вещественное число задается как аргумент метода. Получив целочисленное представление, вы можете изменить отдельные биты получившегося целого числа побитовыми операциями и преобразовать измененное целое число обратно в вещественное значение методами intBitsToFloat ( ) и longBitsToDouble ().

Статическими методами toBinaryString(), toHexString() и toOctalString() классов Integer и Long можно преобразовать целые значения типов int и long, заданные как аргумент метода, в строку символов, показывающую двоичное, шестнадцатеричное или восьмеричное представление числа.

В листинге 4.1 показано применение этих методов, а рис. 4.2 демонстрирует вывод результатов.

Рис. 4.2. Методы числовых классов

Листинг 4.1. Методы числовых классов

class NumberTest{

public static void main(String[] args){ int i = 0; short sh = 0;

double d = 0;

Integer k1 = Integer.valueOf(55);

Integer k2 = Integer.valueOf(100); Double d1 = Double.valueOf(3.14); try{

i = Integer.parseInt(args[0]); sh = Short.parseShort(args[0]);

d = Double.parseDouble(args[1]); d1 = new Double(args[1]); k1 = new Integer(args[0]); }catch(Exception e){} double x = 1.0 / 0.0; System.out.println("i = " + i); System.out.println("sh = " + sh);

System.out.println("d = " + d);

System.out.println("k1.intValue() = " + k1.intValue()); System.out.println("d1.intValue() = " + d1.intValue());

System.out.println("k1 > k2? " + k1.compareTo(k2));

System.out.println("x = " + x);

System.out.println("x isNaN? " + Double.isNaN(x));

System.out.println("x isInfinite? " + Double.isInfinite(x));

System.out.println("x == Infinity? " + (x == Double.POSITIVE INFINITY)); System.out.println("d = " + Double.doubleToLongBits(d));

System.out.println("i = " + Integer.toBinaryString(i));

System.out.println("i = " + Integer.toHexString(i));

System.out.println("i = " + Integer.toOctalString(i));

}

}

Методы parseInt () и конструкторы классов требуют обработки исключений, поэтому в листинг 4.1 вставлен блок try{}catch(){}. Обработку исключительных ситуаций мы подробно разберем в главе 21.

Начиная с версии Java SE 5 в JDK входит пакет java.util.concurrent.atomic, в котором, в частности, есть классы AtomicInteger и AtomicLong, обеспечивающие изменение числового значения этих классов на уровне машинных команд. Начальное значение задается конструкторами этих классов. Затем методами addAndGet ( ), getAndAdd ( ), incrementAndGet ( ), getAndnIncrement(), decrementAndGet(), getAndDecrement, getAndSet(), set() можно изменять это значение.

Автоматическая упаковка и распаковка типов

В листинге 4.1 объекты числовых классов создавались статическим методом, в котором указывалось числовое значение объекта:

Integer k1 = Integer.valueOf(55);

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

Integer k1 = 55;

как будто k1 — простая числовая переменная примитивного типа. Ничего нового в язык Java такая запись не вносит: компилятор, увидев ее, тут же восстановит применение статического метода. Но она облегчает работу программиста, предоставляя ему привычную форму определения переменной. Как говорят, компилятор делает автоматическую упаковку (auto boxing) числового значения в объект. Компилятор может сделать и автоматическую распаковку. После приведенных ранее определений объекта k1 можно написать, например,

int n = k1;

и компилятор извлечет из объекта k1 класса Integer числовое значение 55. Конечно, для этого компилятор обратится к методу intValue () класса Integer, но это незаметно для программиста.

Автоматическая упаковка и распаковка возможна и в методах классов. Рассмотрим простой класс.

class AutoBox{ static int f(Integer value){ return value; // Распаковка.

}

public static void main(String[] args){

Integer n = f(55);

}

}

В методе main() этого примера сначала число 55 приводится к типу параметра метода f() с помощью упаковки. Затем результат работы метода f () упаковывается в объект n класса Integer.

Автоматическую упаковку и распаковку можно использовать в выражениях, написав k1++ или даже (k1 + k2 / k1), но это уже слишком! Представьте себе, сколько упаковок и распаковок вставит компилятор и насколько это замедлит работу программы!

Настраиваемые типы (generics)

Введение в язык Java автоматической упаковки типов позволило определить еще одну новую конструкцию — настраиваемые типы (generics), позволяющие создавать шаблоны классов, интерфейсов и методов. Например, можно записать обобщенный настраиваемый (generic) класс

class MyGenericClass{ private T data;

public MyGenericClass(){}

public MyGenericClass(T data){ this.data = data;

}

public T getData(){ return data;

}

public void setData(T data){ this.data = data;

}

}

в котором есть поле data неопределенного пока типа, обозначенного буквой T. Разумеется, можно написать другую букву или даже идентификатор. Буква T появилась просто как первая буква слова Type.

Перед использованием такого класса-шаблона его надо настроить, задав при обращении к его конструктору определенный тип в угловых скобках. Например:

class MyGenericClassDemo{

public static void main(String[] args){

MyGenericClass iMyGen = new MyGenericClass(55);

Integer n = iMyGen.getData();

MyGenericClass dMyGen = new MyGenericClass(-37.3456);

Double x = dMyGen.getData();

}

}

Если при определении экземпляра настраиваемого класса и слева и справа от знака равенства в угловых скобках записан один и тот же тип, то справа его можно опустить для краткости записи, оставив только пару угловых скобок (так называемый "ромбовидный оператор", "diamond operator"). Используя это новое, введенное в Java 7, сокращение, предыдущий класс можно записать так:

class MyGenericClassDemo{

public static void main(String[] args){

MyGenericClass iMyGen = new MyGenericClass<>(55);

Integer n = iMyGen.getData();

MyGenericClass dMyGen = new MyGenericClass<>(-37.3456);

Double x = dMyGen.getData();

}

}

Рассмотрим более содержательный пример. Пусть нам надо вычислять среднее арифметическое значение нескольких чисел, причем в одном случае это целые числа, в другом — вещественные, в третьем — короткие или, наоборот, длинные целые числа. У среднего значения в любом случае будет тип double. В листинге 4.2 написан один общий класс-шаблон для всех этих случаев.

Листинг 4.2. Настраиваемый класс

class Average{ T[] data;

public Average(T[] data) { this.data = data; }

public double average(){ double result = 0.0;

for (T t: data) result += t.doubleValue(); return result / data.length;

}

public static void main(String[] args){

Integer[] iArray = {1, 2, 3, 4};

Double[] dArray = {3.4, 5.6, 2.3, 1.24};

Average iAver = new Average<>(iArray); System.out.println("int average = " + iAver.average()); Average dAver = new Average<>(dArray); System.out.println("double average = " + dAver.average());

}

Обратите внимание на то, что в заголовке класса в угловых скобках указано, что тип T — подкласс класса Number. Это сделано потому, что здесь тип T не может быть произвольным. Действительно, в методе average ( ) использован метод doubleValue ( ) класса Number, а это означает, что тип T ограничен классом Number и его подклассами. Кроме того, операции сложения и деления тоже допустимы только для чисел.

Конструкция ограничивает сверху множество типов, пригодных для настройки параметра T. Таким же образом, написав , можно ограничить снизу тип T только типом SomeClass и его супертипами.

У настраиваемого типа может быть более одного параметра. Они перечисляются в угловых скобках через запятую:

class MyGenericClass2{ private S id; private T data;

public MyGenericClass2() {}

public MyGenericClass2(S id, T data){ this.id = id; this.data = data;

}

public S getId(){ return id;

}

public void setId(S data){ this.id = id;

}

public T getData(){ return data;

}

public void setData(T data){ this.data = data;

}

}

Из этих примеров видно, что неопределенные типы S, T могут быть типами параметров конструкторов и типами возвращаемых методами значений. Разумеется, они могут быть типами параметров не только конструкторов, но и любых методов. Более того, типами параметров и типами возвращаемых значений методов могут быть настраиваемые типы. Можно написать метод в такой форме:

public MyGenericClass2 makeClass2(S id,

MyGenericClass data){ return new MyGenericClass2(id, data.getData());

}

и обратиться к нему так, как показано в листинге 4.3.

Листинг 4.3. Настраиваемые классы — параметры методов

public class MyGenericClass2Demo{

public MyGenericClass2

makeClass2(S id, MyGenericClass data){

return new MyGenericClass2(id, data.getData());

}

public static void main(String[] args){

MyGenericClass dMyGen = new MyGenericClass<>(34.456);

MyGenericClass2Demo d = new MyGenericClass2Demo<>();

MyGenericClass2 ldMyGen2 = d.makeClass2(123456L, dMyGen);

}

}

Шаблон типа (wildcard type)

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

Number n = new Long(123456L);

Number d = new Double(27.346);

Более того, это свойство распространяется на массивы:

Number[] n = new Long[100];

Можно ли распространить эту возможность на настраиваемые типы? Например, можно ли написать последний оператор листинга 4.3 так:

MyGenericClass2 n = // Сшибка!

d.makeClass2(123456L, dMyGen);

Ответ отрицательный. Из того, что какой-то класс B является подклассом класса A, не следует, что класс g будет подклассом класса g.

Это непривычное обстоятельство вынудило ввести дополнительную конструкцию — шаблон типа (wildcard type), применяемую в процессе настройки типа. Шаблон типа обозначается вопросительным знаком и означает "неизвестный тип" или "произвольный тип". Предыдущий код не вызовет возражений у компилятора, если написать его в таком виде:

MyGenericClass2 n = // Верно.

d.makeClass2(123456L, dMyGen);

или

MyGenericClass2 n = // Верно.

d.makeClass2(123456L, dMyGen);

Можно написать даже неограниченный шаблон типа

MyGenericClass2 n =

d.makeClass2(123456L, dMyGen);

Такая запись будет почти эквивалентна записи

MyGenericClass2 n =

d.makeClass2(123456L, dMyGen);

за тем исключением, что в первом случае компилятор сделает более строгие проверки.

Кроме записи , означающей "произвольный подтип типа Type, включая сам тип Type", можно написать выражение , означающее "произвольный супертип типа Type, включая сам тип Type".

Шаблон типа можно использовать в тех местах кода, где настраивается тип, в том числе в параметрах метода:

public MyGenericClass2 makeClass2(S id,

MyGenericClass data){

return new MyGenericClass2(id, data.getData());

}

но, поскольку шаблон типа не является типом, его нельзя применять для создания объектов и массивов. Следующие определения неверны:

Average a = // Ошибка!

new Average(iArray);

Average[] a = // Ошибка!

new Average[10];

Тем не менее при определении массива (но не объекта) можно записать неограниченный шаблон типа:

Average[] a = // Верно.

new Average[10];

Настраиваемые методы

Настраиваемыми могут быть не только типы, но и методы. Параметры настраиваемого метода (type parameters) указываются в заголовке метода в угловых скобках перед типом возвращаемого значения. Это выглядит так, как показано в листинге 4.4.

Листинг 4.4. Настраиваемый метод

public class MyGenericClass2Demo{

public MyGenericClass2

makeClass2(S id, MyGenericClass data){

return new MyGenericClass2(id, data.getData());

} public static void main(String[] args){

MyGenericClass dMyGen = new MyGenericClass(34.456);

MyGenericClass2Demo d =

new MyGenericClass2Demo();

MyGenericClass2 ldMyGen2 = d.makeClass2(123456L, dMyGen);

}

}

Метод makeClass2 () описан в простом, ненастраиваемом, классе MyGenericClass2Demo, и его параметры задаются в угловых скобках . Здесь можно записывать ограниченные параметры

public

MyGenericClass2 makeClass2(S id, MyGenericClass data){

return new MyGenericClass2(id, data.getData());

}

Как видно из листинга 4.4, специально настраивать метод не нужно, конкретные типы его параметров и возвращаемого значения определяются компилятором по переданным в метод аргументам.

Как вы убедились из приведенных примеров, настраиваемые типы и методы допускают сложную структуру параметров, так же как и вложенные классы. Мы еще не касались вопросов наследования настраиваемых типов, реализации настраиваемых интерфейсов, создания массивов настраиваемых типов. Все эти вопросы подробно рассмотрены на сайте Анжелики Лангер (Angelika Langer), в ее Java Generics FAQ, http:// .

 

Класс Boolean

Это очень небольшой класс, предназначенный главным образом для того, чтобы передавать логические значения в методы по ссылке.

Конструктор Boolean (String s) создает объект, содержащий значение true, если строка s равна "true" в произвольном сочетании регистров букв, и значение false — для любой другой строки.

Статический метод valueOf(boolean b) позволяет получить объект класса Boolean из значения примитивного типа boolean.

Пользуясь автоматической упаковкой, можно определение

Boolean b = new Boolean("true");

или

Boolean b = Boolean.valueOf(true);

сократить до

Boolean b = true;

Метод booleanValue () возвращает логическое значение, хранящееся в объекте.

Статический метод parseBoolean(String s) возвращает значение true, если строка s равна "true" в произвольном сочетании регистров букв, и значение false — для любой другой строки.

 

Класс Character

В этом классе собраны статические константы и методы для работы с отдельными символами.

Статический метод

digit(char ch, in radix);

переводит цифру ch системы счисления с основанием radix в ее числовое значение типа

int.

Статический метод

forDigit(int digit, int radix);

выполняет обратное преобразование целого числа digit в соответствующую цифру (тип char) в системе счисления с основанием radix.

Основание системы счисления должно находиться в диапазоне от Character.MIN_RADIX до Character.MAX RADIX.

Метод toString () переводит символ, содержащийся в классе, в строку с тем же символом.

Статические методы toLowerCase(), toUpperCase(), toTitleCase() возвращают символ, содержащийся в классе, в указанном регистре. Последний из этих методов предназначен для правильного перевода в верхний регистр четырех кодов Unicode, не выражающихся одним символом.

Статический метод

getName(int code);

возвращает полное Unicode-имя символа по его коду code.

Множество статических логических методов проверяют различные характеристики символа, переданного в качестве аргумента метода:

□ isDefined () — выясняет, определен ли символ в кодировке Unicode;

□ isDigit () — проверяет, является ли символ цифрой Unicode;

□ isIdentifierIgnorable () — выясняет, нельзя ли использовать символ в идентификаторах;

□ isISOControl () — определяет, является ли символ управляющим;

□ isBmpCodePoint () — определяет, лежит ли код символа в диапазоне \u0000-\uFFFF;

□ isSupplementaryCodePoint () — определяет, что код символа больше \uFFFF;

□ isJavaIdentifierPart ( ) - выясняет, можно ли использовать символ в идентифика

торах;

□ isJavaIdentifierStart () — определяет, может ли символ начинать идентификатор;

□ isLetter () — проверяет, является ли символ буквой Java;

□ isLetterOrDigit () — проверяет, является ли символ буквой или цифрой Unicode;

□ isLowerCase () — определяет, записан ли символ в нижнем регистре;

□ isSpaceChar () — выясняет, является ли символ пробелом в смысле Unicode;

□ isTitleCase () — проверяет, является ли символ титульным;

□ isUnicodeIdentifierPart ( ) - выясняет, можно ли использовать символ в именах

Unicode;

□ isUnicodeIdentifierStart () — проверяет, является ли символ буквой Unicode;

□ isUpperCase () — проверяет, записан ли символ в верхнем регистре;

□ isWhitespace () — выясняет, является ли символ пробельным.

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

Листинг 4.5 демонстрирует использование этих методов, а на рис. 4.3 показан вывод этой программы.

Листинг 4.5. Методы класса Character в программе CharacterTest

class CharacterTest{

public static void main(String[] args){

char ch = ’9’;

Character cl = Character.valueOf(ch);

System.out.println("ch = " + ch);

System.out.println("c1.charValue() = " + cl.charValue());

System.out.println("number of ’A’ = " + Character.digit('A', 16));

System.out.println("digit for 12 = " +

Character.forDigit(12, 16));

System.out.println("c1 = " + c1.toString());

System.out.println("ch isDefined? " +

Character.isDefined(ch));

System.out.println("ch isDigit? " +

Character.isDigit(ch));

System.out.println("ch isIdentifierIgnorable? " + Character.isIdentifierIgnorable(ch));

System.out.println("ch isISOControl? " + Character.isISOControl(ch));

System.out.println("ch isJavaIdentifierPart? " + Character.isJavaIdentifierPart(ch));

System.out.println("ch isJavaIdentifierStart? " + Character.isJavaIdentifierStart(ch)) ;

System.out.println("ch isLetter? " + Character.isLetter(ch));

System.out.println("ch isLetterOrDigit? " + Character.isLetterOrDigit(ch));

System.out.println("ch isLowerCase? " + Character.isLowerCase(ch));

System.out.println("ch isSpaceChar? " + Character.isSpaceChar(ch));

System.out.println("ch isTitleCase? " + Character.isTitleCase(ch)) ;

System.out.println("ch isUnicodeIdentifierPart? " + Character.isUnicodeIdentifierPart(ch));

System.out.println("ch isUnicodeIdentifierStart? " + Character.isUnicodeIdentifierStart(ch)) ;

System.out.println("ch isUpperCase? " + Character.isUpperCase(ch));

System.out.println("ch isWhitespace? " + Character.isWhitespace(ch));

}

}

Рис. 4.3. Методы класса Character в программе CharacterTest

В класс Character вложены классы Subset и UnicodeBlock, причем класс UnicodeBlock и еще один класс, InputSubset, являются расширениями класса Subset, как это видно на рис. 4.1. Объекты этого класса содержат подмножества кодировки Unicode.

Следует заметить, что каждая новая версия Java добавляет новые методы в класс Character, поэтому точный состав методов лучше посмотреть по документации.

Вместе с классами-оболочками удобно рассмотреть два класса для работы со сколь угодно большими числами.

 

Класс BigInteger

Все примитивные целые типы имеют ограниченный диапазон значений. В целочисленной арифметике Java нет переполнения, целые числа приводятся по модулю, равному диапазону значений.

Для того чтобы было можно производить целочисленные вычисления с любой разрядностью, в состав Java API введен класс BigInteger, хранящийся в пакете java.math. Этот класс расширяет класс Number, следовательно, в нем переопределены методы

doubleValue(), floatValue(), intValue(), longValue(). Методы byteValue() и shortValue() не переопределены, а прямо наследуются от класса Number.

Действия с объектами класса BigInteger не приводят ни к переполнению, ни к приведению по модулю. Если результат операции велик, то число разрядов просто наращивается. Числа хранятся в двоичной форме с дополнительным кодом.

Перед выполнением операции числа выравниваются по длине распространением знакового разряда.

Шесть конструкторов класса создают объект класса BigInteger из строки символов (знака числа и цифр), массива байтов или задают случайное число. Чаще всего используются три конструктора:

□ BigInteger(String value) — объект будет хранить большое целое число, заданное строкой цифр, перед которыми может стоять знак минус;

□ BigInteger(String value, int radix) — задается строка цифр со знаком value, записанная в системе счисления с основанием radix;

□ BigInteger(byte[] value) — объект будет хранить большое целое число, заданное массивом value, содержащим двоичное представление числа в дополнительном коде.

Три константы — zero, one и ten — моделируют нуль, единицу и число десять в операциях с объектами класса BigInteger.

Метод toByteArray() преобразует объект в массив байтов.

Большинство методов класса BigInteger моделируют целочисленные операции и функции, возвращая объект класса BigInteger:

□ abs () — возвращает объект, содержащий абсолютное значение числа, хранящегося в данном объекте this;

□ add (x) — операция сложения this + x;

□ and(x) — операция побитовой конъюнкции this & x;

□ andNot(x) — операция побитовой дизъюнкции с дополнением this & (~x);

□ divide (x) — операция деления this / x;

□ divideAndRemainder (x) - возвращает массив из двух объектов класса BigInteger, со

держащих частное и остаток от деления this на x;

□ gcd(x) — наибольший общий делитель абсолютных значений объекта this и аргумента x;

□ max(x) — наибольшее из значений объекта this и аргумента x;

□ min(x) — наименьшее из значений объекта this и аргумента x;

□ mod(x) — остаток от деления объекта this на аргумент метода x;

□ modInverse (x) — остаток от деления числа, обратного объекту this, на аргумент x;

□ modPow(n, m) — остаток от деления объекта this, возведенного в степень n, на m;

□ multiply(x) — операция умножения this * x;

□ negate () — перемена знака числа, хранящегося в объекте;

□ not () — операция отрицания -this;

□ or(x) — операция побитовой дизъюнкции this | x;

□ pow(n) — операция возведения числа, хранящегося в объекте, в степень n;

□ remainder(x) — операция взятия остатка от деления this % x;

□ shiftLeft (n) — операция сдвига влево this << n;

□ shiftRight (n) — операция арифметического сдвига вправо this >> n;

□ signum() — функция sign(x);

□ subtract (x) — операция вычитания this - x;

□ xor(x ) — операция "исключающее ИЛИ" this л x.

В листинге 4.6 приведены примеры использования данных методов, а рис. 4.4 показывает результаты выполнения этого листинга.

Листинг 4.6. Методы класса BigInteger В Программе BiglntegerTest

import java.math.BigInteger; class BigIntegerTest{

public static void main(String[] args){

BigInteger a = new BigInteger("99999999999999999"); BigInteger b = new BigInteger("88888888888888888888"); System.out.println("bits in a = " + a.bitLength()); System.out.println("bits in b = " + b.bitLength()); System.out.println("a + b = " + a.add(b)); System.out.println("a & b = " + a.and(b)); System.out.println("a & ~b = " + a.andNot(b)); System.out.println("a / b = " + a.divide(b));

BigInteger[] r = a.divideAndRemainder(b);

System.out.println("a / b: q = " + r[0] + ", r = " + r[1]); System.out.println("gcd(a, b) = " + a.gcd(b)); System.out.println("max(a, b) = " + a.max(b)); System.out.println("min(a, b) = " + a.min(b)); System.out.println("a mod b = " + a.mod(b)); System.out.println("1/a mod b = " + a.modInverse(b)); System.out.println("aAn mod b = " + a.modPow(a, b));

System.out.println("a * b = " + a.multiply(b)); System.out.println("-a = " + a.negate()); System.out.println("~a = " + a.not()); System.out.println("a | b = " + a.or(b)); System.out.println("a л 3 = " + a.pow(3)); System.out.println("a % b = " + a.remainder(b)); System.out.println("a << 3 = " + a.shiftLeft(3)); System.out.println("a >> 3 = " + a.shiftRight(3)); System.out.println("sign(a) = " + a.signum()); System.out.println("a — b = " + a.subtract(b)); System.out.println("a л b = " + a.xor(b));

}

}

Рис. 4.4. Методы класса BigInteger в программе BigIntegerTest

Обратите внимание на то, что в программу листинга 4.6 надо импортировать пакет

j ava.math.

 

Класс BigDecimal

Класс BigDecimal расположен в пакете j ava.math. Каждый объект этого класса хранит два целочисленных значения: мантиссу вещественного числа в виде объекта класса BigInteger и неотрицательный десятичный порядок числа типа int. Например, для числа 76,34862 будет храниться мантисса 7 634 862 в объекте класса BigInteger и порядок 5 как целое число типа int. Таким образом, мантисса может содержать любое количество цифр, а порядок ограничен значением константы Integer.MAX_VALUE.

Результат операции над объектами класса BigDecimal округляется по одному из восьми правил, определяемых следующими статическими целыми константами:

□ round_ceiling — округление в сторону большего целого;

□ round_down — округление к нулю, к меньшему по модулю целому значению;

□ round_floor — округление к меньшему целому;

□ round_half_down — округление к ближайшему целому, среднее значение округляется к меньшему целому;

□ round_half_even — округление к ближайшему целому, среднее значение округляется к четному числу;

□ round_half_up — округление к ближайшему целому, среднее значение округляется к большему целому;

□ round_unnecessary — предполагается, что результат будет целым, и округление не понадобится;

□ round_up — округление от нуля, к большему по модулю целому значению.

Три константы — zero, one и ten — моделируют вещественные нуль, единицу и вещественное число десять в операциях с объектами класса BigDecimal.

В классе BigDecimal около двадцати конструкторов. Четыре из них были введены еще в Java 2.

□ BigDecimal (BigInteger bi) - объект будет хранить большое целое bi, порядок равен

нулю;

□ BigDecimal(BigInteger mantissa, int scale) — задается мантисса mantissa и неотрицательный порядок scale объекта; если порядок scale отрицателен, возникает исключительная ситуация;

□ BigDecimal(double d) — объект будет содержать вещественное число удвоенной точности d; если значение d бесконечно или NaN, то возникает исключительная ситуация;

□ BigDecimal (String val) - число задается строкой символов val, которая должна со

держать запись числа по правилам языка Java.

При использовании третьего из перечисленных конструкторов возникает неприятная особенность, отмеченная в документации. Поскольку вещественное число при переводе в двоичную форму представляется, как правило, бесконечной двоичной дробью, то при создании объекта, например BigDecimal (0.1), мантисса, хранящаяся в объекте, окажется очень большой. Она показана на рис. 4.5. Но при создании такого же объекта четвертым конструктором, BigDecimal ("0.1"), мантисса будет равна просто 1.

Остальные конструкторы определяют точность представления числового значения объекта и правила его округления с помощью объекта класса MathContext или непосредственно.

В классе переопределены методы doubleValue (), floatValue (), intValue (), longValue ( ).

Три константы — zero, one и ten — моделируют нуль, единицу и число десять в операциях с объектами класса BigDecimal.

Большинство методов этого класса моделируют операции с вещественными числами. Они возвращают объект класса BigDecimal. Ниже в описании методов буква x обозначает объект класса BigDecimal, буква n — целое значение типа int, буква r — способ округления, одну из восьми перечисленных ранее констант:

□ abs () — абсолютное значение объекта this;

□ add (x) — операция сложения this + x;

□ divide (x, r) — операция деления this / x с округлением по способу r;

□ divide (x, n, r) — операция деления this / x с изменением порядка и округлением по способу r;

□ max(x) — наибольшее из this и x;

□ min(x) — наименьшее из this и x;

□ movePointLeft (n) — сдвиг влево на n разрядов;

□ movePointRight(n) — сдвиг вправо на n разрядов;

□ multiply(x) — операция умножения this * x;

□ negate () — возвращает объект с обратным знаком;

□ scale () — возвращает порядок числа;

□ setScale(n) — устанавливает новый порядок n;

□ setScale (n, r) — устанавливает новый порядок n и округляет число при необходимости по способу r;

□ signum () — знак числа, хранящегося в объекте;

□ subtract (x) — операция вычитания this — x;

□ toBiginteger () — округление числа, хранящегося в объекте;

□ unscaledValue () — возвращает мантиссу числа;

□ upl () — возвращает расстояние до следующего числа.

Листинг 4.7 показывает примеры использования этих методов, а рис. 4.5 — вывод результатов.

Начиная с версии Java SE 5 в класс BigDecimal введено еще много методов преобразования объекта и получения его характеристик.

Листинг 4.7. Методы класса BigDecimal в программе BigDecimalTest

import java.math.*; class BigDecimalTest{

public static void main(String[] args){

BigDecimal x = new BigDecimal("-12345.67890123456789");

BigDecimal y = new BigDecimal("345.7896e-4");

BigDecimal z = new BigDecimal(new BigInteger("123456789"), 8); System.out.println("|x| = " + x.abs());

System.out.println("x + y = " + x.add(y));

System.out.println("x / y = " + x.divide(y, BigDecimal.ROUND DOWN)); System.out.println("x / y = " + x.divide(y, 6, BigDecimal.ROUND HALF EVEN)); System.out.println("max(x, y) = " + x.max(y));

System.out.println("min(x, y) = " + x.min(y));

System.out.println("x << 3 = " + x.movePointLeft(3)); System.out.println("x >> 3 = " + x.movePointRight(3)); System.out.println("x * y = " + x.multiply(y)); System.out.println("-x = " + x.negate());

System.out.println("scale of x = " + x.scale());

System.out.println("increase scale of x to 20 = " + x.setScale(20)); System.out.println("decrease scale of x to 10 = " + x.setScale(10, BigDecimal.ROUND HALF UP));

System.out.println("sign(x) = " + x.signum());

System.out.println("x — y = " + x.subtract(y)); System.out.println("round x = " + x.toBigInteger()); System.out.println("mantissa of x = " + x.unscaledValue()); System.out.println("mantissa of 0.1 =\n= " +

new BigDecimal(0.1).unscaledValue());

}

}

Рис. 4.5. Методы класса BigDecimal в программе BigDecimalTest

Приведем еще один пример. Напишем простенький калькулятор, выполняющий четыре арифметических действия с числами любой величины. Он работает из командной строки. Программа представлена в листинге 4.8, а примеры использования калькулятора — на рис. 4.6.

Листинг 4.8. Простейший калькулятор

import java.math.*; class Calc{

public static void main(String[] args){ if (args.length < 3){

System.err.println("Usage: java Calc operand operator operand"); return;

}

BigDecimal a = new BigDecimal(args[0]);

BigDecimal b = new BigDecimal(args[2]); switch (args[1].charAt(0)){

case ' + ':
case '-':
case '*':
case в– /' :
default :

System.out.println(a.add(b)); break;

System.out.println(a.subtract(b)); break; System.out.println(a.multiply(b)); break; System.out.println(a.divide(b,

BigDecimal.ROUND_HAL F_EVEN)); break; System.out.println("Invalid operator");

}

Рис. 4.6. Результаты работы калькулятора

Почему символ умножения — звездочка — заключен на рис. 4.6 в кавычки? Приверженцам ОС UNIX это понятно, а для других дадим краткое пояснение.

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

Звездочка в кавычках понимается командной оболочкой как обычный символ. Командная оболочка снимает кавычки и передает интерпретатору Java звездочку, что нам и надо.

Класс Class

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

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

В классе Class нет конструкторов, экземпляр этого класса создается исполняющей системой Java во время загрузки класса и предоставляется методом getclass () класса Object, например:

String s = "Это строка";

Class c = s.getClass();

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

Статический метод forName(String class) класса Class возвращает объект класса Class для класса, указанного в аргументе, например:

Class c1 = Class.forName("java.lang.String");

Третий способ получения экземпляра класса Class — к имени класса через точку добавить слово class:

Class c2 = java.lang.String.class;

Логические методы isAnnotation(), isArray(), isInterface(), isEnum(), isPrimitive() позволяют уточнить, не является ли объект аннотацией, массивом, интерфейсом, перечислением или примитивным типом.

Если объект ссылочного типа, то можно извлечь сведения о вложенных классах, конструкторах, методах и полях методами getDeclaredClasses(), getDeclaredConstructors(), getDeclaredMethods(), getDeclaredFields() в виде массива классов: Class, Constructor, Method, Field соответственно. Последние три класса расположены в пакете j ava. lang.reflect и содержат сведения о конструкторах, полях и методах аналогично тому, как класс Class хранит сведения о классах.

Методы getClasses (), getConstructors (), getInterfaces (), getMethods (), getFields ( ) возвращают такие же массивы, но не всех, а только открытых членов класса.

Метод getSuperclass() возвращает суперкласс объекта ссылочного типа, getPackage ( ) — пакет, getModifiers() — модификаторы класса в битовой форме. Модификаторы можно затем расшифровать методами класса Modifier из пакета java.lang.reflect.

Листинг 4.9 показывает применение этих методов, а рис. 4.7 — вывод результатов.

Листинг 4.9. Методы класса class в программе ciassTest

import java.lang.reflect.*; class ClassTest{

public static void main(String[] args){

Class c = null, c1 = null, c2 = null;

Field[] fld = null;

String s = "Some string";

c = s.getClass();

try{

cl = Class.forName("java.lang.String"); // Старый стиль

c2 = java.lang.String.class; if (!c1.isPrimitive())

fld = c1.getDeclaredFields(); }catch(Exception e){}

System.out.println("Superclass c: " System.out.println("Package c: ' System.out.println("Modi fiers c: ' for(int i = 0; i < fld.length; i++) System.out.println(fld[i]);

}

}

// Новый стиль

// Все поля класса String

+ c);

+ c1);

+ c2);

+ c.getSuperclass()); + c.getPackage());

+ c.getModifiers());

Методы, возвращающие свойства классов, вызывают исключительные ситуации, требующие обработки. Поэтому в программу введен блок try{}catch(){}. Рассмотрение обработки исключительных ситуаций мы откладываем до главы 21.

Рис. 4.7. Методы класса Class в программе ClassTest

Начиная с версии Java SE 5 класс Class сделан настраиваемым: Class — это описание класса string, Class — описание класса Long и т. д. Это полезно, когда ссылка на класс Class передается в метод как параметр и надо определить, на какой же класс она направлена.

Вопросы для самопроверки

1. Зачем кроме примитивных типов в язык Java введены еще соответствующие классы-оболочки?

2. Можно ли использовать объекты числовых классов-оболочек в арифметических выражениях?

3. Какое наибольшее целое значение можно занести в объект класса BigInteger?

4. Какое наибольшее вещественное значение можно занести в объект класса BigDecimal?

5. Можно ли использовать в одном выражении значения примитивных типов и распакованные значения числовых классов-оболочек?

6. Для чего в язык Java введены настраиваемые типы?

7. Можно ли создавать настраиваемые интерфейсы или настраиваемыми могут быть только классы?

8. Должны ли методы настраиваемого класса быть настраиваемыми?

9. Можно ли создавать настраиваемые методы в обычных, не настраиваемых классах?

ГЛАВА 5