Кабинет пользователя
Идентификатор пользователя
Пароль

Комментарии

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

  • за двумя наклонными чертами подряд //, без пробела между ними, начинается комментарий, продолжающийся до конца строки;
  • за наклонной чертой и звездочкой /* начинается комментарий, который может занимать несколько строк, до звездочки и наклонной черты */ (без пробелов между этими знаками).

Целые константы

Целые константы можно записывать в трех системах счисления:

  • в десятичной форме: +5, -7, 12345678 ;
  • в восьмеричной форме, начиная с нуля: 027, -0326, 0777 ; в записи таких констант недопустимы цифры 8 и 9;

Замечание

Число, начинающееся с нуля, записано в восьмеричной форме, а не в десятичной.

  • в шестнадцатеричной форме, начиная с нуля и латинской буквы х или X: 0xff0a, 0xFC2D, 0x45a8, 0X77FF ; здесь строчные и прописные буквы не различаются.

Целые константы хранятся в формате типа int (см. ниже).

В конце целой константы можно записать букву прописную L или строчную l , тогда константа будет сохраняться в длинном формате типа long (см. ниже): +25L, -0371, OxffL, OXDFDF1 .


Действительные

Действительные константы записываются только в десятичной системе счисления в двух формах:

  • c фиксированной точкой: 37.25, -128.678967, +27.035 ;
  • с плавающей точкой: 2.5е34, -0.345е-25, 37.2Е+4 ; можно писать строчную или прописную латинскую букву Е ; пробелы и скобки недопустимы.

В конце действительной константы можно поставить букву F или f , тогда константа будет сохраняться в формате типа float (см. ниже): 3.5f, -45.67F, 4.7e-5f . Можно приписать и букву D (или d ): 0.045D, -456.77889d , означающую тип double , но это излишне, поскольку действительные константы и так хранятся в формате типа double .


Символы

Для записи одиночных символов используются следующие формы.

  • Печатные символы можно записать в апострофах: ' а ', ' N ', ' ? '.
  • Управляющие символы записываются в апострофах с обратной наклонной чертой:
    • ' \n ' — символ перевода строки newline с кодом ASCII 10;
    • ' \r ' — символ возврата каретки CR с кодом 13;
    • ' \f ' — символ перевода страницы FF с кодом 12;
    • ' \b ' — символ возврата на шаг BS с кодом 8;
    • ' \t ' — символ горизонтальной табуляции НТ с кодом 9;
    • ' \\ ' — обратная наклонная черта;
    • ' \" ' — кавычка;
    • ' \' ' — апостроф.
  • Код любого символа с десятичной кодировкой от 0 до 255 можно задать, записав его не более чем тремя цифрами в восьмеричной системе счисления в апострофах после обратной наклонной черты: ' \123 ' — буква S , ' \346 ' — букваЖ в кодировке СР1251. Не рекомендуется использовать эту форму записи для печатных и управляющих символов, перечисленных в предыдущем пункте, поскольку компилятор сразу же переведет восьмеричную запись в указанную выше форму. Наибольший код ' \377 ' — десятичное число 255.
  • Код любого символа в кодировке Unicode набирается в апострофах после обратной наклонной черты и латинской буквы ц ровно четырьмя шестнад-цатеричными цифрами: ' \u0053 ' — буква S , ' \u0416 ' — буква Ж .

Символы хранятся в формате типа char (см. ниже).

Примечание

Прописные русские буквы в кодировке Unicode занимают диапазон от ' \u0410 ' — заглавная буква А , до ' \u042F ' — заглавная Я , строчные буквы от ' \u0430 ' — а , до ' \044F ' — я .

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

Компилятор и исполняющая система Java работают только с кодировкой Unicode.


Строки

Строки символов заключаются в кавычки. Управляющие символы и коды записываются в строках точно так же, с обратной наклонной чертой, но, разумеется, без апострофов, и оказывают то же действие. Строки могут располагаться только на одной строке исходного кода, нельзя открывающую кавычку поставить на одной строке, а закрывающую — на следующей.

Вот некоторые примеры:

"Это строка\nс переносом"

"\"Спартак\" — Чемпион!"


Логический тип

Значения логического типа boolean возникают в результате различных сравнений, вроде 2 > з, и используются, главным образом, в условных операторах и операторах циклов. Логических значении всего два: true (истина) и false (ложь). Это служебные слова Java. Описание переменных этого типа выглядит так:

boolean b = true, bb = false, bool2;

Над логическими данными можно выполнять операции присваивания, например, bool2 = true , в том числе и составные с логическими операциями; сравнение на равенство b == bb и на неравенство b != bb , а также логические операции.


Логические операции

Логические операции:

  • отрицание (NOT) ! (обозначается восклицательным знаком); 
  • конъюнкция (AND) & (амперсанд);
  • дизъюнкция (OR) | (вертикальная черта); 
  • исключающее ИЛИ (XOR) ^ (каре).

Они выполняются над логическими данными, их результатом будет тоже логическое значение true или false . Про них можно ничего не знать, кроме того, что представлено в табл. 1.1.


Целые типы

Спецификация языка Java, JLS, определяет разрядность (количество байтов, выделяемых для хранения значений типа в оперативной памяти) и диапазон значений каждого типа. Для целых типов они приведены в табл.

Тип

Разрядность (байт)

  Диапазон

byte

1

от -128 до 127

short

2

от -32768 до 32767

int

4

от -2147483648 до 2147483647

long

8

от -9223372036854775808 до 9223372036854775807

char

2

от '\u0000' до '\uFFFF' , в десятичной форме от 0 до 65535

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

Хотя тип char занимает два байта, в арифметических вычислениях он участвует как тип int , ему выделяется 4 байта, два старших байта заполняются нулями.

Примеры определения переменных целых типов:

byte b1 = 50, b2 = -99, bЗ;

short det = 0, ind = 1;

int i = -100, j = 100, k = 9999;

long big = 50, veryBig = 2147483648L;

char c1 = 'A', c2 = '?', newLine = '\n';


Арифметические операции

К арифметическим операциям относятся:

  • сложение + (плюс);
  • вычитание - (дефис);
  • умножение * (звездочка);
  • деление / (наклонная черта — слэш);
  • взятие остатка от деления (деление по модулю) % (процент);
  • инкремент (увеличение на единицу) ++ ;
  • декремент (уменьшение на единицу) --

Между сдвоенными плюсами и минусами нельзя оставлять пробелы. Сложение, вычитание и умножение целых значений выполняются как обычно, а вот деление целых значений в результате дает опять целое (так называемое "целое деление"), например, 5/2 даст в результате 2 , а не 2.5 , а 5/(-3) даст -1 . Дробная часть попросту отбрасывается, происходит усечение частного. Это поначалу обескураживает, но потом оказывается удобным для усечения чисел.

Замечание

В Java принято целочисленное деление.

Это странное для математики правило естественно для программирования: если оба операнда имеют один и тот же тип, то и результат имеет тот же тип. Достаточно написать 5/2.0 или 5.0/2 или 5.0/2.0 и получим 2.5 как результат деления вещественных чисел.

Операция деление по модулю определяется так: а % b = а - (а / b) * b ; например, 5%2 даст в результате 1 , а 5% (-3) даст, 2 , т.к. 5 = (-3) * (-1) + 2 , но (-5)%3 даст -2 , поскольку -5 = 3 * (-1) - 2 .

Операции инкремент и декремент означают увеличение или уменьшение значения переменной на единицу и применяются только к переменным, но не к константам или выражениям, нельзя написать 5++ или (а + b)++ .


Приведение типов

Результат арифметической операции имеет тип int, кроме того случая, когда один из операндов типа long . В этом случае результат будет типа long .

Перед выполнением арифметической операции всегда происходит повышение (promotion) типов byte , short , char . Они преобразуются в тип int , а может быть, и в тип long , если другой операнд типа long . Операнд типа int повышается до типа long , если другой операнд типа long . Конечно, числовое значение операнда при этом не меняется.

Это правило приводит иногда к неожиданным результатам. Попытка откомпилировать простую программу, представленную в листинге , приведет к сообщениям компилятора

class InvalidDef{

public static void main (String [] args) { 

byte b1 = 50, b2 = -99;

short k = b1 + b2; // Неверно! '

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

}

}

Эти сообщения означают, что в файле InvalidDef.java, в строке 4, обнаружена возможная потеря точности (possible loss of precision). В таких случаях следует выполнить явное приведение типа. В данном случае это будет сужение (narrowing) типа int до типа short . Оно осуществляется операцией явного приведения, которая записывается перед приводимым значением в виде имени типа в скобках. Определение

short k = (short)(b1 + b2) ;

будет верным.

Сужение осуществляется просто отбрасыванием старших битов, что необходимо учитывать для больших значений. Например, определение

byte b = (byte) 300;

даст переменной b значение 44 . Действительно, в двоичном представлении числа 300 , равном 100101100 , отбрасывается старший бит и получается 00101100 .

Таким же образом можно произвести и явное расширение (widening) типа, если в этом есть необходимость. Если результат целой операции выходит за диапазон своего типа int или long , то автоматически происходит приведение по модулю, равному длине этого диапазона, и вычисления продолжаются, переполнение никак не отмечается.

Замечание

В языке Java нет целочисленного переполнения.


Операции сравнения

В языке Java шесть обычных операций сравнения целых чисел по величине: 

  • больше > ; 
  • меньше < ;
  • больше или равно >= ; 
  • меньше или равно <= ; 
  • равно == ; 
  • не равно != .

Сдвоенные символы записываются без пробелов, их нельзя переставлять местами, запись => будет неверной.

Результат сравнения — логическое значение: true , в результате, например, сравнения 3 != 5 ; или false , например, в результате сравнения 3 == 5 .

Для записи сложных сравнений следует привлекать логические.операции. Например, в вычислениях часто приходится делать проверки вида а < х < b Подобная запись на языке Java приведет к сообщению об ошибке, поскольку первое сравнение, а < х , даст true или false , a Java не знает, больше это, чем b , или меньше. В данном случае следует написать выражение (а < х) && (х < b).


Побитовые операции

Иногда приходится изменять значения отдельных битов в целых данных. Это выполняется с помощью побитовых (bitwise) операций путем наложения маски. В языке Java есть четыре побитовые операции:

  • дополнение (complement) ~ (тильда); 
  • побитовая конъюнкция (bitwise AND) & ; 
  • побитовая дизъюнкция (bitwise OR) | ; 
  • побитовое исключающее ИЛИ (bitwise XOR) ^ .

Сдвиги

В языке Java есть три операции сдвига двоичных разрядов: 

  • сдвиг влево <<; 
  • сдвиг вправо >>; 
  • беззнаковый сдвиг вправо >>>.

Вещественные типы

Вещественных типов в Java два: float и double. Они характеризуются разрядностью, диапазоном значений и точностью представления, отвечающим стандарту IEEE 754-1985 с некоторыми изменениями. К обычным вещественным числам добавляются еще три значения»

1. Положительная бесконечность, выражаемая константой POSITIVE_INFINITY и возникающая при переполнении положительного значения, например, в результате операции умножения 3.0*6е307.

2. Отрицательная бесконечность NEGATIVE_INFINITY .

3. "Не число", записываемое константой NaN (Not a Number) и возникающее при делении вещественного числа на нуль или умножении нуля на бесконечность.

Кроме того, стандарт различает положительный и отрицательный нуль, возникающий при делении на бесконечность соответствующего знака, хотя сравнение 0.0 == -0.0 дает true.

Операции с бесконечностями выполняются по обычным математическим правилам.

Во всем остальном вещественные типы — это обычные, вещественные значения, к которым применимы все арифметические операции и сравнения, перечисленные для целых типов. Характеристики вещественных типов приведены в табл.

В языке Java взятие остатка*от деления %, инкремент ++ и декремент — применяются и к вещественным типам.

Тип

Разрядность

Диапазон

Точность

float

4

3,4е-38 < |х| < 3,4е38

7—8 цифр

double

8

1,7е-308<|х|<1,7е308

17 цифр

Примеры определения вещественных типов:

float х = 0.001, у = -34.789; 

double 21 = -16.2305, z2;


Операции присваивания

Простоя операция присваивания (simple assignment operator) записывается знаком равенства =, слева от которого стоит переменная, а справа выражение, совместимое с типом переменной:

х = 3.5, у = 2 * (х - 0.567) / (х + 2), b = х < у, bb = х >= у && b.

Операция присваивания действует так: выражение, стоящее после знака равенства, вычисляется и приводится к типу переменной, стоящей слева от знака равенства. Результатом операции будет приведенное значение правой части.

Операция присваивания имеет еще одно, побочное, действие: переменная, стоящая слева, получает приведенное значение правой части, старое ее значение теряется.

В операции присваивания левая и правая части неравноправны, нельзя написать 3.5 = х. После операции х = у изменится переменная х, став равной у, а после у = х изменится у.

Кроме простой операции присваивания есть еще 11 составных операций присваивания (compound assignment operators):

+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= ; >>>=.

Символы записываются без пробелов, нельзя переставлять их местами.

Все составные операции присваивания действуют по одной схеме:

х ор= а э квивалентно х = (тип х), т. е. (х ор а).

Напомним, что переменная ind типа short определена у нас со значением 1. Присваивание ind +=7.8 даст в результате число 8, то же значение получит и переменная ind. Эта операция эквивалентна простой операции присваивания ind = (short)(ind + 7.8).

Перед присваиванием, при необходимости, автоматически производится приведение типа. Поэтому:

byte b = 1;

b = b + 10; // Ошибка!


Условная операция

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

х < 0 ? 0 : х 

х > у ? х  — у : х + у

Условная операция выполняется так. Сначала вычисляется логическое выражение. Если получилось значение true, то вычисляется первое выражение после вопросительного знака ? и его значение будет результатом всей операции. Последнее выражение при этом не вычисляется. Если же получилось значение false, то вычисляется только последнее выражение, его значение будет результатом операции.

Это позволяет написать n == 0 ? да : m / n не опасаясь деления на нуль. Условная операция поначалу кажется странной, но она очень удобна для записи небольших разветвлений.

 

b += 10; // Правильно!

Перед сложением ь + 50 происходит повышение ь до типа int, результат сложения тоже будет типа int и, в первом случае, не может быть Присвоен переменной ь без явного приведения типа. Во втором случае перед присваиванием произойдет сужение результата сложения до типа byte.


Приоритет операций

Операции перечислены в порядке убывания приоритета. Операции на одной строке имеют одинаковый приоритет.

1. Постфиксные операции ++ и —.

2. Префиксные операции ++ и —, дополнение ~ и отрицание !.

3. Приведение типа (тип).

4. Умножение *, деление / и взятие остатка %.

5. Сложение + и вычитание -.

6. Сдвиги <<, >>, >>>.

7. Сравнения >, <, >=, <=.

8. Сравнения ==, !=.

9. Побитовая конъюнкция &.

10. Побитовое исключающее ИЛИ ^.

11. Побитовая дизъюнкция | .

12. Конъюнкция &&.

13. Дизъюнкция | | .

14. Условная операция ?: .

15. Присваивания =, +=, -=, *=, /=, %=, &=, ^=, |=, <<, >>, >>>.

Здесь перечислены не все операции языка Java


Оператор if

Этот оператор проверяет равно ли действие true. Если да то действие выполняется. Если нет то идёт следующая команда. Пример:

class TEST{ public static void main(String[] args){ if(true){ System.out.println("Тест прошёл успешно."); } } }

Вывод в консоли: Тест прошёл успешно. Как видите этот оператор сработал успешно так как в нём почти ничего не проверялось.


Оператор else

Действия написанные в операторе else будут выполнены только если значение равно false. Пример:

class TEST1{ public static void main(String[] args){ if(false){ System.out.println("Тест прошёл не очень успешно.");} else{ System.out.println("Тест прошёл успешно.");} } }

Вывод в консоли:Тест прошёл успешно.


Оператор switch

Данный способ использует много вариантов сразу. Пример:

class TEST2 { public static void main(String[] args) { int i=2; switch(i) { case 1:System.out.println("Тест прошёл не очень успешно."); case 2:System.out.println("Тест прошёл успешно."); case 3:System.out.println("Тест прошёл не очень успешно."); default:System.out.println("Тест прошёл ужасно"); } } }

Внимание: С оператором switch надо быть очень осторожным так как неправильно написанный код может вывести "Красную взбучку"

Вывод в консоли: Тест прошёл успешно.


Оператор for

Конструкция оператора for:
for (Начальное значение переменной; Логическое выражение с переменной(условие выполнения цикла); Действие над переменной, вызываемое при выполнении условия) {
Операторы, которые будет выполнять цикл при условии ЛогВыр - true;
}
Где chislo - переменная, объявленная в цикле (Её нужно обязательно объявлять в цикле), ЛогВыр - логическое выражение. Если оно равно true, цикл выполняется до тех пор, пока не станет false. Если вместо этого, вставить true, цикл будет выполняться вечно, если false, цикл не будет выполнен ни разу. Действие при выполнение условия - что будет предпринимать цикл, при выполнение оператора. В основном, это увеличение локальной перемененной chislo с помощью инкремента. Пример использования оператора:

public class Цикл { public static void main (String args []){ for (int i = 0; i < 10; ++i){ System.out.print ("Ku-Ku ");//Обратите внимание на метод print //В отличие от println метод print не переводит курсор на новую строку } } }

Данный цикл выведет Ku-Ku Ku-Ku Ku-Ku Ku-Ku Ku-Ku Ku-Ku Ku-Ku Ku-Ku Ku-Ku Ku-Ku


Оператор while

Конструкция оператора while:
while (Логическое выражение) {Тело цикла;}
Где выражение в скобках определяет условие, пока(while) выполняется которое, будет выполнятся выражение в фигурных скобках.

public class Цикл { public static void main (String args []){ int i = 0; while (i < 10){ System.out.print (i++); } } }

Данный цикл выведет 0123456789.
Стоит обратить внимание, что число 10 выведено не будет, так как while это оператор предусловия.


Оператор do...while

Конструкция оператора do while:
do {Тело цикла;} while (условие выполения)
Отличие данного оператора от while только в том, что он является оператором постусловия (сначала выполнит, потом проверит).
Т.е. даже если условие не выполняется никогда, 1 раз действие выполнено будет.

public class Цикл { public static void main (String args []){ int i = 0; do{ System.out.print (i++); } while (i < 10); } }

Данный цикл выведет 0123456789.


Оператор continue и метки

Оператор continue используется только в операторах цикла. Он имеет две формы. Первая форма состоит только из слова continue и осуществляет немедленный переход к следующей итерации цикла. В очередном фрагменте кода оператор continue позволяет обойти деление на нуль:

for (int i = 0; i < N; i++){

if (i '== j) continue;

s += 1.0 / (i - j); 

}

Вторая форма содержит метку:

continue метка

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


Оператор break

Оператор break используется в операторах цикла и операторе варианта для немедленного выхода из этих конструкций.

Оператор break метка

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

Ml: { // Внешний блок

М2: { // Вложенный блок — второй уровень 

М3: { // Третий уровень вложенности... 

if (что-то случилось) break M2; 

// Если true, то здесь ничего не выполняется  

// Здесь тоже ничего не выполняется

}

// Сюда передается управление

}


Массивы

Массивы в языке Java относятся к ссылочным типам и описываются своеобразно, но характерно для ссылочных типов. Описание производится в три этапа.

Первый этап — объявление (declaration). На этом этапе определяется только переменная типа ссылка (reference) на массив, содержащая тип массива. Для этого записывается имя типа элементов массива, квадратными скобками указывается, что объявляется ссылка на массив, а не простая переменная, и перечисляются имена переменных типа ссылка, например,

double[] а, b;

Здесь определены две переменные — ссылки а и ь на массивы типа double. Можно поставить квадратные скобки и непосредственно после имени. Это удобно делать среди определений обычных переменных:

int I = 0, ar[], k = -1;

Здесь определены две переменные целого типа i и k, и объявлена ссылка на целочисленный массив аг.

Второй этап — определение (installation). На этом этапе указывается количество элементов массива, называемое его длиной, выделяется место для массива в оперативной памяти, переменная-ссылка получает адрес массива. Все эти действия производятся еще одной операцией языка Java — операцией new тип, выделяющей участок в оперативной памяти для объекта указанного в операции типа и возвращающей в качестве результата адрес этого участка. Например,

а = new double[5]; 

b = new double[100]; 

ar = new int[50];

Индексы массивов всегда начинаются с 0. Массив а состоит из пяти переменных а[0], а[1], , а[4]. Элемента а[5] в массиве нет. Индексы можно задавать любыми целочисленными выражениями, кроме типа long, например, a[i+j], a[i%5], a[++i]. Исполняющая система Java следит за тем, чтобы значения этих выражений не выходили за границы длины массива.

Третий этап — инициализация (initialization). На этом этапе элементы массива получают начальные значения. Например,

а[0] = 0.01; а[1] = -3.4; а[2] = 2:.89; а[3] = 4.5; а[4] = -6.7;

for (int i = 0; i < 100; i++) b[i] = 1.0 /i;

for (int i = 0; i < 50; i++) ar[i] = 2 * i + 1;

Первые два этапа можно совместить:

doublet] a = new double[5], b = new double[100]; 

int i = 0, ar[] = new int[50], k = -1;

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

double[] а = {0.01, -3.4, 2.89, 4.5, -6.7};

Можно совместить второй и третий этап:

а = new doublet] {0.1, 0.2, -0.3, 0.45, -0.02};

Можно даже создать безымянный массив, сразу же используя результат операции new, например, так:

System.out.println(new char[] {'H', 'e', '1', '1', 'o'});

Ссылка на массив не является частью описанного массива, ее можно перебросить на другой массив того же типа операцией присваивания. Например, после присваивания а = ь обе ссылки а и ь указывают на один и тот же массив из 100 вещественных переменных типа double и содержат один и тот же адрес.

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

ar = null;

После этого массив, на который указывала данная ссылка, теряется, если на него не было других ссылок.


Многомерные массивы

Элементами массивов в Java могут быть снова массивы. Можно объявить:

char[] [] с;

что эквивалентно

char с[] с[];

или

char с[][];

Затем определяем внешний массив:

с = new char[3][];

Становится ясно, что с — массив, состоящий из трех элементов-массивов. Теперь определяем его элементы-массивы:

с[0] = new char[2];

с[1] = new char[4];

с[2] = new char[3];

После этих определений переменная с.length равна з, с[0] .length равна 2,

c[l].length равна 4 и с[2.length равна 3.

Наконец, задаем начальные значения с [0] [0] = 'a', с[0][1] = 'r',

с[1][0] = 'г',с[1][1] = 'а',с[1][2] = 'у' и т.д.


Класс

Класс есть ключевое понятие в объектно-ориентированном программировании, под которое и заточена Java. Класс описывает содержание и поведение некой совокупности данных и действий над этими данными. Объявление класса производится с помощью ключевого слова class. Пример: class < имя_класса > {// содержимое класса}.

К примеру, если мы моделируем прямоугольную комнату классом Комната, то данными могут быть длина, ширина и высота, двери, электророзетки, мебель. Заметим, что на уровне класса мы ещё не знаем, о которой комнате идет речь, но точно знаем, что это не ящик (который тоже имеет длину, высоту и ширину), а именно комната. Действиями могут быть вычисление объема, помещение и изъятие мебели, открытие дверей. Чтобы вычислить объем комнаты или наклеить обои, нам не нужны ее размеры, о своих размерах каждая конкретная комната знает сама.


Наследование

Классы могут наследовать методы и данные один другого. Наследование реализуется с помощью ключевого слова extends (class <имя_класса> extends <имя_суперкласса>). Если существуют ящик и комната, объем которых вычисляется перемножением трех параметров, то можно определить материнский класс для двух вышеперечисленных классов, чтобы в нем определить вычисление объема, а наследники будут только пользоваться унаследованным свойством, а не переписывать его несколько раз. В то же время при желании любой из наследников может перегрузить унаследованное свойство. Так, например, если в комнате находится какой-то предмет и объем комнаты не должен включать объема этого предмета, то функция вычисления объема уже не будет одинаковой для ящика и комнаты.


Интерфейс

Interface описывает предполагаемое поведение класса, не упоминая конкретных действий. Создаётся интерфейс с помощью ключевого слова interface ( interface <имя_интерфейса>. Для того чтобы унаследовать (реализовать) классом интерфейс, используется ключевое слово implements (class <имя_класса> implements <имя_интерфейса>).А между собой интерфейсы унаследуются всё тем же словом extends.


Как описать класс и подкласс

Итак, описание класса начинается со слова class, после которого записывается имя класса. Соглашения "Code Conventions" рекомендуют начинать имя класса с заглавной буквы.

Перед словом class можно записать модификаторы класса (class modifiers). Это одно из слов public, abstract, final, strictfp . Перед именем вложенного класса можно поставить, кроме того, модификаторы protected, private, static . Модификаторы мы будем вводить по мере изучения языка.

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

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

Описание поля может начинаться с одного или нескольких необязательных модификаторов public, protected, private, static, final, transient, volatile . Если надо поставить несколько модификаторов, то перечислять их JLS рекомендует в указанном порядке, поскольку некоторые компиляторы требуют определенного порядка записи модификаторов. С модификаторами мы будем знакомиться по мере необходимости.

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

Описание метода может начинаться с модификаторов public, protected, private, abstract, static, final, synchronized, native, strictfp . Мы будем вводить их по необходимости.

В списке параметров через запятую перечисляются тип и имя каждого параметра. Перед типом какого-либо параметра может стоять модификатор final . Такой параметр нельзя изменять внутри метода. Список параметров может отсутствовать, но скобки сохраняются.

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

В листинге показано, как можно оформить метод деления пополам для нахождения корня нелинейного уравнения из листинга

class Bisection2{

private static double final EPS = le-8; // Константа 

private double a = 0.0, b = 1.5, root;  // Закрытые поля

public double getRoot(}{return root;}   // Метод доступа

private double f(double x)

{

return x*x*x — 3*x*x + 3;   // Или что-то другое 

}

private void bisect(){      // Параметров нет —

                            // метод работает с полями экземпляра

double у = 0.0;             // Локальная переменная — не поле 

do{

root = 0.5 *(а + b); у = f(root);

if (Math.abs(y) < EPS) break;

// Корень найден. Выходим из цикла

// Если на концах отрезка [a; root] 

// функция имеет разные знаки: 

if (f(а) * у < 0.0} b = root;

      // значит, корень здесь

      // Переносим точку b в точку root

      //В противном случае: 

else a = root;

      // переносим точку а в точку root

      // Продолжаем, пока [а; Ь] не станет мал 

} while(Math.abs(b-a) >= EPS); 

}

public static void main(String[] args){ 

Bisection2 b2 = new Bisection2(); 

b2.bisect(); 

System.out.println("x = " +

b2.getRoot() +    // Обращаемся к корню через метод доступа 

", f() = " +b2.f(b2.getRoot())); 

}

В описании метода f() сохранен старый, процедурный стиль: метод получает аргумент, обрабатывает его и возвращает результат. Описание метода bisect о выполнено в духе ООП: метод активен, он сам обращается к полям экземпляра b2 и сам заносит результат в нужное поле. Метод bisect () — это внутренний механизм класса Bisection2, поэтому он закрыт (private).

Имя метода, число и типы параметров образуют сигнатуру (signature) метода. Компилятор различает методы не по их именам, а по сигнатурам. Это позволяет записывать разные методы с одинаковыми именами, различающиеся числом и/или типами параметров.


Окончательные члены и классы

Пометив метод модификатором final , можно запретить его переопределение в подклассах. Это удобно в целях безопасности. Вы можете быть уверены, что метод выполняет те действия, которые вы задали. Именно так определены математические функции sin(), cos() и прочие в классе Math . Мы уверены, что метод Math.cos (x) вычисляет именно косинус числа х . Разумеется, такой метод не может быть абстрактным.

Для полной безопасности, поля, обрабатываемые окончательными методами, следует сделать закрытыми (private).

Если же пометить модификатором final весь класс, то его вообще нельзя будет расширить. Так определен, например, класс Math :

public final class Math{ . . . }

Для переменных модификатор final имеет совершенно другой смысл. Если пометить модификатором final описание переменной, то ее значение (а оно должно быть обязательно задано или здесь же, или в блоке инициализации или в конструкторе) нельзя изменить ни в подклассах, ни в самом классе. Переменная превращается в константу. Именно так в языке Java определяются константы:

public final int MIN_VALUE = -1, MAX_VALUE = 9999;

По соглашению "Code Conventions" константы записываются прописными буквами, слова в них разделяются знаком подчеркивания.

На самой вершине иерархии классов Java стоит класс Object .


Класс Object

Если при описании класса мы не указываем никакое расширение, т. е. не пишем слово extends и имя класса за ним, как при описании класса Pet , то Java считает этот класс расширением класса object , и компилятор дописывает это за нас:

class Pet extends Object{ . . . }

Можно записать это расширение и явно.

Сам же класс object не является ничьим наследником, от него начинается иерархия любых классов Java. В частности, все массивы — прямые наследники класса object .

Поскольку такой класс может содержать только общие свойства всех классов, в него включено лишь несколько самых общих методов, например, метод equals() , сравнивающий данный объект на равенство с объектом, заданным в аргументе, и возвращающий логическое значение. Его можно использовать так:

Object objl = new Dog(), obj 2 = new Cat(); 

if (obj1.equals(obj2)) ...


Оцените объектно-ориентированный дух этой записи: объект obj1 активен, он сам сравнивает себя с другим объектом. Можно, конечно, записать и obj2.equals (obj1) , сделав активным объект obj2 , с тем же результатом.


Конструкторы класса

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

Такой "метод" называется конструктором класса (class constructor). Его своет образие заключается не только в имени. Перечислим особенности конструктора.

  • Конструктор имеется в любом классе. Даже если вы его не написали, компилятор Java сам создаст конструктор по умолчанию (default constructor), который, впрочем, пуст, он не делает ничего, кроме вызова конструктора суперкласса.
  • Конструктор выполняется автоматически при создании экземпляра класса, после распределения памяти и обнуления полей, но до начала использования создаваемого объекта.
  • Конструктор не возвращает никакого значения. Поэтому в его описании не пишется даже слово void , но можно задать один из трех модификаторов public , protected или private .
  • Конструктор не является методом, он даже не считается членом класса. Поэтому его нельзя наследовать или переопределить в подклассе.
  • Тело конструктора может начинаться:
    • с вызова одного из конструкторов суперкласса, для этого записывается слово super() с параметрами в скобках, если они нужны;
    • с вызова другого конструктора того же класса, для этого записывается слово this() с параметрами в скобках, если они нужны.

    Если же super() в начале конструктора не указан, то вначале выполняется конструктор суперкласса без аргументов, затем происходит инициализация полей значениями, указанными при их объявлении, а уж потом то, что записано в конструкторе.

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

В классе может быть несколько конструкторов. Поскольку у них одно и то же имя, совпадающее с именем класса, то они должны отличаться типом и/или количеством параметров.

В наших примерах мы ни разу не рассматривали конструкторы классов, поэтому при создании экземпляров наших классов вызывался конструктор класса object .


Операция new

Пора подробнее описать операцию с одним операндом, обозначаемую словом new . Она применяется для выделения памяти массивам и объектам.

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

double a[] = new double[100];

Во втором случае операндом служит конструктор класса. Если конструктора в классе нет, то вызывается конструктор по умолчанию.

Числовые поля класса получают нулевые значения, логические поля — значение false , ссылки — значение null .

Результатом операции new будет ссылка на созданный объект. Эта ссылка может быть присвоена переменной типа ссылка на данный тип:

Dog k9 = new Dog () ;

но может использоваться и непосредственно

new Dog().voice();

Здесь после создания безымянного объекта сразу выполняется его метод voice() . Такая странная запись встречается в программах, написанных на Java, на каждом шагу.


Статические члены класса

Разные экземпляры одного класса имеют совершенно независимые друг от друга поля-, принимающие разные значения. Изменение поля в одном экземпляре никак не влияет на то же поле в другом экземпляре. В каждом экземпляре для таких полей выделяется своя ячейка памяти. Поэтому такие поля называются переменными экземпляра класса (instance variables) или переменными объекта.

Иногда надо определить поле, общее для всего класса, изменение которого в одном экземпляре повлечет изменение того же поля во всех экземплярах. Например, мы хотим в классе Automobile отмечать порядковый заводской номер автомобиля. Такие поля называются переменными класса (class variables). Для переменных класса выделяется только одна ячейка памяти, общая для всех экземпляров. Переменные класса образуются в Java модификатором static . В листинге мы записываем этот модификатор при определении переменной number .

class Automobile {

   private static int number; 

   Automobile(){ 

      number++;

      System.out.println("From Automobile constructor:"+ 

                         " number = "+number);

   }

}

public class AutomobiieTest{

   public static void main(String[] args){

   Automobile lada2105    = new Automobile(), 

              fordScorpio = new Automobile(),

              oka         = new Automobile!);

   } 

}


Класс Complex

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

class Complex {

   private static final double EPS = le-12; // Точность вычислений 

   private double re, im;                   // Действительная и мнимая часть

                                            // Четыре конструктора 

   Complex(double re, double im) { 

      this, re = re; this.im = im;

   }

   Complex(double re){this(re, 0.0); }

   Complex(){this(0.0, 0.0); }

   Complex(Complex z){this(z.re, z.im) ; }

      // Методы доступа 

   public double getRe(){return re;} 

   public double getlmf){return im;} 

   public Complex getZ(){return new Complex(re, im);} 

   public void setRe(double re){this.re = re;} 

   public void setlm(double im){this.im = im;} 

   public void setZ(Complex z){re = z.re; im = z.im;}

      // Модуль и аргумент комплексного числа

   public double mod(){return Math.sqrt(re * re + im * im);} 

   public double arg()(return Math.atan2(re, im);}

      // Проверка: действительное число? 

   public boolean isReal(){return Math.abs(im) < EPS;}

   public void pr(){    // Вывод на экран

      System.out.println(re + (im < 0.0 ? "" : '"+") + im + "i");

   }

      // Переопределение методов класса Object

   public boolean equals(Complex z){ 

      return Math.abs(re -'z.re) < EPS && 

             Math.abs(im - z.im) < EPS;

   }

   public String toString(){

      return "Complex: " + re + " " + im;

   }

      // Методы, реализующие операции +=, -=, *=, /= 

   public void add(Complex z){re += z.re; im += z.im;} 

   public void sub(Complex z){re -= z.re; im —= z.im;} 

   public void mul(Complex z){

      double t = re * z.re — im * z. im; 

            im = re * z.im + im * z.re; 

            re = t;

   }

   public void div(Complex z){

      double m = z.mod();

      double t = re * z.re — im * z.im;

      im = (im * z.re — re * z.im) / m;

      re = t / m; 

   }

      // Методы, реализующие операции +, -, *, / 

   public Complex plus(Complex z){

      return new Complex(re + z.re, im + z im);

   } 

   public Complex minus(Complex z){

      return new Complex(re - z.re, im - z.im); 

   }

   public Complex asterisk(Complex z){ 

      return new Complex(

         re * z.re - im * z.im, re * z.im + im * z re);

   }

public Complex slash(Complex z){ 

   double m = z.mod(); 

   return new Complex(

      (re * z.re - im * z.im) / m, (im * z.re - re * z.im) / m);

   }

}

   // Проверим работу класса Complex 

public class ComplexTest{

   public static void main(Stringf] args){ 

      Complex zl = new Complex(),

              z2 = new Complex(1.5), 

              z3 = new Complex(3.6, -2.2), 

              z4 = new Complex(z3);

      System.out.printlnf);       // Оставляем пустую строку 

      System.out.print("zl = "); zl.pr(); 

      System.out.print("z2 = "); z2.pr(); 

      System.out.print("z3 = "); z3.pr(); 

      System.out.print ("z4 = "}; z4.pr(); 

      System.out.println(z4);     // Работает метод toString()

      z2.add(z3);

      System.out.print("z2 + z3 = "}; z2.pr();

      z2.div(z3);

      System.out.print("z2 / z3 = "); z2.pr(); 

      z2 = z2.plus(z2);

      System.out.print("z2 + z2 = "); z2.pr(); 

      z3 = z2.slash(zl);

      System.out.print("z2 / zl = "); z3.pr(); 

   } 

}


Метод main()

Всякая программа, оформленная как приложение (application), должна содержать метод с именем main . Он может быть один на все приложение или содержаться в некоторых классах этого приложения, а может находиться и в каждом классе.

Метод main() записывается как обычный метод, может содержать любые описания и действия, но он обязательно должен быть открытым ( public ), статическим ( static ), не иметь возвращаемого значения ( void ). Его аргументом обязательно должен быть массив строк ( string[] ). По традиции этот массив называют args , хотя имя может быть любым.

Эти особенности возникают из-за того, что метод main() вызывается автоматически исполняющей системой Java в самом начале выполнения приложения. При вызове интерпретатора java указывается класс, где записан метод main() , с которого надо начать выполнение. Поскольку классов с методом main() может быть несколько, можно построить приложение с дополнительными точками входа, начиная выполнение приложения в разных ситуациях из различных классов.

Часто метод main() заносят в каждый класс с целью отладки. В этом случае в метод main() включают тесты для проверки работы всех методов класса.

При вызове интерпретатора java можно передать в метод main() несколько параметров, которые интерпретатор заносит в массив строк. Эти параметры перечисляются в строке вызова java через пробел сразу после имени класса. Если же параметр содержит пробелы, надо заключить его в кавычки. Кавычки не будут включены в параметр, это только ограничители.


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

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

В каждом из этих классов есть статические константы MAX_VALUE и MIN_VALUE , показывающие диапазон числовых значений соответствующих примитивных типов. В классах Double и Float есть еще константы POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN , о которых шла речь в главе 1, и логические методы проверки isNan() , isInfinite() .

Если вы хорошо знаете двоичное представление вещественных чисел, то можете воспользоваться статическими методами floatTointBits() и doubieToLongBits() , преобразующими вещественное значение в целое. Вещественное число задается как аргумент метода. Затем вы можете изменить отдельные биты побитными операциями и преобразовать измененное целое число обратно в вещественное значение методами intsitsToFioat() и longBitsToDouble() .

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

class NumberTest{

  public static void main(String[] args){ 

    int i = 0; 

    short sh = 0;

    double d = 0; 

    Integer kl = new Integer(55); 

    Integer k2 = new Integer(100); 

    Double dl = new Double(3.14); 

    try{

      i = Integer.parselnt(args[0]);

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

      d = Double.parseDouble(args[1]);

     dl = new Double(args[1]);

     kl = new Integer(args[0]); 

    }catch(Exception e){} 

    double x = 1.0/0.0; 

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

    System.outjprintln("sh - " + sh) ; 

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

    System.out.println("kl.intValue() = " + kl.intValue()); 

    System.out.println("dl.intValue() '= "'+ dl.intValuei)); 

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

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

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

    System.out.println("x islnfinite? " + Double.islnfinite(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)); 

  } 

}


Класс Boolean

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

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

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


Класс 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(), toTitieCase() возвращают символ, содержащийся в классе, в указанном регистре. Последний из этих методов предназначен для правильного перевода в верхний регистр четырех кодов Unicode, не выражающихся одним символом.

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

  • isDef ined() — выясняет, определен ли символ в кодировке Unicode; 
  • isDigit() — проверяет, является ли символ цифрой Unicode;
  • isidentifierignorable() — выясняет, нельзя ли использовать символ в идентификаторах;
  • isisocontroi() — определяет, является ли символ управляющим;
  • isJavaidentifierPart() — выясняет, можно ли использовать символ в идентификаторах;
  • isjavaidentifierstart() — определяет, может ли символ начинать идентификатор;
  • isLetter() — проверяет, является ли символ буквой Java;
  • IsLetterOrDigit() — Проверяет, является ли символ буквой или цифрой Unicode;
  • isLowerCase() — определяет, записан ли символ в нижнем регистре; 
  • isSpaceChar() — выясняет, является ли символ пробелом в смысле Unicode; 
  • isTitieCase() — проверяет, является ли символ титульным;
  • isUnicodeldentifierPart() — выясняет, можно ли использовать символ в именах Unicode;
  • isunicodeidentifierstart() — проверяет, является ли символ буквой Unicode; 
  • isUpperCase() — проверяет, записан ли символ в верхнем регистре; 
  • isWhitespace() — выясняет, является ли символ пробельным.

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

class CharacterTest{

  public static void main(String[] args){ 

  char ch = '9';

  Character cl = new Character(ch); 

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

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

     c1.charValue()); 

  System.out.println("number of 'A' = " +

     Character.digit('A', 16}}; 

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

     Character.forDigit(12, 16}}; 

  System.out.printlnC'cl = " + cl.toString() ); 

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

     Character.isDefined(ch)); 

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

     Character.isDigit(ch)); 

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

     Character.isldentifierlgnorable(ch)); 

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

     Character.isISOControl(ch)); 

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

     Character.isJavaldentifierPart(ch)); 

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

     Character.isJavaldentifierStart(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 isUnicodeldentifierPart? " +

     Character.isUnicodeldentifierPart(ch)); 

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

     Character.isUnicodeldentifierStart(ch)); 

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

     Character.isUpperCase(ch)); 

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

     Character.isWhitespace(ch)); } }

В класс Character вложены классы Subset и UnicodeBlock , причем класс Unicode и еще один класс, inputSubset , являются расширениями класса Subset.


Класс Biglnteger

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

Для того чтобы было можно производить целочисленные вычисления с любой разрядностью, в состав Java API введен класс Biglnteger , хранящийся в пакете java.math . Этот класс расширяет класс Number , следовательно, в нем переопределены методы doubleValue(), floatValue(), intValue(), longValue() . Методы byteVaiue() и shortvalue() не переопределены, а прямо наследуются от класса Number .

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

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

Шесть конструкторов класса создают объект класса BigDecimai из строки символов (знака числа и цифр) или из массива байтов.

Две константы — ZERO и ONE — моделируют нуль и единицу в операциях с объектами класса Biglnteger .

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

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

  • abs() — возвращает объект, содержащий абсолютное значение числа, хранящегося в данном объекте this ;
  • add(x) — операция this + х ; 
  • and(x) — операция this & х ;
  • andNot(x) — операция this & (~х) ;
  • divide (x) — операция this / х ; 
  • divideAndRemainder(х) — возвращает массив из двух объектов класса Biglnteger , содержащих частное и остаток от деления this на х ;
  • gcd(x) — наибольший общий делитель, абсолютных, значений объекта this и аргумента х ;
  • mах(х) — наибольшее из значений объекта this и аргумента х ; min(x) — наименьшее из значений объекта this и аргумента х ; mod(x) — остаток от деления объекта this на аргумент метода х ;
  • modinverse(x) — остаток от деления числа, обратного объекту this , на аргумент х ;
  • modPow(n, m) — остаток от деления объекта this , возведенного в степень n , на m ;
  • multiply (х) —операция this * х ;
  • negate() — перемена знака числа, хранящегося в объекте;
  • not() — операция ~this ;
  • оr(х) — операция this | х ;
  • pow(n) — операция возведения числа, хранящегося в объекте, в степень n ;
  • remainder(х) —операция this % х ;
  • shiftLeft (n) — операция this « n ;
  • shiftRight (n) — операция this » n;
  • signum() — функция sign (x) ;
  • subtract (x) — операция this - x ;
  • xor(x) — операция this ^ x .

import Java.math.Biglnteger;

class BiglntegerTest{

  public static void main(String[] args){

    Biglnteger a = new Biglnteger("99999999999999999") ;

    Biglnteger b = new Biglnteger("88888888888888888888");

    System.out.println("bits in a = " + a.bitLength());

    System.out.println("bits in b = " + b.bitLengthO);

    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));

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

    System.out.println("a / b: q = " + r[0] + ", r = " + r[l]);

    System.out.println("gcd(a, b) = " + a.gcd(b));

    System.out.println("max(a, b) = " + a.max(b));

    System.out.printin("min(a, b) = " + a.min(b));

    System.out.println("a mod b = " + a.mod(b));

    System.out.println("I/a mod b = " + a.modlnverse(b));

    System.out.println("алп 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("а л 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("а л b = " + a.xor(b)); 

    } 

}


Класс Big Decimal

Класс BigDecimal расположен В пакете java.math .

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

Например, для числа 76.34862 будет храниться мантисса 7 634 862 в объекте класса Biglnteger , и порядок 5 как целое число типа int . Таким образом, мантисса может содержать любое количество цифр, а порядок ограничен значением константы integer.MAX_VALUE . Результат операции над объектами класса BigDecimal округляется по одному из восьми правил, определяемых следующими статическими целыми константами:

  • ROUND_CEILING — округление в сторону большего целого;
  • ROUND_DOWN — округление к нулю, к меньшему по модулю целому значению;
  • ROUND_FLOOR — округление к меньшему целому;
  • ROUND_HALF_DOWN — округление к ближайшему целому, среднее значение округляется к меньшему целому;
  • ROUND_HALF_EVEN — округление к ближайшему целому, среднее значение округляется к четному числу;
  • ROOND_HALF_UP — округление к ближайшему целому, среднее значение округляется к большему целому;
  • ROUND_UNNECESSARY — предполагается, что результат будет целым, и округление не понадобится; 
  • ROUND_UP — округление от нуля, к большему по модулю целому значению.

В классе BigDecimal четыре конструктора:

  • BigDecimal (Biglnteger bi) — объект будет хранить большое целое bi, порядок равен нулю;
  • BigDecimal (Biglnteger mantissa, int scale) — задается мантиса mantissa и неотрицательный порядок scale объекта; если порядок scale отрицателен, возникает исключительная ситуация;
  • BigDecimal (double d) — объект будет содержать вещественное число удвоенной точности d ; если значение d бесконечно или NaN , то возникает исключительная ситуация;
  • BigDecimal (String val) — число задается строкой символов val , которая должна содержать запись числа по правилам языка Java.

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

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

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

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

add(x) — операция this + х ;

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

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

mах(х) — наибольшее из this и х ; 

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

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

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

multiply(х) — операция this * х ; 

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

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

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

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

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

subtract(х) — операция this - х ;

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

unscaiedvalue() —возвращает мантиссу числа.

 

import java.math.*;

class BigDecimalTest{

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

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

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

    BigDecimal z = new BigDecimal(new Biglnteger("123456789"),8);

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

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

    System.out.println("x / у = " + x.divide(y, BigDecimal.ROUND__DOWN));

    System.out.println("х / у = " +

      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.mpvePQintRight(3));

    System.out.println("x * у = " + 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 - у = " + x.subtract(y)}; 

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

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

    System.out.println("mantissa of 0.1 =\n= " +

      new BigDecimal(0.1).unscaledValue()); } }


Класс Class

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

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

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

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

Class с = s.getClass();

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

Class cl = Class.forName("Java,lang.String");

Но этот способ создания объекта класса class считается устаревшим (deprecated). В новых версиях JDK для этой цели используется специальная конструкция — к имени класса через точку добавляется слово class :

Class c2 = Java.lang.String.class;

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

Если объект ссылочного типа, то можно извлечь сведения о вложенных классах, конструкторах, методах и полях методами getoeciaredciasses() , getdeclaredConstructors(), getDeclaredMethods(), getDeclaredFields() , в виде массива классов, соответствейно, Class, Constructor, Method, Field . Последние три класса расположены в пакете java.lang.reflect и содержат сведения о конструкторах, полях и методах аналогично тому, как класс class хранит сведения о классах.

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

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

import java.lang.reflect.*;

class ClassTest{

  public static void main(String[] args)(

    Class с = null, c1 = null, c2 = null;

    Field[] fld = null;

    String s = "Some string";

    с = s.getClass();

    try{

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

      c2 =  Java.lang.String.class;           // Новый стиль 

      if (!c1.isPrimitive())

      fid = cl.getDeclaredFields();           // Все поля класса String

    }catch(Exception e){}

    System.out.println("Class      c: " + c); 

    System.out.println("Class     cl: " + cl); 

    System,out.println("Class     c2: " + c2); 

    System.out.printlnt"Superclass c: " + c.getSuperclass());

    System.out.println("Package    c: " + c.getPackageO); 

    System.out.printlnf"Modifiers  c: " + c.getModifiers()); 

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

      System.out.println(fld[i]);

  }

}

Работа со строками

Очень большое место в обработке информации занимает работа с текстами. Как и многое другое, текстовые строки в языке Java являются объектами. Они представляются экземплярами класса string или класса stringBuffer .

Зачем в язык введены два класса для хранения строк? В объектах класса string хранятся строки-константы неизменной длины. Это значительно ускоряет обработку строк и позволяет экономить память, разделяя строку между объектами, использующими ее. Длину строк, хранящихся в объектах класса stringBuffer , можно менять, вставляя и добавляя строки и символы, удаляя подстроки или сцепляя несколько строк в одну строку. Во многих случаях, когда надо изменить длину строки типа string , компилятор Java неявно преобразует ее к типу stringBuffer , меняет длину, потом преобразует обратно в тип string . Например, следующее действие

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

компилятор выполнит так:

String s = new StringBuffer().append("Это").append(" одна ") 

       .append("строка").toString();

Будет создан объект класса stringBuffer , в него последовательно добавлены строки "Это", " одна ", "строка", и получившийся объект класса StringBuffer будет приведен к типу String методом toString () .

Напомним, что символы в строках хранятся в кодировке Unicode, в которой каждый символ занимает два байта. Тип каждого символа char .


Класс String

Самый простой способ создать строку — это организовать ссылку типа string на строку-константу:

String si = "Это строка.";

Если константа длинная, можно записать ее в нескольких строках текстового редактора, связывая их операцией сцепления:

String s2 = "Это длинная строка, " +

"записанная в двух строках исходного текста";

Замечание

Не забывайте разницу между пустой строкой string s = "" , не содержащей ни одного символа, и пустой ссылкой string s = null, не указывающей ни на какую строку и не являющейся объектом.

Самый правильный способ создать объект с точки зрения ООП — это вызвать его конструктор в операции new. Класс string предоставляет вам девять конструкторов:

  • string() — создается объект с пустой строкой;
  • string (String str) — из одного объекта создается другой, поэтому этот конструктор используется редко;
  • string (StringBuf fer str) — преобразованная коп-ия объекта класса BufferString;
  • string(byte[] byteArray) — объект создается из массива байтов byteArray;
  • String (char [] charArray) — объект создается из массива charArray символов Unicode;
  • String (byte [] byteArray, int offset, int count) — объект создается из части массива байтов byteArray, начинающейся с индекса offset и содержащей count байтов;
  • String (char [] charArray, int offset, int count) — то же, но массив состоит из символов Unicode;
  • String(byte[] byteArray, String encoding) — символы, записанные в массиве байтов, задаются в Unicode-строке, с учетом кодировки encoding ;
  • String(byte[] byteArray, int offset, int count, String encoding) — то же самое, но только для части массива.

При неправильном заданий индексов offset , count или кодировки encoding возникает исключительная ситуация.

Конструкторы, использующие массив байтов byteArray , предназначены для создания Unicode-строки из массива байтовых ASCII-кодировок символов. Такая ситуация возникает при чтении ASCII-файлов, извлечении информации из базы данных или при передаче информации по сети.

В самом простом случае компилятор для получения двухбайтовых символов Unicode добавит к каждому байту старший нулевой байт. Получится диапазон ' \u0000 ' — ' \u00ff ' кодировки Unicode, соответствующий кодам Latin 1. Тексты на кириллице будут выведены неправильно.

Если же на компьютере сделаны местные установки, как говорят на жаргоне "установлена локаль" (locale) (в MS Windows это выполняется утилитой Regional Options в окне Control Panel ), то компилятор, прочитав эти установки, создаст символы Unicode, соответствующие местной кодовой странице. В русифицированном варианте MS Windows это обычно кодовая страница СР1251.

Если исходный массив с кириллическим ASCII-текстом был в кодировке СР1251, то строка Java будет создана правильно. Кириллица попадет в свой диапазон '\u0400'—'\u04FF' кодировки Unicode.

Но у кириллицы есть еще, по меньшей мере, четыре кодировки. 

  • В MS-DOS применяется кодировка СР866. 
  • В UNIX обычно применяется кодировка KOI8-R. 
  • На компьютерах Apple Macintosh используется кодировка MacCyrillic. 
  • Есть еще и международная кодировка кириллицы ISO8859-5;

Например, байт 11100011 ( 0xЕ3 в шестнадцатеричной форме) в кодировке СР1251 представляет кириллическую букву Г , в кодировке СР866 — букву У , в кодировке KOI8-R — букву Ц , в ISO8859-5 — букву у , в MacCyrillic — букву г .

Если исходный кириллический ASCII-текст был в одной из этих кодировок, а местная кодировка СР1251, то Unicode-символы строки Java не будут соответствовать кириллице.

В этих случаях используются последние два конструктора, в которых параметром encoding указывается, какую кодовую таблицу использовать конструктору при создании строки.

Листинг ниже показывает различные случаи записи кириллического текста. В нем создаются три массива байто'в, содержащих слово "Россия" в трех кодировках.

  • Массив byteCP1251 содержит слово "Россия" в кодировке СР1251. 
  • Массив byteСP866 содержит слово "Россия" в кодировке СР866. 
  • Массив byteKOI8R содержит слово "Россия" в кодировке KOI8-R.

Из каждого массива создаются по три строки с использованием трех кодовых таблиц.

Кроме того, из массива символов с[] создается строка s1 , из массива бай-тов, записанного в кодировке СР866, создается строка s2 . Наконец, создается ссылка зз на строку-константу.

class StringTest{

  public static void main(String[] args){

    String winLikeWin = null, winLikeDOS = null, winLikeUNIX = null; 

    String dosLikeWin = null, dosLikeDOS = null, dosLikeUNIX = null; 

    String unixLikeWin = null, unixLikeDOS = null, unixLikeUNIX = null; 

    String msg = null;

    byte[] byteCp!251 = {

     (byte)0xD0, (byte)0xEE, (byte)0xFl,

     (byte)0xFl, (byte)0xES, (byte)0xFF 

    }; 

    byte[] byteCp866 = {

     (byte)0x90, (byte)0xAE, (byte)0xE1,

     (byte)0xEl, (byte)0xA8, (byte)0xEF 

    }; 

    byte[] byteKOISR = (

     (byte)0xF2, (byte)0xCF, (byte)0xD3,

     (byte)0xD3, (byte)0xC9, (byte)0xDl

    };

    char[] с = {'Р', 'о', 'с', 'с', 'и', 'я'};

    String s1 = new String(c);

    String s2 = new String(byteCp866);   // Для консоли MS Windows

    String s3 = "Россия";

    System.out.println();

    try{

           // Сообщение в Cp866 для вывода на консоль MS Windows.

      msg = new String("\"Россия\" в ".getBytes("Ср866"), "Cpl251");

      winLikeWin = new String(byteCp1251, "Cpl251");  //Правильно

      winLikeDOS = new String(byteCpl251,: "Cp866");

      winLikeUNIX - new String(byteCp1251, "KOI8-R");

      dosLikeWin = new String(byteCp866, "Cpl251");  // Для консоли

      dosLikeDOS = new String(byteCp866, "Cp866");   // Правильно

      dosLikeUNIX = new String(byteCp866, "KOI8-R");

      unixLikeWin = new String(byteKOISR, "Cpl251");

      unixLikeDOS = new String(byteKOISR, "Cp866");

      unixLikeUNIX = new String(byteKOISR, "KOI8-R");  // Правильно

      System.out.print(msg + "Cpl251: ");

      System.out.write(byteCp1251);

      System.out.println();

      System.out.print(msg + "Cp866 : ");

      System, out.write (byteCp866} ;

      System.out.println();

      System.out.print(msg + "KOI8-R: ");

      System.out.write(byteKOI8R); 

    {catch(Exception e)(

      e.printStackTrace(); 

    } 

    System.out.println(); 

    System.out.println();

    System.out.println(msg + "char array       : " + s1); 

    System.out.println(msg + "default encoding : " + s2); 

    System.out.println(msg + "string constant  : " + s3); 

    System.out.println();

    System.out.println(msg + "Cp1251 -> Cp1251: " + winLikeWin); 

    System.out.println(msg + "Cp1251 -> Cp866 : " + winLikeDOS); 

    System.out.println(msg + "Cp1251 -> KOI8-R: " + winLikeUNIX); 

    System.out.println(msg + "Cp866 -> Cp1251: " + dosLikeWin); 

    System.out.println(msg + "Cp866 -> Cp866 : " + dosLikeDOS); 

    System.out.println(msg + "Cp866 -> KOI8-R: " + dosLikeUNIX); 

    System.out.println(msg + "KOI8-R -> Cpl251: " + unixLikeWin); 

    System.out.println(msg + "KOI8-R -> Cp866 : " + unixLikeDOS); 

    System.out.println(msg + "KOI8-R -> KOI8-R: " + unixLikeUNIX); 

  } 

}


Сцепление строк

Со строками можно производить операцию сцепления строк (concatenation), обозначаемую знаком плюс +. Эта операция создает новую строку, просто составленную из состыкованных первой и второй строк, как показано в начале данной главы. Ее можно применять и к константам, и к переменным. Например:

String attention = "Внимание: ";

String s = attention + "неизвестный символ";

Вторая операция — присваивание += — применяется к переменным в левой части:

attention += s;

Поскольку операция + перегружена со сложения чисел на сцепление строк, встает вопрос о приоритете этих операций. У сцепления строк приоритет выше, чем у сложения, поэтому, записав "2" + 2 + 2 получим строку "222 ". Но, записав 2 + 2 + "2" , получим строку "42", поскольку действия выполняются слева направо. Если же запишем "2" + (2 + 2) , то получим "24" .


Как узнать длину строки

Для того чтобы узнать длину строки, т. е. количество символов в ней, надо обратиться к методу length() :

String s = "Write once, run anywhere."; 

int len = s.length{);

или еще проще

int len = "Write once, run anywhere.".length();

поскольку строка-константа — полноценный объект класса string . Заметьте, что строка — это не массив, у нее нет поля length .


Как выбрать символы из строки

Выбрать символ с индексом ind (индекс первого символа равен нулю) можно методом charAt(int ind) Если индекс ind отрицателен или не меньше чем длина строки, возникает исключительная ситуация. Например, после определения

char ch = s.charAt(3);

переменная ch будет иметь значение 't'

Все символы строки в виде массива символов можно получить методом

toCharArray() , возвращающим массив символов.

Если же надо включить в массив символов dst , начиная с индекса ind массива подстроку от индекса begin включительно до индекса end исключительно, то используйте метод getChars(int begin, int end, char[] dst, int ind) типа void .

В массив будет записано end - begin символов, которые займут элементы массива, начиная с индекса ind до индекса in d + (end - begin) - 1 .

Этот метод создает исключительную ситуацию в следующих случаях: 

  • ссылка dst = null ; 
  • индекс begin отрицателен; 
  • индекс begin больше индекса end ;
  • индекс end больше длины строки; 
  • индекс ind отрицателен;
  • ind + (end — begin) > dst.length.

Например, после выполнения

char[] ch = ('К', 'о', 'р', 'о', 'л', 'ь', ' ', 'л', 'е', 'т', 'а'};

"Пароль легко найти".getChars(2, 8, ch, 2);

результат будет таков:

ch = ('К', 'о', 'р', 'о', 'л', 'ь ', ' ', 'л', 'е', 'т', 'а'};

Если надо получить массив байтов, содержащий все символы строки в байтовой кодировке ASCII, то используйте метод getBytes() .

Этот метод при переводе символов из Unicode в ASCII использует локальную кодовую таблицу.

Если же надо получить массив байтов не в локальной кодировке, а в какой-то другой, используйте метод getBytes(String encoding) .


Как выбрать подстроку

Метод substring(int begin, int end) выделяет подстроку от символа с индексом begin включительно до символа с индексом end исключительно. Длина подстроки будет равна end - begin .

Метод substring (int begin) выделяет подстроку от индекса begin включительно до конца строки.

Если индексы отрицательны, индекс end больше длины строки или begin больше чем end , то возникает исключительная ситуация.

Например, после выполнения

String s = "Write onсe, run anywhere."; 

String sub1 = s.substring(6, 10); 

String sub2 = s.substring(16);

получим в строке sub1 значение " once ", а в sub2 — значение " anywhere ".


Как сравнить строки

Операция сравнения == сопоставляет только ссылки на строки. Она выясняет, указывают ли ссылки на одну и ту же строку. Например, для строк

String s1 = "Какая-то строка"; 

String s2 = "Другая-строка";

сравнение s1 == s2 дает в результате false .

Значение true получится, только если обе ссылки указывают на одну и ту же строку, например, после присваивания si = s2 .

Интересно, что если мы определим s2 так:

String s2 == "Какая-то строка";

то сравнение s1 == s2 даст в результате true , потому что компилятор создаст только один экземпляр константы "Какая-то строка" и направит на него все ссылки.

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

Логический метод equals (object obj) , переопределенный из класса object , возвращает true , если аргумент obj не равен null , является объектом класса string , и строка, содержащаяся в нем, полностью идентична данной строке вплоть до совпадения регистра букв. В остальных случаях возвращается значение false .

Логический метод equalsIgnoreCase(object obj) работает так же, но одинаковые буквы, записанные в разных регистрах, считаются совпадающими.

Например, s2.equals("другая строка") даст в результате false , а s2.equalsIgnoreCase("другая строка") возвратит true .

Метод compareTo(string str) возвращает целое число типа int , вычисленное по следующим правилам:

  1. Сравниваются символы данной строки this и строки str с одинаковым индексом, пока не встретятся различные символы с индексом, допустим k , или пока одна из строк не закончится.
  2. В первом случае возвращается значение this.charAt(k) - str.charAt(k), т. е. разность кодировок Unicode первйх несовпадающих символов.
  3. Во втором случае возвращается значение this.length() - str.length() , т. е. разность длин строк.
  4. Если строки совпадают, возвращается 0.

Если значение str равно null , возникает исключительная ситуация.

Нуль возвращается в той же ситуации, в которой метод equals() возвращает true .

Метод compareToignoreCase(string str) производит сравнение без учета регистра букв, точнее говоря, выполняется метод

this.toUpperCase().toLowerCase().compareTo( 

str.toUpperCase().toLowerCase());

Еще один метод— compareTo (Object obj) создает исключительную ситуацию, если obj не является строкой. В остальном он работает как метод compareTo(String str).

Эти методы не учитывают алфавитное расположение символов в локальной кодировке.

Русские буквы расположены в Unicode по алфавиту, за исключением одной буквы. Заглавная буква Ё расположена перед всеми кириллическими буквами, ее код '\ u040l ', а строчная буква е — после всех русских букв, ее код '\ u0451 '.

Если вас такое расположение не устраивает, задайте свое размещение букв с помощью класса  RuleBasedCollator из пакета java.text .

Сравнить подстроку данной строки this с подстрокой той же длины len другой строки str можно логическим методом

regionMatches(int indl, String str, int ind2, int len)

Здесь ind1 — индекс начала подстроки данной строки this, ind2 — индекс начала подстроки другой строки str . Результат false получается в следующих случаях:

  • хотя бы один из индексов ind1 или ind2 отрицателен;
  • хотя бы одно из ind1 + len или ind2 + len больше длины соответствующей строки;
  • хотя бы одна пара символов не совпадает.

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

regionMatches(boolean flag, int indl, String str, int ind2, int len)

Если первый параметр flag равен true , то регистр букв при сравнении подстрок не учитывается, если false — учитывается.

 


Как найти символ в строке

Поиск всегда ведется с учетом регистра букв.

Первое появление символа ch в данной строке this можно отследить методом indexOf(int ch) , возвращающим индекс этого символа в строке или -1 , если символа ch в строке this нет.

Например, "Молоко", indexOf('о') выдаст в результате 1 .

Конечно, этот метод выполняет в цикле последовательные сравнения this.charAt(k++> == ch , пока не получит значение true .

Второе и следующие появления символа ch в данной строке this можно отследить методом indexOf(int ch, int ind) .

Этот метод начинает поиск символа ch с индекса ind . Если ind < о, то поиск идет с начала строки, если ind больше длины строки, то символ не ищется, т. е. возвращается -1.

Например, "молоко".indexof('о', indexof ('о') + 1)   даст в результате 3. .

Последнее появление символа ch в данной строке this отслеживает метод lastIndexof (int ch). Он просматривает строку в обратном порядке. Если символ ch не найден, возвращается.-1. 

Например, "Молоко".lastindexof('о') даст в результате 5. 

Предпоследнее и предыдущие появления символа ch в данной строке this можно отследить методом lastIndexof (int ch, int ind) , который просматривает строку в обратном порядке, начиная с индекса ind .

Если ind больше длины строки, то поиск идёт от конца строки, если ind < о, то возвращается-1. 


Как найти подстроку

Поиск всегда ведется с учетом регистра букв.

Первое вхождение подстроки sub в данную строку this отыскивает метод indexof (String sub). Он возвращает индекс первого символа первого вхождения подстроки sub в строку или -1, если подстрока sub не входит в строку this . Например, " Раскраска ".indexof ("рас") даст в результате 4.

Если вы хотите начать поиск не с начала строки, ас какого-то индекса ind , используйте метод indexOf (String sub, int ind). если  i nd < 0 , то поиск идет с начала строки, если ind больше .длины строки, то символ не ищется, т. е. возвращается -1.

Последнее вхождение подстроки sub в данную строку this можно отыскать методом lastindexof ( string sub ), возвращающим индекс первого символа последнего вхождения подстроки sub в строку this или (-1), если подстрока sub не входит в строку this .

Последнее вхождение подстроки sub не во всю строку this , а только в ее начало до индекса ind можно отыскать методом l astIndexof(String stf, int ind ). Если ind больше длины строки, то .поиск идет от конца строки, еслиind < о , то возвращается -1.

Для того чтобы проверить, не начинается ли данная строка this с подстроки sub , используйте логический метод startsWith(string sub) , возвращающий true , если данная строка this начинается с подстроки sub , или совпадает с ней, или подстрока sub пуста. 

Можно проверить и появление подстроки sub в данной строке this , начиная с некоторого индекса ind логическим методом s tartsWith(String sub),int ind). Если индекс ind отрицателен или больше длины строки,возвращается   false . 

Для того чтобы проверить, не заканчивается ли данная строка this подстрокой sub , используйте логический метод endsWitht(String sub) . Учтите, что он возвращает true , если подстрока sub совпадает со всей строкой или подстрока sub пуста.

Например, if (fileName.endsWith(". Java")) отследит имена файлов с исходными текстами Java.

Перечисленные выше методы создают исключительную ситуацию, если

sub == null.

Если вы хотите осуществить поиск, не учитывающий регистр букв, измените предварительно регистр всех символов строки.


Как изменить регистр букв

Метод toLowerCase () возвращает новую строку, в которой все буквы переведены в нижний регистр, т. е. сделаны строчными.

Метод toUpperCase () возвращает новую строку, в которой все буквы переведены в верхний регистр, т. е. сделаны прописными.

При этом используется локальная кодовая таблица по умолчанию. Если нужна другая локаль, то применяются методы toLowerCase(Locale l oc ) и toUpperCase(Locale loc).


Как заменить отдельный символ

Метод replace (int old, int new) возвращает новую строку, в которой все вхождения символа old заменены символом new . Если символа old в строке нет, то возвращается ссылка на исходную строку.

Например, после выполнения " Рука в руку сует хлеб" , replace ('у', 'е') получим строку " Река в реке сеет хлеб".

Регистр букв при замене учитывается.


Как убрать пробелы в начале и конце строки

Метод trim о возвращает новую строку, в которой удалены начальные и конечные символы с кодами, не превышающими '\u0020 '.


Как преобразовать данные другого типа в строку

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

Класс string содержит восемь статических методов valueof (type elem) преобразования В строку примитивных типов boolean, char, int, long, float, double , массива char[] , и просто объекта типа object .

Девятый метод valueof(char[] ch, int offset, int len) преобразует в строку подмассив массива ch , начинающийся с индекса offset и имеющий len элементов.

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

Еще один простой способ — сцепить значение elem какого-либо типа с пустой строкой: "" + elem. При этом неявно вызывается метод  elem. toString ().


Класс StringBuffer

Объекты класса StringBuffer — это строки переменной длины. Только что созданный объект имеет буфер определенной емкости (capacity), по умолчанию достаточной для хранения 16 символов. Емкость можно задать в конструкторе объекта.

Как только буфер начинает переполняться, его емкость автоматически увеличивается, чтобы вместить новые символы.

В любое время емкость буфера можно увеличить, обратившись к методу ensureCapacity(int minCapacity)

Этот метод изменит емкость, только если minCapacity будет больше длины хранящейся в объекте строки. Емкость будет увеличена по следующему правилу. Пусть емкость буфера равна N. Тогда новая емкость будет равна

Мах(2 * N + 2, minCapacity)

Таким образом, емкость буфера нельзя увеличить менее чем вдвое.

Методом setLength (int newLength) можно установить любую длину строки.

Если она окажется больше текущей длины, то дополнительные символы будут равны ' \uOOOO' . Если она будет меньше текущей длины, то строка будет обрезана, последние символы потеряются, точнее, будут заменены символом '\uOOOO' . Емкость при этом не изменится.

Если число newLength окажется отрицательным, возникнет исключительная ситуация.

Совет

Будьте осторожны, устанавливая новую длину объекта.

Количество символов в строке можно узнать, как и для объекта класса String , методом length () , а емкость — методом capacity ().


Класс String Buffer

Создать объект класса stringBuf fer можно только конструкторами.

В классе stringBuffer три конструктора:

stringBuffer () — создает пустой объект с емкостью 16 символов;

stringBuffer .(int capacity) — создает пустой объект заданной емкости capacity ; 

StringBuffer (String str) — создает объект емкостью str . length () + 16, содержащий строку str .


Как добавить подстроку (класс String Buffer)

В классе stringBuffer есть десять методов append (), добавляющих подстроку в конец строки. Они не создают новый экземпляр строки, а возвращают ссылку на ту же самую, но измененную строку.

Основной метод append (string str) присоединяет строку str в конец данной строки. Если ссылка str == null, то добавляется строка "null".

Шесть методов append (type elem) добавляют примитивные типы boolean, char, int, long, float, double, преобразованные в строку.

Два метода присоединяют к строке массив str и подмассив sub символов,

преобразованные в строку: append (char [] str) И append (char [.] , sub, int offset, int len).

Десятый метод добавляет просто объект append (Object obj). Перед этим объект obj преобразуется в строку своим методом tostring ().


Как вставить подстроку (класс String Buffer)

Десять методов insert () предназначены для вставки строки, указанной параметром метода, в данную строку. Место вставки задается первым параметром метода ind . Это индекс элемента строки, перед которым будет сделана вставка. Он должен быть неотрицательным и меньше длины строки, иначе возникнет исключительная ситуация. Строка раздвигается, емкость буфера при необходимости увеличивается. Методы возвращают ссылку ни ту же преобразованную строку. 

Основной метод insert (int ind, string str) вставляет строку str в данную строку перед ее символом с индексом and . Если ссылка s tr == null вставляется строка "null".

Например, после выполнения

String s = new StringBuffer("Это большая строка"). insert(4, "не").toString();

ПОЛУЧИМ s == "Это небольшая строка".

Метод sb.insert(sb.length о, "xxx") будет работать так же, как метод

sb.append("xxx") .

Шесть методов insert (int ind, type elem) вставляют примитивные типы boolean, char, int, long, float, double, преобразованные в строку.

Два метода вставляют массив str и подмассив sub символов, преобразованные в строку:

i nsert(int ind, chart] str)

insert(int ind, char[] sub, int offset, int len)

Десятый метод вставляет просто объект :

insert(int ind, Object obj)

Объект obj перед добавлением преобразуется в строку своим методом

toString ().


Как удалить подстроку (класс String Buffer)

Метод delete tint begin, int end) удаляет из строки символы, начиная с индекса begin включительно до индекса end исключительно, если end больше длины строки, то до конца строки.

Например, после выполнения

String s = new StringBuffer("Это небольшая строка"). 

    delete(4, 6).toString();

получим   s == "Это большая строка".

Если begin отрицательно, больше длины строки или больше end , возникает исключительная ситуация.

Если begin == end, удаление не происходит.


Как удалить символ (класс String Buffer)

Метод deieteCharAt (int ind) удаляет символ с указанным индексом ind . Длина строки уменьшается на единицу.

Если индекс ind отрицателен или больше длины строки, возникает исключительная ситуация.


Как заменить подстроку (класс String Buffer)

Метод replace (int begin, int end. String str ) удаляет символы из строки, начиная с индекса begin включительно до индекса end исключительно, если end больше длины строки, то до конца строки, и вставляет вместо них строку str .

Если begin отрицательно, больше длины строки или больше end , возникает исключительная ситуация.

Разумеется, метод replace () — это последовательное выполнение методов

delete () и insert ().


Как перевернуть строку (класс String Buffer)

Метод reverse о меняет порядок расположения символов в строке на обратный порядок.

Например, после выполнения

String s = new StringBuffer("Это небольшая строка"), 

      reverse().toString();

получим s == "акортс яашьлобен отЭ".


Класс StringTokenizer (парсинг)

Класс StringTokenizer из пакета java.utii небольшой, в нем три конструктора и шесть методов.

Первый конструктор StringTokenizer (String str) создает объект, готовый разбить строку str на слова, разделенные пробелами, символами табуляций  '\t', перевода строки '\n' и возврата каретки '\r' . Разделители не включаются в число слов.

Второй конструктор StringTokenizer (String str. String delimeters) задает разделители вторым параметром deiimeters , например:

StringTokenizer("Казнить,нельзя:пробелов-нет", " \t\n\r,:-");

Здесь первый разделитель — пробел. Потом идут символ табуляции, символ перевода строки, символ возврата каретки, запятая, двоеточие, дефис. Порядок расположения разделителей в строке deiimeters не имеет значения. Разделители не включаются в число слов.

Третий конструктор позволяет включить разделители в число слов:

StringTokenizer(String str, String deiimeters, boolean flag);

Если параметр flag равен true , то разделители включаются в число слов, если false — нет. Например:

StringTokenizer("а - (Ь + с) / Ь * с", " \t\n\r+*-/(), true);

В разборе строки на слова активно участвуют два метода:

метод nextToken () возвращает в виде строки следующее слово;

логический метод hasMoreTokens () возвращает true , если в строке еще есть слова, и false , если слов больше нет.

Третий метод countTokens () возвращает число оставшихся слов.

Четвертый метод nextToken(string newDeiimeters) позволяет "на ходу" менять разделители. Следующее слово будет выделено по новым разделителям newDeiimeters; новые разделители действуют далее вместо старых разделителей, определенных в конструкторе или предыдущем методе nextToken () .

Оставшиеся два метода nextEiement () и hasMoreEiements () реализуют интерфейс Enumeration . Они просто обращаются к методам nextToken () и  hasMoreTokens().

String s = "Строка, которую мы хотим разобрать на слова"; 

StringTokenizer st = new StringTokenizer(s, " \t\n\r,."); 

while(st.hasMoreTokens()){

// Получаем слово и что-нибудь делаем с ним, например,

// просто выводим на экран

System.out.println(st.nextToken()) ; 

}

Полученные слова обычно заносятся в какой-нибудь класс-коллекцию: Vector, Stack или другой, наиболее подходящий для дальнейшей обработки текста контейнер.


Класс Vector

В классе vector из пакета java.uti i хранятся элементы типа object , а значит, любого типа. Количество элементов может быть любым и наперед не определяться. Элементы получают индексы 0, 1, 2, .... К каждому элементу вектора можно обратиться по индексу, как и к элементу массива.

Кроме количества элементов, называемого размером (size) вектора, есть еще размер буфера — емкость (capacity) вектора. Обычно емкость совпадает с размером вектора, но можно ее увеличить методом e nsureCapacity(int minCapacity) или сравнять с размером вектора методом trimToSize().

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

В классе четыре конструктора:

vector () — создает пустой объект нулевой длины;

Vector (int capacity) — создает пустой объект указанной емкости capacity ;

vector (int capacity, int increment) — создает пустой объект указанной емкости capacity и задает число increment , на которое увеличивается емкость при необходимости;

vector (Collection с) — вектор создается по указанной коллекции. Если capacity отрицательно, создается исключительная ситуация. После создания вектора его можно заполнять элементами.


Как добавить элемент в вектор

Метод add (Object element) позволяет добавить элемент в конец вектора

то же делает старый метод addElement (Object element) .

Методом add (int index, Object element) или  старым методом  insertElementAt (Object element, int index) можно вставить элемент В указанное место index . Элемент, находившийся на этом месте, и все последующие элементы сдвигаются, их индексы увеличиваются на единицу.

Метод addAil (Collection coll) позволяет добавить в конец вектора все элементы коллекции coll .

Методом addAii(int index, Collection coll) возможно вставить в позицию index все элементы коллекции coll .


Как заменить элемент

Метод set (int index, object element) заменяет элемент, стоявший в векторе в позиции index , на элемент element (то же позволяет выполнить старый

метод setElementAt (Object element, int index))


Как узнать размер вектора

Количество элементов в векторе всегда можно узнать методом size (). Метод capacity о возвращает емкость вектора.

Логический метод isEmpty () возвращает true , если в векторе нет ни одного элемента.


Как обратиться к элементу вектора

Обратиться к первому элементу вектора можно методом firstEiement () , к последнему — методом lastEiement () , к любому элементу — методом get (int index) или старым методом elementAt (int index).

Эти методы возвращают объект класса object . Перед использованием его следует привести к нужному типу.

Получить все элементы вектора в виде массива типа object[] можно методами toArray( ) и toAr ray (Object [] а) . Второй метод заносит все элементы вектора в массив а, если в нем достаточно места.


Как узнать, есть ли элемент в векторе

Логический метод contains (object element) возвращает true , если элемент element находится в векторе.

Логический метод containsAii (Collection с) возвращает true , если вектор содержит все элементы указанной коллекции.


Как узнать индекс элемента

Четыре метода позволяют отыскать позицию указанного элемента element:

indexof (Object element) — возвращает индекс первого появления элемента в векторе;

indexOf (Object element, int begin) — ведет поиск, начиная с индекса begin включительно;

lastindexOf (object element) — возвращает индекс последнего появления элемента в векторе;

lastindexOf (Object element, int start) — ведет поиск от индекса start включительно к началу вектора.

Если элемент не найден, возвращается —1.


Как удалить элементы

Логический метод remove (Object element) удаляет из вектора первое вхождение указанного элемента element . Метод возвращает true , если элемент найден и удаление произведено.

Метод remove (int index) удаляет элемент из позиции index и возвращает его в качестве своего результата типа object .

Аналогичные действия позволяют выполнить старые методы типа void :

removeElement (Object element) И removeElementAt (int index) , не возвращающие результата.

Удалить диапазон элементов можно методом removeRange(int begin, int end) , не возвращающим результата. Удаляются элементы от позиции begin включительно до позиции end исключительно.

Удалить из данного вектора все элементы коллекции coil возможно логическим Методом removeAll(Collection coll).

Удалить последние элементы можно, просто урезав вектор методом

setSizefint newSize).

Удалить все элементы, кроме входящих в указанную коллекцию coil , разрешает логический метод retainAll(Collection coll).

Удалить все элементы вектора можно методом clear () или старым методом

removeAHElements () или  обнулив размервектораметодом setSize(O).

 

Vector v = new Vector();

String s = "Строка, которую мы хотим разобрать на слова.";

 StringTokenizer st = new StringTokenizer(s, " \t\n\r,.");

 while (st.hasMoreTokens()){

   // Получаем слово и заносим в вектор

  v.add(st.nextToken());                                         // Добавляем в конец вектора 

}

System.out.println(v.firstElement());       // Первый элемент 

System.out.println(v.lastElement());        // Последний элемент

 v.setSize(4);                              // Уменьшаем число элементов 

v.add("собрать.");                          // Добавляем в конец

                                            // укороченного вектора

v.set(3, "опять");                          // Ставим в позицию 3 

for (int i = 0; i < v.sizeO; i++)         // Перебираем весь вектор

System.out.print(v.get(i) + " "); 

System.out.println();

Класс vector является примером того, как можно объекты класса object , a значит, любые объекты, объединить в коллекцию. Этот тип коллекции упорядочивает и даже нумерует элементы. В векторе есть первый элемент, есть последний элемент. К каждому элементу обращаются непосредственно по индексу. При добавлении и удалении элементов оставшиеся элементы автоматически перенумеровываются.


Класс Stack

Класс stack из пакета java.utii. объединяет элементы в стек.

Стек ( stack) реализует порядок работы с элементами подобно магазину винтовки— первым выстрелит патрон, положенный в магазин последним,— или подобно железнодорожному тупику — первым из тупика выйдет вагон, загнанный туда последним. Такой порядок обработки называется LIFO (Last In — First Out).

Перед работой создается пустой стек конструктором stack ().

Затем на стек кладутся и снимаются элементы, причем доступен только "верхний" элемент, тот, что положен на стек последним.

Дополнительно к методам класса vector класс stack содержит пять методов, позволяющих работать с коллекцией как со стеком:

push (Object item) —помещает элемент item в стек;

pop () — извлекает верхний элемент из стека;

peek () — читает верхний элемент, не извлекая его из стека;

empty () — проверяет, не пуст ли стек;

search (object item) — находит позицию элемента item в стеке. Верхний элемент имеет позицию 1, под ним элемент 2 и т. д. Если элемент не найден, возвращается — 1.

Листинг показывает, как можно использовать стек для проверки парности символов.


 

import java.utii.*; 

class StackTesti

static boolean checkParity(String expression,

                    String open, String close){ 

   Stack stack = new Stack (); 

   StringTokenizer st = new StringTokenizer(expression,

                          " \t\n\r+*/-(){}", true);

   while (st..hasMoreTokens ()) {

     String tmp = st.nextToken();

     if (tmp.equals(open)) , stack.push(open);

           i f (tmp.equals(close)) stack.pop(); 

     }

     if (stack.isEmpty () ) return true/return fals e; 

}

public static void main(String[] args){ 

  System.out.println(

          checkParityC'a - (b - (c - a) / (b + c) - 2) , "(", "))); 

 } 

}

Как видите, коллекции значительно облегчают обработку наборов данных.


Класс Hashtable

Класс Hashtable расширяет абстрактный класс Dictionary . В объектах этого класса хранятся пары "ключ — значение".

Из таких пар "Фамилия И. О. — номер" состоит, например, телефонный справочник.

Еще один пример — анкета. Ее можно представить как совокупность пар "Фамилия — Иванов", "Имя — Петр", "Отчество — Сидорович", "Год рождения — 1975" и т. д.

Подобных примеров можно привести множество.

Каждый объект класса Hashtable кроме размера (size) — количества пар, имеет еще две характеристики: емкость (capacity) — размер буфера, и показатель загруженности (load factor) — процент заполненности буфера, по достижении которого увеличивается его размер.


Как создать таблицу (Класс Hashtable)

Для создания объектов класс Hashtable предоставляет четыре конструктора:

Hashtable () — создает пустой объект с начальной емкостью в 101 элемент и показателем загруженности 0,75;

Hashtable (int capacity) — создает пустой объект с начальной емкостью capacity и показателем загруженности 0,75;

Hashtable(int capacity, float loadFactor) — создает пустой Объект с начальной емкостью capacity и показателем загруженности loadFactor;

Hashtable (Map f) — создает объект класса Hashtable , содержащий все элементы отображения f, с емкостью, равной удвоенному числу элементов отображения f , но не менее 11, и показателем загруженности 0,75.


Как заполнить таблицу (Класс Hashtable)

Для заполнения объекта класса Hashtable используются два метода:

Object put(Object key, Object value) — добавляет пару " key— value ", если ключа key не было в таблице, и меняет значение value ключа key , если он уже есть в таблице. Возвращает старое значение ключа или pull , если его не было. Если хотя бы один параметр равен null , возникает исключительная ситуация;

void putAii(Map f) — добавляет все элементы отображения f . В объектах-ключах key должны быть реализованы методы hashCode() и equals ().


Как получить значение по ключу (Класс Hashtable)

Метод get (Object key) возвращает значение элемента с ключом key в виде объекта класса object . Для дальнейшей работы его следует преобразовать к конкретному типу.


Как узнать наличие ключа или значения (Класс Hashtable)

Логический метод containsKey(object key) возвращает true , если в таблице есть ключ key .

Логический метод containsvalue (Object value) или старый метод contains (object value) возвращают true , если в таблице есть ключи со значением value .

Логический метод isEmpty () возвращает true , если в таблице нет элементов.


Как получить все элементы таблицы (Класс Hashtable)

Метод values () представляет все значения value таблицы в виде интерфейса Collection . Все модификации в объекте collection изменяют таблицу, и наоборот.

Метод keyset () предоставляет все ключи key таблицы в виде интерфейса set . Все изменения в объекте set корректируют таблицу, и наоборот.

Метод entrySet() представляет все пары " key— value " таблицы в виде интерфейса Set . Все модификации в объекте set изменяют таблицу, и наоборот.

Метод tostring () возвращает строку, содержащую все пары.

Старые методы elements () и keys () возвращают значения и ключи в виде интерфейса Enumeration .


Как удалить элементы (Класс Hashtable)

Метод remove (Object key) удаляет пару с ключом key , возвращая значение этого ключа, если оно есть, и null , если пара с ключом key не найдена.

Метод clear о удаляет все элементы, очищая таблицу.

В листинге показано, как можно использовать класс Hashtabie для создания телефонного справочника

import java.util.*;

class PhoneBook{

public static void main(String[] args){ 

Hashtabie yp = new Hashtabie();

 String name = null; 

yp.put("John", "123-45-67");

 yp.put ("Lemon", "567-34-12"); 

yp.put("Bill", "342-65-87"); 

yp.put("Gates", "423-83-49");

  yp.put("Batman", "532-25-08");

  try{

      name = args[0]; 

   (catch(Exception e){

      System.out.println("Usage: Java PhoneBook Name"); 

return;

 }

 if (yp.containsKey(name))

     System.out.println(name + "'s phone = " + yp.get(name)); 

 else

   System.out.println("Sorry, no such name"); 

 ) 

}


Класс Properties

Класс ' Properties расширяет класс Hashtabie . Он предназначен в основном для ввода и вывода пар свойств системы и их значений. Пары хранятся в виде строк типа string . В классе Properties два конструктора:

Properties () — создает пустой объект;

Properties (Properties default) — создает объект с заданными парами свойств default .

Кроме унаследованных от класса Hashtabie методов в классе Properties есть еще следующие методы.

Два метода, возвращающих значение ключа-строки в виде строки:

  • string getProperty (string key) — возвращает значение по ключу key ;
  • String getProperty(String.key, String defaultValue) — возвращает значение по ключу key если такого ключа нет, возвращается defaultValue .

Метод setProperty(String key, String value) добавляет новую пару, если ключа key нет, и меняет значение, если ключ key есть.

Метод load(Inputstream in ) загружает свойства из входного потока in .

Методы list(PrintStream out) И list (PrintWriter out) выводят свойства в выходной поток out.

Метод store (OutputStream out, String header) выводит свойства в выходной поток out с заголовком header .

Очень простой листинг демонстрирует вывод всех системных свойств Java.


 

class Prop{

  public static void main(String[] args){

  System.getProperties().list(System.out); 

 } 

}



Интерфейс Collection

Интерфейс collection из пакета java.util описывает общие свойства коллекций List и set . Он содержит методы добавления и удаления элементов, проверки и преобразования элементов:

boolean add (Object obj) — добавляет элемент obj в конец коллекции; возвращает false , если такой элемент в коллекции уже есть, а коллекция не допускает повторяющиеся элементы; возвращает true , если добавление прошло удачно;

boolean addAii (Collection coll) — добавляет все элементы коллекции coll в конец данной коллекции;

void clear ( ) — удаляет все элементы коллекции;

boolean contains (Object obj) — проверяет наличие элемента obj в коллекции;

boolean containsAii (Collection coll ) — проверяет наличие всех элементов коллекции coll в данной коллекции;

boolean isEmpty() — проверяет, пуста ли коллекция;

iterator iterator () — возвращает итератор данной коллекции;

boolean remove (object obj) — удаляет указанный элемент из коллекции; возвращает false , если элемент не найден, true , если удаление прошло успешно;

boolean removeAii (Collection coil) — удаляет элементы указанной коллекции, лежащие в данной коллекции;

boolean retainAii (Collection coll ) — удаляет все элементы данной коллекции, кроме элементов коллекции coll ;

int size () — возвращает количество элементов в коллекции;

object [] toArray () — возвращает все элементы коллекции в виде массива;

Objectn toArray<object[] a) — записывает все элементы коллекции в массив а, если в нем достаточно места.


Интерфейс List

Интерфейс List из пакета java.utii, расширяющий интерфейс collection , описывает методы работы с упорядоченными коллекциями. Иногда их называют последовательностями (sequence ). Элементы такой коллекции пронумерованы, начиная от нуля, к ним можно обратиться по индексу. В отличие от коллекции Set элементы коллекции List могут повторяться.

Класс vector — одна из реализаций интерфейса List .

Интерфейс List добавляет к методам интерфейса Collection методы, использующие индекс index элемента:

void add(int index, object obj) — вставляет элемент obj в позицию index ; старые элементы, начиная с позиции index , сдвигаются, их индексы увеличиваются на единицу;

boolean addAll(int index, Collection coll) — вставляет все элементы коллекции coil ;

object get(int index) — возвращает элемент, находящийся в позиции index ;

int indexOf(Object obj) — возвращает индекс первого появления элемента obj в коллекции;

int lastindexOf (object obj) — возвращает индекс последнего появления элемента obj в коллекции;

Listiterator listiterator () — возвращает итератор коллекции;

Listiterator listiterator (int index) — возвращает итератор конца коллекцииот позиции   index ;

object set (int index, object obj) — заменяет элемент, находящийся в позиции index , элементом obj ;

List subListUnt from, int to) — возвращает часть коллекции от позиции from включительно до позиции to исключительно.


Интерфейс Set

Интерфейс set из пакета java.utii, расширяющий интерфейс Collection , описывает неупорядоченную коллекцию, не содержащую повторяющихся элементов. Это соответствует математическому понятию множества (set) . Такие коллекции удобны для проверки наличия или отсутствия у элемента свойства, определяющего множество. Новые методы в интерфейс Set не добавлены, просто метод add () не станет добавлять еще одну копию элемента, если такой элемент уже есть в множестве.

Этот интерфейс расширен интерфейсом sortedset .


Интерфейс SortedSet

Интерфейс sortedset из пакета java.utii, расширяющий интерфейс Set, описывает упорядоченное множество, отсортированное по естественному порядку возрастания его элементов или по порядку, заданному реализацией

интерфейса comparator.

Элементы не нумеруются, но есть понятие первого, последнего, большего и меньшего элемента.

Дополнительные методы интерфейса отражают эти понятия:

comparator comparator () — возвращает способ упорядочения коллекции; object first ()— возвращает первый, меньший элемент коллекции;

SortedSet headset (Object toEiement) — возвращает начальные, меньшие элементы до элемента toEiement исключительно;

object last () — возвращает последний, больший элемент коллекции;

SortedSet subset(Object fromElement, Object toEiement) — Возвращает подмножество коллекции от элемента fromElement включительно до элемента toEiement исключительно;

SortedSet tailSet (Object fromElement) — возвращает последние, большие элементы коллекции от элемента fromElement включительно.


Интерфейс Map

Интерфейс Map из пакета java.utii описывает коллекцию, состоящую из пар "ключ — значение". У каждого ключа только одно значение, что соответствует математическому понятию однозначной функции или отображения (тар).

Такую коллекцию часто называют еще словарем (dictionary) или ассоциативным массивом (associative array).

Обычный массив — простейший пример словаря с заранее заданным числом элементов. Это отображение множества первых неотрицательных целых чисел на множество элементов массива, множество пар "индекс массива ^-элемент массива".

Класс HashTable — одна из реализаций интерфейса мар.

Интерфейс Map содержит методы, работающие с ключами и значениями:

boolean containsKey (Object key) — проверяет наличие ключа  key ;  

boolean containsValue (Object value) — проверяет наличие значения value ;

Set entryset () — представляет коллекцию в виде множества, каждый элемент которого — пара из данного отображения, с которой можно работать методами вложенного интерфейса Map. Entry;

object get (object key) — возвращает значение, отвечающее ключу key; set keyset () — представляет ключи коллекции в виде множества;

Object put(Object key, Object value) — добавляет пару "key— value",

если такой пары не было, и заменяет значение ключа key, если такой ключ уже есть в коллекции;

void putAii (Map m) — добавляет к коллекции все пары из отображения m;  

collection values () — представляет все значения в виде коллекции.

В интерфейс мар вложен интерфейс Map.Entry , содержащий методы работы с отдельной парой.


Вложенный интерфейс Map.Entry

Этот интерфейс описывает методы работы с парами, полученными методом entrySet():

методы g etKey() и getvaiue() позволяют получить ключ и значение пары; метод setvaiue (object value) меняет значение в данной паре.


Интерфейс SortedMap

Интерфейс SortedMap , расширяющий интерфейс Map , описывает упорядоченную по ключам коллекцию мар. Сортировка производится либо в естественном порядке возрастания ключей, либо, в порядке, описываемом в интерфейсеComparator .

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

comparator comparator () — возвращает способ упорядочения коллекции;

object firstKey() — возвращает первый, меньший элемент коллекции;

SortedMap headMap(Object toKey) — возвращает начало коллекции до элемента с ключом toKey исключительно;

object lastKey() — возвращает последний, больший ключ коллекции;

SprtedMap subMap (Object fromKey, Object toKey) — возвращает часть коллекции от элемента с ключом fromKey включительно до элемента с ключом toKey исключительно;

SortedMap taiiMap (object fromKey) — возвращает остаток коллекции от элемента fromKey включительно.

Вы можете создать свои коллекции, реализовав рассмотренные интерфейсы. Это дело трудное, поскольку в интерфейсах много методов. Чтобы облегчить эту задачу, в  Java API введены частичные реализации интерфейсов — абстрактные классы-коллекции. 


Абстрактные классы-коллекции

Эти классы лежат в пакете java.util,

Абстрактный класс AbstractGollection .реализует интерфейс Collection , но оставляет нереализованными методы iterator (), size ().

Абстрактный класс AbstractList реализует интерфейс List , но оставляет нереализованным метод get(mt) и унаследованный метод size() Этот класс позволяет реализовать коллекцию  спрямым доступом к элементам, подобно массиву

Абстрактный 5класе AbsttaatSequantaaiList реализует интерфейс List , но оставляет нереализованным метод listiteratordnt index) и унаследованный метрд size () . Данный класс позволяет реализовать коллекции с последовательным доступом к элементам с помощью итератора Listiterator

Абстрактный класс Abstractset реализует интерфейс Set , но оставляет нереализованными методы, унаследованные от Absjractcollection

Абстрактный класс AbstractMap реализует интерфейс Map , но оставляет нереализованным метод entrySet (),

Наконец, в составе Java API есть полностью реализованные классы-коллекции помимо уже рассмотренных классов Vectdr, Stack, Hashtable и Properties , Это классы ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, WeakHashMap ,

Для работы с этими классами разработаны интерфейсы iterator ,

Listiterator, Comparator И классы Arrays И Collections.

Перед тем Как рассмотреть использование данных классов, обсудим понятие итератора..


Интерфейс Iterator

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

StringTokenizer.

В интерфейсе iterator описаны всего три метода:

логический метод hasNext () возвращает true , если обход еще не завершен;

метод next о делает текущим следующий элемент коллекции и возвращает его в виде объекта класса object ;

метод remove о удаляет текущий элемент коллекции.

Можно представить себе дело так, что итератор — это указатель на элемент коллекции. При создании итератора указатель устанавливается перед первым элементом, метод next () перемещает указатель на первый элемент и показывает его. Следующее применение метода next () перемещает указатель на второй элемент коллекции и показывает его. Последнее применение метода next () выводит указатель за последний элемент коллекции.

Метод remove (), пожалуй, излишен, он уже не относится к задаче обхода коллекции, но позволяет при просмотре коллекции удалять из нее ненужные элементы.


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

Vector v = new Vector();

String s = "Строка, которую мы хотим разобрать на слова."; 

StringTokenizer st = new StringTokenizer(s, " \t\n\r,."); 

while (st.hasMoreTokens()){

     // Получаем слово и заносим в вектор.

     v.add(st.nextToken());                                 // Добавляем в конец вектора }

System.out.print*Ln(v.firstElement(});                      // Первый элемент 

System.out.println(v.lastElement());                        // Последний элемент 

v.setSize(4);                                               // Уменьшаем число элементов

  v.add("собрать.");                                         // Добавляем в конец укороченного вектора 

v.set(3, "опять");                                          // Ставим в позицию 3

for (int i = 0; i < v.sizeO; i++)                           // Перебираем весь вектор

System.out.print(v.get(i) + "."); 

System.out.println(};

Iterator it = v.iterator ();                                // Получаем итератор вектора 

try{

    while(it.hasNext())                                     // Пока в векторе есть элементы,

        System.out.println(it.next());           // выводим текущий элемент 

}catch(Exception e){}


Интерфейс  Listlterator

Интерфейс Listiterator расширяет интерфейс iterator , обеспечивая перемещение по коллекции как в прямом, так и в обратном направлении. Он может быть реализован только в тех коллекциях, в которых есть понятия следующего и предыдущего элемента и где элементы пронумерованы.

В интерфейс Listiterator добавлены следующие методы:

void add (Object element) — добавляет элемент element перед текущим элементом;

boolean hasPrevious () — возвращает true , если в коллекции есть элементы, стоящие перед текущим элементом;

int nextindex() — возвращает индекс текущего элемента; если текущим является последний элемент коллекции, возвращает размер коллекции;

Object previous () — возвращает предыдущий элемент и делает его текущим;

int previous index () — возвращает индекс предыдущего элемента;

void set (Object element) — заменяет текущий элемент элементом element;

выполняется сразу после next () или previous ().

Как видите, итераторы могут изменять коллекцию, в которой они работают, добавляя, удаляя и заменяя элементы. Чтобы это не приводило к конфликтам, предусмотрена исключительная ситуация, возникающая при попытке использования итераторов параллельно "родным" методам коллекции.

Интересно, что повторное применение методов next () и previous () друг за другом будет выдавать один и тот же текущий элемент.


Классы, создающие списки

Класс ArrayList полностью реализует интерфейс  List и итератор типа iterator . Класс ArrayList очень похож на класс Vector,имеет тот же набор методов и может использоваться в тех же ситуациях.

В классе ArrayList три конструктора;

ArrayList ()—создает пустой объект; 

 ArrayList (Collection coil) — создает объект, содержащий все элементы коллекции coll ; 

ArrayList (int initCapacity ) — создает пустой Объект емкости initCapacity .

Единственное отличие класса ArrayList от класса vector заключается  в  том, что класс ArrayList не синхронизован. Это означает что одновременное изменение экземпляра этого класса несколькими подпроцессами приведет к непредсказуемым результатам. 


Двунаправленный список

Класс LinkedList полностью реализует интерфейс List и содержит дополнительные методы, превращающие его в двунаправленный список. Он реализует итераторы типа iterator и bistiterator .

Этот класс можно использовать для обpaботки элементов в стеке, деке или двунаправленном списке.

В классе LinkedList два конструктора: .

  LinkedList - создает пустойобъект

LinkedList (Collection coil) — создает объект, содержащий все элементы коллекции coll.


Классы, создающие отображения

Класс например полностью реализует интерфейс Map , а также итератор типа iterator . Класс HashMap очень похож на класс Hashtabie и может использоваться в тех же ситуациях. Он имеет тот же набор функций и такие же конструкторы:

HashMap () — создает пустой объект с показателем загруженности 0,75;

НаshМар( int .capacity) - создает пустой объект с начальной емкостью capacity и показателем загруженности 0,75;

HashMap (int capacity, float loadFactor) — создает пустой объект С начальной емкостью capacity и показателем загруженности loadFactor ;

HashMap (Map f) — создает объект класса HashMap , содержащий все элементы отображения f , с емкостью, равной удвоенному числу элементов отображения f, но не менее 11, и показателем загруженности 0,75.

Класс WeakHashMap отличается от класса HashMap только тем, что в его объектах неиспользуемые элементы, на которые никто не ссылается, автоматически исключаются из объекта.


Упорядоченные отображения

Класс ТгееМар полностью реализует интерфейс sortedMap . Он реализован как бинарное дерево поиска, значит его элементы хранятся в упорядоченном виде. Это   значительно ускоряет поиск нужного элемента.

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

В этом классе четыре конструктора:

ТгееМар () — создает пустой объект с естественным  порядком элементов;

TreeМар (Comparator с) — создает пустой объект, в котором порядок задается объектом сравнения с ;

ТгееМар (Map f) — создает объект, содержащий все элементы отображения f, с естественным порядком 'его элементов;

ТгееМар (SortedMap sf) — создает объект, содержащий все элементы отображения sf , в том же порядке. 

Здесь надо пояснить, каким образом можно задать упорядоченность элементов коллекции


Сравнение элементов коллекций

Интерфейс Comparator описывает два метода сравнения:

int compare (Object obji, object obj2 ) — возвращает отрицательное число, если objl в каком-то смысле меньше obj2 ; нуль, если они считаются равными; положительное число, если objl больше obj2 . Для читателей, знакомых с теорией множеств, скажем, что этот метод сравнения обладает свойствами тождества, антисимметричности и транзитивности;

boolean equals (Object obj) — сравнивает данный объект с объектом obj , возвращая true , если объекты совпадают в каком-либо смысле, заданном этим методом.

Для каждой коллекции можно реализовать эти два метода, задав конкретный способ сравнения элементов, и определить объект класса SortedMap вторым конструктором. Элементы коллекции будут автоматически отсортированы в заданном порядке.

Листинг показывает один из возможных способов упорядочения комплексных чисел — объектов класса complex. Здесь описывается класс ComplexCompare , реализующий интерфейс Comparator , В листинге он применяется для упорядоченного хранения множества комплексных чисел.

import java.util.*;

class ComplexCompare implements Comparator{ 

public int compare(Object objl, Object obj2){ 

Complex zl = (Complex)objl, z2 = (Complex)obj2; 

double rel = zl.getReO, iml = zl.getlm(); 

double re2 = z2.getRe(), im2 = z2.getlm(); 

if (rel != re2) return (int)(rel - re2); 

else if (iml != im2) return (int)(iml — im2);

else return 0; 

public boolean equals(Object z) {

return compare(this, z) == 0; 

 } 


Классы, создающие множества

Класс HashSet полностью реализует интерфейс set и итератор типа iterator . Класс Hashset используется в тех случаях, когда надо хранить только одну копию каждого элемента.

В классе HashSet четыре конструктора:

Hashset () — создает пустой объект с показателем загруженности 0,75;

HashSet (int capacity) — создает пустой объект с начальной емкостью capacity и показателем загруженности 0,75;

HashSet (int capacity, float loadFactor) — создает пустой объект с начальной емкостью capacity и показателем загруженности loadFactor ;

HashSet (Collection coll) — создает объект, содержащий все элементы коллекции coll , с емкостью, равной удвоенному числу элементов коллекции coll , но не менее 11, и показателем загруженности 0,75.

 


Упорядоченные множества

Класс TreeSet полностью реализует интерфейс sortedset и итератор типа iterator . Класс TreeSet реализован как бинарное дерево поиска, значит, его элементы хранятся в упорядоченном виде. Это значительно ускоряет поиск нужного элемента. 

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

Этот класс удобен при поиске элемента во множестве, например, для проверки, обладает ли какой-либо элемент свойством, определяющим множество.

В классе TreeSet четыре конструктора:

TreeSet () — создает пустой объект с естественным порядком элементов;

TreeSet (Comparator с) — создает пустой объект, в котором порядок задается объектом сравнения с;

TreeSet (Collection coll) — создает объект, содержащий все элементы коллекции coll , с естественным порядком ее элементов;

TreeSet (SortedMap sf) — создает объект, содержащий все элементы отображения sf , в том же порядке.

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

TreeSet ts = new TreeSet (new ComptexCompare()); 

ts.add(new Complex(1.2, 3.4));

ts. add (new Complex (-1.25, 33.4»; 

ts.add(new Complex(1.23, -3.45));

ts.add(new Complex(16.2, 23.4));

Iterator it = ts.iterator();

while(it.hasNext()) , ((Complex)it.next()).pr();


Методы класса Collections

Все методы класса collections статические, ими можно пользоваться, не создавая экземпляры классу C ollections   

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

Сортировка может быть сделана только в упорядочиваемой коллекции, реализующей интерфейс List . Для сортировки в классе collections есть два метода:

static void sort (List coll) — сортирует в естественном порядке возрастания коллекцию coll, реализующую интерфейс List;

static void sort (List coll, Comparator c) — сортирует коллекцию coll

в порядке, заданном объектом с. После сортировки можно осуществить бинарный поиск в коллекции:

static int binarySearch(List coll, Object element) — отыскивает элемент element в отсортированной в естественном порядке возрастания коллекции coll и возвращает индекс элемента или отрицательное число, если элемент не найден; отрицательное число показывает индекс, с которым элемент element был бы вставлен в коллекцию, с обратным знаком;

static int binarySearchfList coll, Object element, Comparator c) — TO же, но коллекция отсортирована в порядке определенном объектом с .

Четыре метода находят наибольший и наименьший элементы в упорядочиваемой коллекции:

static object max (Collection coll) — возвращает наибольший в естественном порядке элемент коллекции coll;

static Object max (Collection coll, Comparator c) — TO же В порядке заданном объектом с ;

static object mm (Collection coll) — возвращает наименьший в естественном порядке элемент коллекции сои;

static Object min(Collection coll, Comparator c) — TO же В порядке заданном объектом с . 

Два метода "перемешивают" элементы коллекции в случайном порядке:

static void shuffle (List coll) — случайные числа задаются по умолчанию;

static void shuffle (List coll, Random r) — случайные числа определяются объектом г .

Метод reverse (List coll) меняет порядок расположения элементов на обратный.

Метод copy (List from, List to) копирует коллекцию from в коллекцию to .

Метод fill (List coll, object element) заменяет все элементы существующей коллекции coll элементом element .


  Р абота с массивами

В классе Arrays из пакета java.utii собрано множество методов для работы с массивами. Их можно разделить на четыре группы.

Восемнадцать статических методов сортируют массивы с разными типами числовых элементов в порядке возрастания чисел или просто объекты в их естественном порядке.

Восемь из них имеют простой вид

static void sort(type[] a)

где type может быть один из семи примитивных типов byte, short, int, long, char, float, double ИЛИ ТИП Object .

Восемь методов с теми же типами сортируют часть массива от индекса from включительно до индекса to исключительно:

static void sort(type[] a, int from, int to)

Оставшиеся два метода сортировки упорядочивают массив или его часть с элементами типа object по правилу, заданному объектом с, реализующим интерфейс Comparator :

static void sort(Object[] a, Comparator c)

static void sort(Object[] a, int from, int to, Comparator c)

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

static int binarySearch(type[] a, type element)

где type — один из тех же восьми типов. Девятый метод поиска имеет вид

static int binarySearch(Object[] a, Object element, Comparator c).

Он отыскивает элемент element в массиве, отсортированном в порядке, заданном объектом с .

Методы поиска возвращают индекс найденного элемента массива. Если элемент не найден, то возвращается отрицательное число, означающее индекс, с которым элемент был бы вставлен в массив в заданном порядке, с обратным знаком.

Восемнадцать статических методов заполняют массив или часть массива указанным значением value :

static void fill(type[], type value)

static void fill(type[], int from, int to, type value)

где type — один из восьми примитивных типов или тип object . Наконец, девять статических логических методов сравнивают массивы:

static boolean equals(type[] al, type[] a2)

где type — один из восьми примитивных типов или тип Object .

Массивы считаются равными, и возвращается true , если они имеют одинаковую длину и равны элементы массивов с одинаковыми индексами.

В листинге приведен простой пример работы с этими методами.

import java.utii.*;

class ArraysTest{

public static void main(String[] args){

int[] a = {34, -45, 12, 67, -24, 45, 36, -56};

Arrays.sort(a) ;

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

System.out.print (a[i]. + " "); 

System.out.println();

Arrays.fill(a, Arrays.binarySearch(a, 12), a.length, 0); 

for (int i = 6; i < a.length; i++)

  System.out.print(a[i] + " "); 

System.out.println(); 

 } 

}


Локальные установки

Некоторые данные — даты, время — традиционно представляются в разных местностях по-разному. Например, дата в России выводится в формате число, месяц, год через точку: 27.06.01. В США принята запись месяц/число/год через наклонную черту: 06/27/01.

Совокупность таких форматов для данной местности, как говорят на жаргоне "локаль", хранится в объекте класса Locale из пакета java.utii . Для создания такого объекта достаточно знать язык language и местность country. Иногда требуется третья характеристика — вариант variant , определяющая программный продукт, например, "WIN", "MAC", "POSIX".

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

user.language = ru                  // Язык — русский

user.region = RU                   // Местность — Россия

file.encoding = Cpl251           // Байтовая кодировка — CP1251

Они определяют русскую локаль и локальную кодировку байтовых символов. Локаль, установленную по умолчанию на той машине, где выполняется программа, можно выяснить статическим методом Locale.getoefauito .

Чтобы работать с другой локалью, ее надо прежде всего создать. Для этого в классе Locale есть два конструктора:

Locale(String language, String country)

Locale(String language, String country. String variant)

Параметр language — это строка из двух строчных букв, определенная стандартом ISO639, например, "ru", "fr", "en". Параметр country — строка из двух прописных букв, определенная стандартом ISO3166, например, "RU", "us", "ев" . Параметр variant не определяется стандартом, это может быть,

например, строка " Traditional ".

Локаль часто указывают одной строкой "ru_RU", "en_GB", "en_us", "en_CA " и т. д.

После создания локали можно сделать ее локалью по умолчанию статическим методом:

Locale.setDefault(Locale newLocale);

Несколько статических методов класса Locale позволяют получить параметры локали по умолчанию, или локали, заданной параметром locale :

string getcountryo — стандартный код страны из двух букв;

string getDispiayCountry() — страна записывается словом, обычно выводящимся на экран;

String getDisplayCountry (Locale locale) — то же для указанной локали.

Такие же методы есть для языка и варианта.

Можно просмотреть список всех локалей, определенных для данной JVM, и их параметров, выводимый в стандартном виде:

Locale[] getAvailableLocales()

String!] getlSOCountries()

String[] getlSOLanguages()

Установленная локаль в дальнейшем используется при выводе данных в местном формате.


Работа с датами и временем

Методы работы с датами и показаниями времени собраны в два класса: Calendar и Date из пакета java.utii.

Объект класса Date хранит число миллисекунд, прошедших с 1 января 1970 г. 00:00:00 по Гринвичу. Это "день рождения" UNIX, он называется " Epoch ".

Класс Date удобно использовать для отсчета промежутков времени в миллисекундах.

Получить текущее число миллисекунд, прошедших с момента Epoch на той машине, где выполняется программа, можно статическим методом

System.currentTimeMillis()

В классе Date два конструктора. Конструктор Date () заносит в создаваемый объект текущее время машины, на которой выполняется программа, по системным часам, а конструктор Date (long miiiisec) — указанное число.

Получить значение, хранящееся в объекте, можно методом long getTime (),

установить новое значение — методом setTimedong newTime).

Три логических метода сравнивают отсчеты времени:

boolean after (long when) — возвращает true , если время when больше данного;

boolean before (long when) — возвращает true , если время when меньше данного;

boolean after (Object when) — возвращает true , если when — объект класca Date и времена совпадают.

Еще два метода, сравнивая отсчеты времени, возвращают отрицательное число типа int , если данное время меньше аргумента when; нуль, если времена совпадают; положительное число, если данное время больше аргумента when :

int compareTo(Date when);

int compareTotobject when) — если when не относится к объектам класса Date , создается исключительная ситуация.

Преобразование миллисекунд, хранящихся в объектах класса Date , в текущее время и дату производится методами класса calendar .


Часовой пояс и летнее время

Методы установки и изменения часового пояса (time zone) , а также летнего времени DST (Daylight Savings Time), собраны в абстрактном классе Timezone из пакета java.utii. В этом же пакете есть его реализация — подкласс SimpleTimeZone .

В классе simpieTimeZon e три конструктора, но чаще всего объект создается статическим методом getoefauito , возвращающим часовой пояс, установленный на машине, выполняющей программу.

В этих классах множество методов работы с часовыми поясами, но в большинстве случаев требуется только узнать часовой пояс на машине, выполняющей программу, статическим методом getDefault () , проверить, осуществляется ли переход на летнее время, логическим методом useDaylightTime () , и установить часовой пояс методом setDef ault (TimeZone zone).


Класс Calendar

Класс Calendar — абстрактный, в нем собраны общие свойства календарей: юлианского, григорианского, лунного. В Java API пока есть только одна его реализация — подкласс GregorianCalendar.

Поскольку calendar — абстрактный класс, его экземпляры создаются четырьмя статическими методами по заданной локали и/или часовому поясу:

Calendar getlnstance()

Calendar getlnstance(Locale loc)

Calendar getlnstance(TimeZone tz)

Calendar getlnstance(TimeZone tz, Locale loc)

Для работы с месяцами определены целочисленные константы от JANUARY

до DECEMBER , 3 для работы с днями  недели — константы  MONDAY до SUNDAY .

Первый день недели можно узнать методом i nt getFirstDayOfweek(), a установить — методом setFirstDayOfWeek(int day), например:

setFirstDayOfWeek(Calendar.MONDAY)

Остальные методы позволяют просмотреть время и часовой пояс или установить их.


Подкласс GregorianCalendar

В григорианском календаре две целочисленные константы определяют эры: вс (before Christ) и AD (Anno Domini).

Семь конструкторов определяют календарь по времени, часовому поясу и/или локали:

GregorianCalendar()

GregorianCalendar(int year, int month, int date)

GregorianCalendar(int year, int month, int date, int hour, int minute)

GregorianCalendar(int year, int month, int date,

int hour, int minute, int second)

GregorianCalendar(Locale loc)

GregorianCalendar(TimeZone tz)

GregorianCalendar(TimeZone tz, Locale loc)

После создания объекта следует определить дату перехода с юлианского календаря на григорианский календарь методом setGregorianChange(Date date ). По умолчанию это 15 октября 1582 г. На территории России переход был осуществлен 14 февраля 1918 г., значит, создание объекта greg надо выполнить так:

GregorianCalendar greg = new GregorianCalendar(); greg.setGregorianChange(new

GregorianCalendar(1918, Calendar.FEBRUARY, 14) .getTime ()) ;

Узнать, является ли год високосным в григорианском календаре, можно л огическим методом i sLeapYear ().

Метод get (int field) возвращает элемент календаря, заданный аргументом field . Для этого аргумента в классе Calendar определены следующие статические целочисленные константы:

ERA     WEEK_OF_YEAR    DAY_OF_WEEK               SECOND

YEAR    WEEK_OF_MONTH   DAY_OF_WEEK_IN_MONTH      MILLISECOND

MONTH   DAY_OF_YEAR     HOUR_OF_DAY               ZONE_OFFSET

DATE    DAY_OF_MONTH    MINUTE                    DST_OFFSET

Несколько методов set () , использующих эти константы, устанавливают соответствующие значения.

 


Представление даты и времени

Различные способы представления дат и показаний времени можно осуществить методами, собранными в абстрактный класс DateFormat и его подкласс SimpleDateFormat ИЗ пакета Java. text.

Класс DateFormat предлагает четыре стиля представления даты и времени:

стиль SHORT представляет дату и время в коротком числовом виде: 27.04.01 17:32; в локали США: 4/27/01 5:32 РМ;

стиль MEDIUM задает год четырьмя цифрами и показывает секунды: 27.04.2001 17:32:45; в локали США месяц представляется тремя буквами;

стиль LONG представляет месяц словом и добавляет часовой пояс: 27 апрель 2001 г. 17:32:45 GMT+03.-00;

стиль FULL в русской локзли таков же, как и стиль LONG ; в локали США добавляется еще день недели.

Есть еще стиль DEFAULT , совпадающий со стилем MEDIUM .

При создании объекта класса simpieDateFormat можно задать в конструкторе шаблон, определяющий какой-либо другой формат, например:

SimpieDateFormat sdf = new SimpieDateFormat("dd-MM-yyyy hh.iran"); System.out.println(sdf.format(new Date()));

Получим вывод в таком виде: 27-04-2001 17.32.

В шаблоне буква d означает цифру дня месяца, м — цифру месяца, у — цифру года, h — цифру часа, m — цифру минут. Остальные обозначения для шаблона указаны В Документации ПО Классу SimpieDateFormat .

Эти буквенные обозначения можно изменить с помощью класса DateFormatSymbols.

Не во всех локалях можно создать объект класса SimpieDateFormat . В таких случаях используются статические методы getinstanceo класса DateFormat , возвращающие объект класса DateFormat . Параметрами этих методов служат стиль представления даты и времени и, может быть, локаль.

После создания объекта метод format о класса DateFormat возвращает строку с датой и временем, согласно заданному стилю. В качестве аргумента задается объект класса Date .

Например:

System.out.println("LONG: " + DateFormat.getDateTimelnstance( 

DateFormat. LONG, DateFormat. LONG) . format (new Date ()));

или

System.out.println("FULL: " + DateFormat.getDateTimelnstance(

DateFormat.FULL,DateFormat.FULL, Locale.US).format(new Date()));


Получение случайных чисел

Получить случайное неотрицательное число, строго меньшее единицы, в виде типа double можно статическим методом random () ИЗ класса java.lang.Math.

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

Более серьезные действия со случайными числами можно организовать с помощью методов класса Random из пакета java.utii . В классе два конструктора:

Random (long seed) — создает генератор псевдослучайных чисел, использующий для начала работы число s eed; Random() —выбирает в качестве начального значения текущее время. ;

Создав генератор, можно получать случайные числа соответствующего типа методами nextBoolean(), nextDouble(), nextFloat()(, nextGau.ssian(), next into, nextLong(), nextint(int max) или записать сразу последовательность случайных чисел в заранее определенный массив байтов bytes методом nextBytes(byte[] bytes) .

Вещественные случайные числа равномерно располагаются в диапазоне от 0,0 включительно до 1,0 исключительно. Целые случайные числа равномерно распределяются по всему диапазону соответствующего типа за, одним исключением: если в аргументе указано целое число max , то диапазон случайных чисел будет от нуля включительно до max исключительно.


Копирование массивов

В классе System из пакета java.iang есть статический метод копирования массивов, который использует сама исполняющая система Java. Этот метод действует быстро и надежно, его удобно применять в программах. Синтаксис:

static void arraycopy(Object src, int src_ind, Object dest, int dest_ind, int count)

Из массива, на который указывает ссылка src , копируется count элементов, начиная с элемента с индексом src_ind , в массив, на который указывает ссылка dest , начиная с его элемента с индексом dest_ind.

Все индексы должны быть заданы так, чтобы элементы лежали в массивах, типы массивов должны быть совместимы, а примитивные типы обязаны полностью совпадать. Ссылки на массивы не должны быть равны null .

Ссылки src и dest могут совпадать, при этом для копирования создается промежуточный буфер. Метод можно использовать, например, для сдвига элементов в массиве. После выполнения

int[] arr = {5, 6, 1, 8, 9, 1, 2, 3, 4, 5, -3, -7}; 

System.arraycopy(arr, 2, arr, 1, arr.length — 2);

получим ( 5, 7, 8, 9, 1, 2, 3, 4, 5, -3, -7, -7} .


Взаимодействие с системой

Класс System позволяет осуществить и некоторое взаимодействие с системой во время выполнения программы (run time ). Но кроме него для этого есть специальный класс Runtime .

Класс Runtime содержит некоторые методы взаимодействия с JVM во время выполнения программы. Каждое приложение может получить только один экземпляр данного класса статическим методом getRuntime (}. Все вызовы этого метода возвращают ссылку на один и тот же объект.

Методы fгееметогу () и totaiMemory () возвращают количество свободной и всей памяти, находящейся в распоряжении JVM для размещения объектов, в байтах, в виде числа типа long. He стоит твердо опираться на эти числа, поскольку количество памяти меняется динамически.

Метод exit(int status) запускает процесс останова JVM и передает операционной системе статус завершения status . По соглашению, ненулевой статус означает ненормальное завершение. Удобнее использовать аналогичный метод классаsystem , который является статическим.

Метод hait(int status ) осуществляет немедленный останов JVM. Он не завершает запущенные процессы нормально и должен использоваться только в аварийных ситуациях.

Метод loadbibrary(string libName) позволяет подгрузить динамическую библиотеку во время выполнения по ее имени libName .

Метод l oad (string fileName ) подгружает динамическую библиотеку по имени файла fileName , в котором она хранится.

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

Метод gc() запускает процесс освобождения ненужной оперативной памяти ( garbage collection) . Этот процесс периодически запускается самой виртуальной машиной Java и выполняется на фоне с небольшим приоритетом, но можно его запустить и из программы. Опять-таки удобнее использовать статический Метод System.gc () .

Наконец, несколько методов ехес () запускают в отдельных процессах исполнимые файлы. Аргументом этих методов служит командная строка исполнимого файла.

Например Runtime.getRuntime () .exec ("notepad" ) запускает Программу

Блокнот на платформе MS Windows.

Методы exec () возвращают экземпляр класса process , позволяющего управлять запущенным процессом. Методом destroy () можно остановить процесс, методом exitValue() получить его код завершения. метод waitFor() приостанавливает основной подпроцесс до тех пор, пока не закончится запущенный процесс. Три метода getlnputStream(), getOutputStream() И getErrorStream()( возвращают входной, выходной поток и поток ошибок запущенного процесса 


Обработка исключительных ситуаций

Исключительные ситуации (exceptions) могут возникнуть во время выполнения (runtime) программы, прервав ее обычный ход. К ним относится деление на нуль, отсутствие загружаемого файла, отрицательный или вышедший за верхний предел индекс массива, переполнение выделенной памяти и масса других неприятностей, которые могут случиться в самый неподходящий момент.

Конечно, можно предусмотреть такие ситуации и застраховаться от них как-нибудь так:

if (something == wrong){

// Предпринимаем аварийные действия 

}else{

// Обычный ход действий 

}

Но при этом много времени уходит на проверки, и программа превращается в набор этих проверок. Посмотрите любую штатную производственную программу, написанную на языке С или Pascal, и увидите, что она на 2/3 состоит из таких проверок.

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

Приведем пример. В программе листинга может возникнуть деление на нуль, если запустить ее с аргументом 0. В программе нет никаких средств обработки такой исключительной ситуации.

class SimpleExt {

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

int n = Integer.parselnt(args[0]); 

System.out.println("10 / n = " + (10 / n)); 

System.out.println("After all actions"); 

}

Программа SimpleExt запущена три раза. Первый раз аргумент args[0] равен 5 и программа выводит результат: "ю / n = 2". После этого появляется второе сообщение: "After all actions".

Второй раз аргумент равен о, и вместо результата мы получаем сообщение о том, что в подпроцессе "main" произошло исключение класса ArithmeticException вследствие деления на нуль: "/ by zero". Далее уточняется, что исключение возникло при выполнении метода main класса SimpleExt, а в скобках указано, что действие, в результате которого возникла исключительная ситуация, записано в четвертой строке файла SimpleExt.java. Выполнение программы прекращается, заключительное сообщение не появляется.

Третий раз программа запущена вообще без аргумента. В массиве argsn нет элементов, его длина равна нулю, а мы пытаемся обратиться к элементу args [0]. Возникает исключительная ситуация класса ArrayindexOutofBoundsException вследствие действия, записанного в третьей строке файла SimpleExt.java. Выполнение программы прекращается, обращение к методу printino не происходит.


Блоки перехвата исключения

Мы можем перехватить и обработать исключение в программе. При описании обработки применяется бейсбольная терминология. Говорят, что исполняющая система или программа "выбрасывает" (throws) объект-исключение. Этот объект "пролетает" через всю программу, появившись сначала в том методе, где произошло исключение, а программа в одном или нескольких местах пытается (try) его "перехватить" (catch) и обработать. Обработку можно сделать полностью в одном месте, а можно обработать исключение в одном месте, выбросить снова, перехватить в другом месте и обрабатывать дальше.

Мы уже много раз сталкивались с необходимостью обрабатывать различные исключительные ситуации, но не делали этого, потому что не хотели отвлекаться от основных конструкций языка. Не вводите это в привычку! Хорошо написанные объектно-ориентированные программы обязательно должны обрабатывать все возникающие в них исключительные ситуации.

Для того чтобы попытаться (try) перехватить (catch) объект-исключение, надо весь код программы, в котором может возникнуть исключительная ситуация, охватить оператором try{} catch о {}. Каждый блок catchou перехватывает исключение только одного типа, того, который указан в его аргументе. Но можно написать несколько блоков catch(){} для перехвата нескольких типов исключений.

Например, мы знаем, что в программе могут возникнуть исключения двух типов. Напишем блоки их обработки, как это сделано в листинге.

class SimpleExtlf

public static void main(String[] args){ 

try{

int n = Integer.parselnt(args[0]);

System.out.println("After parselnt());

System.out.println(" 10 / n = " + (10 / n) ) ;

Systfem. out. println ("After results output"); 

}catch(ArithmeticException ae){

System.out.println("From Arithm.Exc. catch: "+ae); 

}catch(ArraylndexOutOfBoundsException arre){

System.out.println("From Array.Exc.catch: "+arre); 

}finally{

System.out.println("From finally"); 

}

System.out.println("After all actions"); 

}

В программу вставлен блок try{} и два блока перехвата catchou для каждого типа исключений. Обработка исключения здесь заключается просто в выводе сообщения и содержимого объекта-исключения, как оно представлено методом tostring() соответствующего класса-исключения.

После блоков перехвата вставлен еще один, необязательный блок finaliy(). Он предназначен для выполнения действий, которые надо выполнить обязательно, чтобы ни случилось. Все, что написано в этом блоке, будет выполнено и при возникновении исключения, и при обычном ходе программы, и даже если выход из блока try{} осуществляется оператором return.

Если в операторе обработки исключений есть блок finally{}, то блок catch () {} может отсутствовать, т. е. можно не перехватывать исключение, но при его возникновении все-таки проделать какие-то обязательные действия.

Кроме блоков перехвата в листинге после каждого действия делается трассировочная печать, чтобы можно было проследить за порядком выполнения программы. Программа запущена три раза: с аргументом 5, с аргументом 0 и вообще без аргумента.

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

После второго запуска, приводящего к делению на нуль, управление сразу

Же передается В соответствующий блок catch(ArithmeticException ае) {}, потом выполняется то, что написано в блоке finally{}.

После третьего запуска управление после выполнения метода parseinto передается В другой блок catch(ArraylndexOutOfBoundsException arre){}, затем в блок finally{}.

Обратите внимание, что во всех случаях — и при обычном ходе программы, и после этих обработок — выводится сообщение "After all actions". Это свидетельствует о том, что выполнение программы не прекращается при возникновении исключительной ситуации, а продолжается после обработки и выполнения блока finally*}.

При записи блоков обработки исключений надо совершенно четко представлять себе, как будет передаваться управление во всех случаях.

Интересно, что пустой блок catch (){}, в котором между фигурными скобками нет ничего, даже пробела, тоже считается обработкой исключения и приводит к тому, что выполнение программы не прекратится. Именно так мы "обрабатывали" исключения в предыдущих главах.

Немного выше было сказано, что выброшенное исключение "пролетает" через всю программу. Что это означает? Изменим программу, вынеся деление в отдельный метод f ().

class SimpleExt2{

private static void f(int n){

System.out.println(" 10 / n = " + (10 / n)); 

}

public static void main(String[] args){ 

try{

int n = Integer.parselnt(args[0]); 

System.out.println("After parselnt()); 

f (n);

System.out.println("After results output"); 

}catch(SrithmeticException ae){

System.out.println("From Arithm.Exc. catch: "+ae); 

}catch(ArraylndexQutOfBoundsException arre){

System.out.println("From Array.Exc. catch: "+arre); 

}finally{

System,out.println("From finally"); 

}

System.out.println("After all actions"); 

}

}

Откомпилировав и запустив программу, убедимся, что вывод программы не изменился, он такой же. Исключение, возникшее при делении на нуль в методе f (), "пролетело" через этот метод, "вылетело" в метод main (), там перехвачено и обработано.


Часть заголовка метода throws

То обстоятельство, что метод не обрабатывает возникающее в нем исключение, а выбрасывает (throws) его, следует отмечать в заголовке метода служебным словом throws и указанием класса исключения:

private static void f(int n) throws ArithmeticException{

System.out.println(" 10 / n = " + (10 / n)) ; 

}

Дело в том, что спецификация JLS делит все исключения на проверяемые (checked), те, которые проверяет компилятор, и непроверяемые (unchecked). При проверке компилятор замечает необработанные в методах и конструкторах исключения и считает ошибкой отсутствие в заголовке таких методов и конструкторов пометки throws. Именно для предотвращения подобных ошибок мы в предыдущих главах вставляли в листинги блоки обработки исключений.

Так вот, исключения класса RuntimeException и его подклассов, одним из которых является ArithmeticException, непроверяемые, для них пометка throws необязательна. Еще одно большое семейство непроверяемых исключений составляет класс Error и его расширения.

Почему компилятор не проверяет эти типы исключений? Причина в том, что исключения класса RuntimeException свидетельствуют об ошибках в программе, и единственно разумный метод их обработки — исправить исходный текст программы и перекомпилировать ее. Что касается класса Error, то эти исключения очень трудно локализовать и на стадии компиляции невозможно определить место их появления.

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

Если метод или конструктор выбрасывает несколько исключений, то их надо перечислить через запятую после слова throws. Заголовок метода main () , если бы исключения, которые он выбрасывает, не были бы объектами подклассов класса RuntimeException, следовало бы написать так:

public static void main(String[] args)

throws ArithmeticException, ArrayIndexOutOfBoundsException{ 

// Содержимое метода 

}

Перенесем теперь обработку деления на нуль в метод f о и добавим трассировочную печать.

class SimpleExt3{

private static void f(int n){ // throws ArithmeticException{ 

try{

System.out.println(" 10 / n = " + (10 / n) ) ; 

System.out.printlnt"From f() after results output"); 

}catch(ArithmeticException ae)(

System.out.printlnf"From f() catch: " + ae) ; 

// throw ae; 

}finally{

System.out.println("From f() finally"); 

}

public static void main(String[] args){ 

try{

inf n = Integer.parselnt(args[0]); 

System.out.printlnt"After parselnt()); 

f (n);

System.out.println("After results output"); (

catch(ArithmeticException ae){

System.out.println("From Arithm.Exc. catch: "+ае); 

}catch(ArraylndexOutOfBoundsException arre)(

System.out.println("From Array.Exc. catch: "+arre); 

}finally{

System.out.println("From finally"); 

}

System.out.println("After all actions"); 

}

Внимательно проследите за передачей управления и заметьте, что исключение класса ArithmeticException уже не выбрасывается в метод main ().

Оператор try {}catch о {} в методе f о можно рассматривать как вложенный в оператор обработки исключений в методе main ().

При необходимости исключение можно выбросить оператором throw.


Оператор throw

Этот оператор очень прост: после слова throw через пробел записывается объект класса-исключения. Достаточно часто он создается прямо в операторе throw, например:

throw new ArithmeticException();

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

Итак, каждый блок catch о и перехватывает один определенный тип исключений. Если требуется одинаково обработать несколько типов исключений, то можно воспользоваться тем, что классы-исключения образуют иерархию.

class SimpleExt4(

public static void main(String[] args){

try{

int n = Integer.parselnt(args[0]);

System.out.println("After parselnt());

System.out.println(" 10 / n = " + (10 / n) ) ;

System.out.println("After results output"); 

}catch(RuntimeException ae){

System.out.println("From Run.Exc. catch: "+ae); 

}finally{

System.out.println("From finally"); 

}

System.out.println("After all actions"); 

}

В листинге два блока catch() {} заменены одним блоком, перехватывающим исключение класса RuntimeException. Как видно этот блок перехватывает оба исключения. Почему? Потому что это исключения подклассов класса RuntimeException.


Иерархия классов-исключений

Все классы-исключения расширяют класс Throwabie — непосредственное расширение класса object.

У класса Throwabie и у всех его расширений по традиции два конструктора: 

  • Throwabie о — конструктор по умолчанию;
  • Throwabie (String message) — создаваемый объект будет содержать произвольное сообщение message.

Записанное в конструкторе сообщение можно получить затем методом getMessage (). Если объект создавался конструктором по умолчанию, то данный метод возвратит null.

Метод tostringo возвращает краткое описание события, именно он работал в предыдущих листингах.

Три метода выводят сообщения обо всех методах, встретившихся по пути "полета" исключения:

  • printstackTrace() — выводит сообщения в стандартный вывод, как правило, это консоль;
  • printStackTrace(PrintStream stream) — выводит сообщения в байтовый поток stream;
  • printStackTrace(PrintWriter stream) — выводит сообщения в символьный поток stream.

У класса Throwabie два непосредственных наследника — классы Error и Exception. Они не добавляют новых методов, а служат для разделения классов-исключений на два больших семейства — семейство классов-ошибок (error) и семейство собственно классов-исключений (exception).

Классы-ошибки, расширяющие класс Error, свидетельствуют о возникновении сложных ситуаций в виртуальной машине Java. Их обработка требует глубокого понимания всех тонкостей работы JVM. Ее не рекомендуется выполнять в обычной программе. Не советуют даже выбрасывать ошибки оператором throw. He следует делать свои классы-исключения расширениями класса Error или какого-то его подкласса.

Имена классов-ошибок, по соглашению, заканчиваются словом Error.

Классы-исключения, расширяющие класс Exception, отмечают возникновение обычной нештатной ситуации, которую можно и даже нужно обработать. Такие исключения следует выбросить оператором throw. Классов-исключений очень много, более двухсот. Они разбросаны буквально по всем пакетам J2SDK. В большинстве случаев вы способны подобрать готовый класс-исключение для обработки исключительных ситуаций в своей программе. При желании можно создать и свой класс-исключение, расширив класс Exception или любой его подкласс.

Среди классов-исключений выделяется класс RuntimeException — прямое расширение класса Exception. В нем и его подклассах отмечаются исключения, возникшие при работе JVM, но не столь серьезные, как ошибки. Их можно обрабатывать и выбрасывать, расширять своими классами, но лучше

доверить это JVM, поскольку чаще всего это просто ошибка в программе, которую надо исправить. Особенность исключений данного класса в том, что их не надо отмечать в заголовке метода пометкой throws.

Имена классов-исключений, по соглашению, заканчиваются словом Exception.


Порядок обработки исключений

Блоки catch () {} перехватывают исключения в порядке написания этих блоков. Это правило приводит к интересным результатам.

Если в листинге после блока, перехватывающего RuntimeException, поместить блок, обрабатывающий выход индекса за пределы:

try{

// Операторы, вызывающие исключения 

}catch(RuntimeException re){

// Какая-то обработка 

}catch(ArrayindexOutofBoundsException ae){

// Никогда не будет выполнен! 

}

то он не будет выполняться, поскольку исключение этого типа является, к тому же, исключением общего типа RuntimeException и будет перехватываться Предыдущим блоком catch () {}.


Создание собственных исключений

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

Потом надо выбрать суперкласс создаваемого класса-исключения. Им может быть класс Exception или один из его многочисленных подклассов.

После этого можно написать класс-исключение. Его имя, по соглашению, должно завершаться словом Exception. Как правило, этот класс состоит только из двух конструкторов и переопределения методов tostringo и getMessageO.

Рассмотрим простой пример. Пусть метод handle(int cipher) обрабатывает арабские цифры 0—9, которые передаются ему в аргументе cipher типа int.

Мы хотим выбросить исключение, если аргумент cipher выходит за диапазон 0—9.

Прежде всего, убедимся, что такого исключения нет в иерархии классов Exception. Ко всему прочему, не отслеживается и более общая ситуация попадания целого числа в какой-то диапазон. Поэтому будем расширять наш класс. Назовем его cipherException, прямо от класса Exception.

class CipherException extends Exception{ 

private String msg; 

CipherException(){ msg = null;} 

CipherException(String s){ msg = s;} 

public String toString(){

return "CipherException (" + msg + "); 

class Except Demo(

static public void handle(int cipher) throws CipherException{ 

System.out.pnntln("handle()'s beginning"); 

if (cipher < 0 || cipher > 9)

throw new CipherException("" + cipher); 

System.out.println("handle()'s ending"); 

}

public static void main(String[] args){ 

try{

handle(1) ; 

handle(10); 

}catch(CipherException ce){

System.out.println("caught " + ce) ; 

ce.printStackTrace(); 

}


Класс Thread

В классе Thread семь конструкторов:

  • Thread(ThreadGroup group, Runnabie target, String name) — создает ПОД-процесс с именем name, принадлежащий группе group и выполняющий метод run о объекта target. Это основной конструктор, все остальные обращаются к нему с тем или иным параметром, равным null;
  • Thread () — создаваемый подпроцесс будет выполнять свой метод run ();
  • Thread(Runnabie target);
  • Thread(Runnabie target, String name);
  • Thread(String name);
  • Thread(ThreadGroup group, Runnabie target);
  • Thread(ThreadGroup group, String name).

Имя подпроцесса name не имеет никакого значения, оно не используется, виртуальной машиной Java и применяется только для различения подпроцессов в программе.

После создания подпроцесса его надо запустить методом start (). Виртуальная машина Java начнет выполнять метод run () этого объекта-подпроцесса.

Подпроцесс завершит работу после выполнения метода run (). Для уничтожения объекта-подпроцесса вслед за этим он должен присвоить значение null.

Выполняющийся подпроцесс можно приостановить статическим методом sleep (long ms) на ms миллисекунд. Этот метод мы уже использовали в предыдущих главах. Если вычислительная система может отсчитывать наносекунды, то можно приостановить подпроцесс с точностью до наносекунд методом sleep(long ms, int nanosec).

В листинге ниже приведен простейший пример. Главный подпроцесс создает два подпроцесса с именами Thread i и Thread 2, выполняющих один и тот же метод run (). Этот метод просто выводит 20 раз текст на экран, а затем сообщает о своем завершении.


 

class OutThread extends Thread{ 

private String msg; 

OutThread(String s, String name){

super(name); msg = s; 

public void run()

{

for(int i = 0; i < 20; i++){ 

// try{

// Thread.sleep(100); 

// }catch(InterruptedException ie){}

System.out.print(msg + " "); 

System.out.println("End of " + getName()); 

} class TwoThreads{

public static void main(String[] args){

new OutThread("HIP", "Thread 1").start(); 

new OutThread("hop", "Thread 2").start(); 

System.out.println(); 

}


Синхронизация подпроцессов

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

Классический пример — банковская транзакция, в которой изменяется остаток на счету клиента с номером numDep. Предположим, что для ее выполнения запрограммированы такие действия:

Deposit myDep = getDeposit(numDep); // Получаем счет с номером numDep 

int rest = myDep.getRest();         // Получаем остаток на счету myDep 

Deposit newDep = myDep.operate(rest, sum); // Изменяем остаток

                                           // на величину sum 

myDep.setDeposit(newDep); // Заносим новый остаток на счет myDep

Пусть на счету лежит 1000 рублей. Мы решили снять со счета 500 рублей, а в это же время поступил почтовый перевод на 1500 рублей. Эти действия выполняют разные подпроцессы, но изменяют они один и тот же счет myDep с номером numDep. Посмотрев еще раз, вы поверите, что последовательность действий может сложиться так. Первый подпроцесс проделает вычитание 1000-500, в это время второй подпроцесс выполнит все три действия и запишет на счет 1000+1500 = 2500 рублей, после чего первый подпроцесс выполнит свое последнее действие и у нас на счету окажется 500 рублей. Вряд ли вам понравится такое выполнение двух транзакций.

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

Все это делается одним оператором synchronized () {}, как показано ниже:

Deposit myDep = getDeposit(numDep); synchronized(myDep){

int rest = myDep.getRest();

Deposit newDep = myDep.operate(rest, sum);

myDep.setDeposit(newDep); 

}

В заголовке оператора synchronized в скобках указывается ссылка на объект, который будет заблокирован перед выполнением блока. Объект будет недоступен для других подпроцессов, пока выполняется блок. После выполнения блока блокировка снимается.

Если при написании какого-нибудь метода оказалось, что в блок synchronized входят все операторы этого метода, то можно просто пометить метод-словом synchronized, сделав его синхронизированным (synchronized):

synchronized int getRest()(

// Тело метода 

}

synchronized Deposit operate(int rest, int sum) { 

// Тело метода

}

synchronized void setDeposit(Deposit dep){

// Тело метода 

}

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

Приведем простейший пример. Метод run о в листинге выводит строку "Hello, World!" с задержкой в 1 секунду между словами. Этот метод выполняется двумя подпроцессами, работающими с одним объектом th. Программа выполняется два раза. Первый раз метод run () не синхронизирован, второй раз синхронизирован, его заголовок показан в листинге как комментарий.


 

class TwoThreads4 implements Runnable{ 

public void run(){

// synchronized public void run(){ 

System.out.print("Hello, "); 

try{

Thread.sleep(1000); 

}catch(InterruptedException ie){} 

System.out.println("World!"); 

}

public static void main(String[] args){ 

TwoThreads4 th = new TwoThreads4(); 

new Thread(th).start(); 

new Thread(th).start(); 

}

Действия, входящие в синхронизированный блок или метод образуют критический участок (critical section) программы. Несколько подпроцессов, собирающихся выполнять критический участок, встают в очередь. Это замедляет работу программы, поэтому для быстроты ее выполнения критических участков должно быть как можно меньше, и они должны быть как можно короче.


Согласование работы нескольких подпроцессов

Возможность создания многопоточных программ заложена в язык Java с самого его создания. В каждом объекте есть три метода wait о и один метод notify о, позволяющие приостановить работу подпроцесса с этим объектом, позволить другому подпроцессу поработать с объектом, а затем уведомить (notify) первый подпроцесс о возможности продолжения работы. Эти методы определены прямо в классе object и наследуются всеми классами.

С каждым объектом связано множество подпроцессов, ожидающих доступа к объекту (wait set). Вначале этот "зал ожидания" пуст.

Основной метод wait (long miiiisec) приостанавливает текущий подпроцесс this, работающий с объектом, на miiiisec миллисекунд и переводит его в "зал ожидания", в множество ожидающих подпроцессов. Обращение к этому методу допускается только в синхронизированном блоке или методе, чтобы быть уверенными в том, что с объектом работает только один подпроцесс. По истечении miiiisec или после того, как объект получит уведомление методом notify о, подпроцесс готов возобновить работу. Если аргумент miiiisec равен о, то время ожидания не определено и возобновление работы подпроцесса возможно только после того, как объект получит уведомление методом notify().

Отличие данного метода от метода sleep о в том, что метод wait о снимает блокировку с объекта. С объектом может работать один из подпроцессов из "зала ожидания", обычно тот, который ждал дольше всех, хотя это не гарантируется спецификацией JLS.

Второй метод wait () эквивалентен wait (0). Третий метод wait (long millisec, int nanosec) уточняет задержку на nanosec наносекунд, если их сумеет отсчитать операционная система.

Метод notify () выводит из "зала ожидания" только один, произвольно выбранный подпроцесс. Метод notifyAll() выводит из состояния ожидания все подпроцессы. Эти методы тоже должны выполняться в синхронизированном блоке или методе.

Как же применить все это для согласованного доступа к объекту? Как всегда, лучше всего объяснить это на примере.

Обратимся снова к схеме "поставщик-потребитель".Один подпроцесс, поставщик, производит вычисления, другой, потребитель, ожидает результаты этих вычислений и использует их по мере поступления. Подпроцессы передают информацию через общий экземпляр st класса store.

Работа этих подпроцессов должна быть согласована. Потребитель обязан ждать, пока поставщик не занесет результат вычислений в объект st, а поставщик должен ждать, пока потребитель не возьмет этот результат.

Для простоты поставщик просто заносит в общий объект класса store целые числа, а потребитель лишь забирает их. Несогласованные подпроцессы

class Store{

private inf inform;

synchronized public int getlnform(){ return inform; } 

synchronized public void setlnform(int n){ inform = n; } 

}

class Producer implements Runnable{ 

private Store st; 

private Thread go; 

Producer(Store st){ 

this.st = st; 

go = new Thread(this); 

go.start(); 

}

public void run(){ 

int n = 0;

Thread th = Thread.currentThread(); 

while(go == th){ 

st.setlnform(n);

System.out.print("Put: " + n + " "); 

n++; 

}

public void stop(){ go = null; 

}

class Consumer implements Runnable{ 

private Store st; 

private Thread go; 

Consumer(Store st){

this.st = st; 

go =-new Thread(this); 

go.start () ; 

public void run(){

Thread th = Thread.currentThread();

while(go == th) System.out.println("Got: " + st.getlnformf)); 

}

public void stop(){ go = null; } 

class ProdCons{

public static void main(String[] args){ 

Store st = new Store(); 

Producer p = new Producer(st); 

Consumer с = new Consumer(st); 

try{

Thread.sleep(30); 

}catch(InterruptedException ie){} 

p.stop(); c.stop(); 

}

В следующем листинге в класс store внесено логическое поле ready, отмечающее процесс получения и выдачи информации. Когда новая порция информации получена от поставщика Producer, в поле ready заносится значение true, получатель consumer может забирать эту порцию информации. После выдачи информации переменная ready становится равной false.

Но этого мало. То, что получатель может забрать продукт, не означает, что он действительно заберет его. Поэтому в конце метода setinformf) получатель уведомляется о поступлении продукта методом notify о. Пока поле ready не примет нужное значение, подпроцесс переводится в "зал ожидания" методом wait ().

class Store{

private int inform = -1;

private boolean ready; 

synchronized public int getlnform(){ 

try{

if (! ready) wait(); 

ready = false; 

return inform;

}catch(InterruptedException ie){ 

}finally!

notify(); 

}

return -1; 

}

synchronized public void setlnform(int n)( 

if (ready) 

try{

wait ();

}catch(InterruptedException ie){} 

inform = n; 

ready = true; 

notify(); 

}

Поскольку уведомление поставщика в методе getinformo должно происходить уже после отправки информации оператором return inform, оно включено В блок finally{}


Приоритеты подпроцессов

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

В классе Thread есть три целые статические константы, задающие приоритеты:

  • NORM_PRIORITY — обычный приоритет, который получает каждый подпроцесс при запуске, его числовое значение 5;
  • MIN_PRIORITY — наименьший приоритет, его значение 1; 
  • MAX_PRIORITY — наивысший приоритет, его значение 10.

Кроме этих значений можно задать любое промежуточное значение от 1 до 10, но надо помнить о том, что процессор будет переключаться между подпроцессами с одинаковым высшим приоритетом, а подпроцессы с меньшим приоритетом не станут выполняться, если только не приостановлены все подпроцессы с высшим приоритетом. Поэтому для повышения общей производительности следует приостанавливать время от времени методом sleep о подпроцессы с высоким приоритетом.

Установить тот или иной приоритет можно в любое время методом setPriorityfint newPriority), если подпроцесс имеет право изменить свой приоритет. Проверить наличие такого права можно методом checkAtcess(). Этот метод выбрасывает исключение класса Security&xception, если подпроцесс не может изменить свой приоритет.

Порожденные подпроцессы будут иметь тот же приоритет, что и подпроцесс-родитель.

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


Подпроцессы-демоны

Работа программы начинается с выполнения метода main о главным подпроцессом. Этот подпроцесс может породить другие подпроцессы, они, в свою очередь, способны породить свои подпроцессы. После этого главный подпроцесс ничем не будет отличаться от остальных подпроцессов. Он не следит за порожденными им подпроцессами, не ждет от них никаких сигналов. Главный подпроцесс может завершиться, а программа будет продолжать работу, пока не закончит работу последний подпроцесс.

Это правило не всегда удобно. Например, какой-то из подпроцессов может приостановиться, ожидая сетевого соединения, которое никак не может наступить. Пользователь, не дождавшись соединения, прекращает работу главного подпроцесса, но программа продолжает работать.

Такие случаи можно учесть, объявив некоторые подпроцессы демонами (daemons). Это понятие не совпадает с понятием демона в UNIX. Просто программа завершается по окончании работы последнего пользовательского (user) подпроцесса, не дожидаясь окончания работы демонов. Демоны будут принудительно завершены исполняющей системой Java.

Объявить подпроцесс демоном можно сразу после его создания, перед запуском. Это делается методом setoaemon(true). Данный метод обращается к методу checkAccess () И МОЖEТ выбросить SecurityException.

Изменить статус демона после запуска процесса уже нельзя.

Все подпроцессы, порожденные демоном, тоже будут демонами. Для изменения их статуса необходимо обратиться к методу setoaemon(false).


Группы подпроцессов

Подпроцессы объединяются в группы. В начале работы программы исполняющая система Java создает группу подпроцессов с именем main. Все подпроцессы по умолчанию попадают в эту группу.

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

ThreadGroup(String name)

При этом группа получает имя, заданное аргументом name. Затем этот экземпляр указывается при создании подпроцессов в конструкторах класса Thread. Все подпроцессы попадут в группу с именем, заданным при создании группы.

Группы подпроцессов могут образовать иерархию. Одна группа порождается от другой конструктором

ThreadGroup(ThreadGroup parent, String name)

Группы подпроцессов используются главным образом для задания приоритетов подпроцессам внутри группы. Изменение приоритетов внутри группы не будет влиять на приоритеты подпроцессов вне иерархии этой группы. Каждая группа имеет максимальный приоритет, устанавливаемый методом setMaxPriorityfint maxPri) класса ThreadGroup. Ни один подпроцесс из этой группы не может превысить значения maxPri, но приоритеты подпроцессов, заданные до установки maxPri, не меняются.


Потоки ввода и вывода

Для того чтобы отвлечься от особенностей конкретных устройств ввода/вывода, в Java употребляется понятие потока (stream). Считается, что в программу идет входной поток (input stream) символов Unicode или просто байтов, воспринимаемый в программе методами read(). Из программы методами write о или print (), println() выводится выходной поток (output stream) символов или байтов. При этом неважно, куда направлен поток: на консоль, на принтер, в файл или в сеть, методы write () и print () ничего об этом не знают.

Можно представить себе поток как трубу, по которой в одном направлении последовательно "текут" символы или байты, один за другим. Методы read () , write () , print (), println () взаимодействуют с одним концом трубы, другой конец соединяется с источником или приемником данных конструкторами классов, в которых реализованы эти методы. 

Конечно, полное игнорирование особенностей устройств ввода/вывода сильно замедляет передачу информации. Поэтому в Java все-таки выделяется файловый ввод/вывод, вывод на печать, сетевой поток.

Три потока определены в классе system статическими полями in, out и err. Их можно использовать без всяких дополнительных определений. Они называются соответственно стандартным вводом (stdin), стандартным выводом (stdout) и стандартным выводом сообщений (stderr). Эти стандартные потоки могут быть соединены с разными конкретными устройствами ввода и вывода.

Потоки out и err — это экземпляры класса Printstream, организующего выходной поток байтов. Эти экземпляры выводят информацию на консоль методами print (), println () и write (), которых в классе Printstream имеется около двадцати для разных типов аргументов.

Поток err предназначен для вывода системных сообщений программы: трассировки, сообщений об ошибках или, просто, о выполнении каких-то этапов программы. Такие сведения обычно заносятся в специальные журналы, log-файлы, а не выводятся на консоль. В Java есть средства переназначения потока, например, с консоли в файл.

Поток in — это экземпляр класса inputstream. Он назначен на клавиатурный ввод с консоли методами read(). Класс inputstream абстрактный, поэтому реально используется какой-то из его подклассов.

Понятие потока оказалось настолько удобным и облегчающим программирование ввода/вывода, что в Java предусмотрена возможность создания потоков, направляющих символы или байты не на внешнее устройство, а в массив или из массива, т. е. связывающих программу с областью оперативной памяти. Более того, можно создать поток, связанный со строкой типа string, находящейся, опять-таки, в оперативной памяти. Кроме того, можно создать канал (pipe) обмена информацией между подпроцессами.

Еще один вид потока — поток байтов, составляющих объект Java. Его можно направить в файл или передать по сети,'а потом восстановить в оперативной памяти. Эта операция называется сериализацией (serialization) объектов.

Методы организации потоков собраны в классы пакета java.io.

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

Еще одна возможность, предоставляемая классами пакета java.io, — слить несколько потоков в один поток.

Итак, в Java есть целых четыре иерархии классов для создания, преобразования и слияния потоков. Во главе иерархии четыре класса, непосредственно расширяющих класс object:

  • Reader — абстрактный класс, в котором собраны самые общие методы символьного ввода;
  • writer — абстрактный класс, в котором собраны самые общие методы символьного вывода;
  • inputstream — абстрактный класс с общими методами байтового ввода; 
  • Outputstream — абстрактный класс с общими методами байтового вывода.

Классы входных потоков Reader и inputstream определяют по три метода ввода:

  • read () — возвращает один символ или байт, взятый из входного потока, в виде целого значения типа int; если поток уже закончился, возвращает -1;
  • read (chart] buf) — заполняет заранее определенный массив buf символами из входного потока; в классе inputstream массив типа bytet] и заполняется он байтами; метод возвращает фактическое число взятых из потока элементов или -1, если поток уже закончился;
  • read (char[] buf, int offset, int len) — заполняет часть символьного или байтового массива buf, начиная с индекса offset, число взятых из потока элементов равно len; метод возвращает фактическое число взятых из потока элементов или -1.

Эти методы выбрасывают IOException, если произошла ошибка ввода/вывода.

Четвертый метод skip (long n) "проматывает" поток с текущей позиции на п символов или байтов вперед. Эти элементы потока не вводятся методами read(). Метод возвращает реальное число пропущенных элементов, которое может отличаться от п, например поток может закончиться.

Текущий элемент потока можно пометить методом mark (int n), а затем вернуться к помеченному элементу методом reset о, но не более чем через п элементов. Не все подклассы реализуют эти методы, поэтому перед расстановкой пометок следует обратиться к логическому методу marksupported (), который возвращает true, если реализованы методы расстановки и возврата к пометкам.

Классы выходных потоков writer и outputstream определяют по три почти одинаковых метода вывода:

  • write (char[] buf) — выводит массив в выходной поток, в классе Outputstream массив имеет тип byte[];
  • write (char[] buf, int offset, int len) — выводит len элементов массива buf, начиная с элемента с индексом offset;
  • write (int elem) в классе Writer выводит 16, а в классе Outputstream 8 младших битов аргумента elem в выходной поток, 

В классе writer есть еще два метода: 

  • write (string s) — выводит строку s в выходной поток;
  • write (String s, int offset, int len) — выводит len символов строки s, начиная с символа с номером offset.

Многие подклассы классов writer и outputstream осуществляют буферизованный вывод. При этом элементы сначала накапливаются в буфере, в оперативной памяти, и выводятся в выходной поток только после того, как буфер заполнится. Это удобно для выравнивания скоростей вывода из программы и вывода потока, но часто надо вывести информацию в поток еще до заполнения буфера. Для этого предусмотрен метод flush о. Данный метод сразу же выводит все содержимое буфера в поток.

Наконец, по окончании работы с потоком его необходимо закрыть методом closed.

Все классы пакета java.io можно разделить на две группы: классы, создающие поток (data sink), и классы, управляющие потоком (data processing).

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

  •  классы, создающие потоки, связанные с файлами:

    FileReader        FilelnputStream 

    FileWriterFile    Outputstream

                      RandomAccessFile

  • классы, создающие потоки, связанные с массивами:

    CharArrayReader   ByteArraylnputStream 

    CharArrayWriter   ByteArrayOutputStream

  • классы, создающие каналы обмена информацией между подпроцессами:

    PipedReader     PipedlnputStream 

    PipedWriter     PipedOutputStream

  • классы, создающие символьные потоки, связанные со строкой:

    StringReader    

    StringWriter

  • классы, создающие байтовые потоки из объектов Java:

                             ObjectlnputStream 

                            ObjectOutputStream

Слева перечислены классы символьных потоков, справа — классы байтовых потоков.

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

Четыре класса созданы специально для преобразования потоков:

FilterReader        FilterlnputStream 

FilterWriter        FilterOutputStream

Сами по себе эти классы бесполезны — они выполняют тождественное преобразование. Их следует расширять, переопределяя методы ввода/вывода. Но для байтовых фильтров есть полезные расширения, которым соответствуют некоторые символьные классы. Перечислим их.

Четыре класса выполняют буферизованный ввод/вывод:

BufferedReader         BufferedlnputStream 

BufferedWriter         BufferedOutputStream

Два класса преобразуют поток байтов, образующих восемь простых типов Java, в эти самые типы:

DatalnputStream        DataOutputStream

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

PushbackReader         PushbacklnputStream

Два класса связаны с выводом на строчные устройства — экран дисплея, принтер:

PrintWriter             PrintStream

Два класса связывают байтовый и символьный потоки:

  • inputstreamReader — преобразует входной байтовый поток в символьный поток;
  • Outputstreamwriter — преобразует выходной символьный поток в байтовый поток.

Класс streamTokenizer позволяет разобрать входной символьный поток на отдельные элементы (tokens) подобно тому, как класс stringTokenizer, рассмотренный нами в главе 5, разбирал строку.

Из управляющих классов выделяется класс sequenceinputstream, сливающий несколько потоков, заданных в конструкторе, в один поток, и класс

LineNumberReader, "умеющий" читать выходной символьный поток построчно. Строки в потоке разделяются символами '\n' и/или '\г'.

Этот обзор классов ввода/вывода немного проясняет положение, но не объясняет, как их использовать. Перейдем к рассмотрению реальных ситуаций.


Консольный ввод/вывод

Для вывода на консоль мы всегда использовали метод printino класса Pnntstream, никогда не определяя экземпляры этого класса. Мы просто использовали статическое поле out класса system, которое является объектом класса PrintStream. Исполняющая система Java связывает это поле с консолью.

Кстати говоря, если вам надоело писать system.out.printino, то вы можете определить новую ссылку на system, out, например:

PrintStream pr - System.out;

и писать просто pr. printin ().

Консоль является байтовым устройством, и символы Unicode перед выводом на консоль должны быть преобразованы в байты. Для символов Latin 1 с кодами '\u0000' — '\u00FF' при этом просто откидывается нулевой старший байт и выводятся байты '0х00' —'0xFF'. Для кодов кириллицы, которые лежат в диапазоне '\u0400 1 —'\u04FF 1 кодировки Unicode, и других национальных алфавитов производится преобразование по кодовой таблице, соответствующей установленной на компьютере л окал и.

Трудности с отображением кириллицы возникают, если вывод на консоль производится в кодировке, отличной от локали. Именно так происходит в русифицированных версиях MS Windows NT/2000. Обычно в них устанавливается локаль с кодовой страницей СР1251, а вывод на консоль происходит в кодировке СР866.

В этом случае надо заменить Printstream, который не может работать с сим- , вольным потоком, на Printwriter и "вставить переходное кольцо" между потоком символов Unicode и потоком байтов system, out, выводимых на консоль, в виде объекта класса OutputstreamWriter. В конструкторе этого объекта следует указать нужную кодировку, в данном случае, СР866. Все это можно сделать одним оператором:

PrintWriter pw = new PrintWriter(

new OutputstreamWriter(System.out, "Cp866"), true);

Класс Printstream буферизует выходной поток. Второй аргумент true его конструктора вызывает принудительный сброс содержимого буфера в выходной поток после каждого выполнения метода printin(). Но после print() буфер не сбрасывается! Для сброса буфера после каждого print() надо писать flush().

Замечание

Методы класса PrintWriter по умолчанию не очищают буфер, а метод print () не очищает его в любом случае. Для очистки буфера используйте метод flush().

После этого можно выводить любой текст методами класса PrintWriter, которые просто дублируют методы класса Printstream, и писать, например,

pw.println("Это русский текст");

 

Следует заметить, что конструктор класса PrintWriter, в котором задан байтовый поток, всегда неявно создает объект класса OutputstreamWriter с локальной кодировкой для преобразования байтового потока в символьный поток.

Ввод с консоли производится методами read о класса inputstream с помощью статического поля in класса system. С консоли идет поток байтов, полученных из scan-кодов клавиатуры. Эти байты должны быть преобразованы в символы Unicode такими же кодовыми таблицами, как и при выводе на консоль. Преобразование идет по той же схеме — для правильного ввода кириллицы удобнее всего определить экземпляр класса BufferedReader, используя в качестве "переходного кольца" объект класса inputstreamReader:

BufferedReader br = new BufferedReader(

new InputstreamReader(System.an, "Cp866"));

Класс BufferedReader переопределяет три метода read о своего суперкласса Reader. Кроме того, он содержит метод readLine ().

Метод readLine о возвращает строку типа string, содержащую символы входного потока, начиная с текущего, и заканчивая символом '\п' и/или '\r'. Эти символы-разделители не входят в возвращаемую строку. Если во входном потоке нет символов, то возвращается null.

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

import j ava.io.*;

class PrWr{

public static void main(String[] args){ 

try{

BufferedReader br =

new BufferedReader(new InputstreamReader(System.in, "Cp866")); 

PrintWriter pw = new PrintWriter(

new OutputstreamWriter(System.out, "Cp866"), true); 

String s = "Это строка с русским текстом"; 

System.out.println("System.out puts: " + s); 

pw.println("PrintWriter puts: " + s) ; 

int с = 0;

pw.println("Посимвольный ввод:"); 

while((с = br.read()) != -1)

pw.println((char)c); 

pw.println("Построчный ввод:"); 

do{

s = br.readLine(); 

pw.println(s); 

}while(!s.equals("q")); 

}catch(Exception e){

System.out.println(e); 

}

Первая строка выводится потоком system.out. Как видите, кириллица выводится неправильно. Следующая строка предварительно преобразована в поток байтов, записанных в кодировке СР866.

Затем, после текста "Посимвольный ввод:" с консоли вводятся символы "Россия" и нажимается клавиша <Enter>. Каждый вводимый символ отображается на экране — операционная система работает в режиме так называемого "эха". Фактический ввод с консоли начинается только после нажатия клавиши <Enter>, потому что клавиатурный ввод буферизуется операционной системой. Символы сразу после ввода отображаются по одному на строке. Обратите внимание на две пустые строки после буквы я. Это выведены символы '\п' и '\г', которые попали во входной поток при нажатии клавиши <Enter>. У них нет никакого графического начертания (glyph).

Потом нажата комбинация клавиш <Ctrl>+<Z>. Она отображается на консоль как "^Z" и означает окончание клавиатурного ввода, завершая цикл ввода символов. Коды этих клавиш уже не попадают во входной поток.

Далее, после текста "Построчный ввод:" с клавиатуры набирается строка "Это строка" и, вслед за нажатием клавиши <Enter>, заносится в строку s. Затем строка s выводится обратно на консоль.

Для окончания работы набираем q и нажимаем клавишу <Enter>.


Файловый ввод/вывод

Поскольку файлы в большинстве современных операционных систем понимаются как последовательность байтов, для файлового ввода/вывода создаются байтовые потоки с помощью классов Fiieinputstream и FiieOutputstream. Это особенно удобно для бинарных файлов, хранящих байт-коды, архивы, изображения, звук.

Но очень много файлов содержат тексты, составленные из символов. Несмотря на то, что символы могут храниться в кодировке Unicode, эти тексты чаще всего записаны в байтовых кодировках. Поэтому и для текстовых файлов можно использовать байтовые потоки. В таком случае со стороны программы придется организовать преобразование байтов в символы и обратно.

Чтобы облегчить это преобразование, в пакет java.io введены классы FineReader и FileWriter. Они организуют преобразование потока: со стороны программы потоки символьные, со стороны файла — байтовые. Это происходит потому, что данные классы расширяют классы InputStreamReader и OutputstreamWriter, соответственно, значит, содержат "переходное кольцо" внутри себя.

Несмотря на различие потоков, использование классов файлового ввода/вывода очень похоже.

В конструкторах всех четырех файловых потоков задается имя файла в виде строки типа string или ссылка на объект класса File. Конструкторы не только создают объект, но и отыскивают файл и открывают его. Например:

Fileinputstream fis = new FilelnputStreamC'PrWr.Java"); 

FileReader fr = new FileReader("D:\\jdkl.3\\src\\PrWr.Java");

При неудаче выбрасывается исключение класса FileNotFoundException, но конструктор класса FileWriter выбрасывает более общее исключение IOException.

После открытия выходного потока типа FileWriter или FileQutputStEeam содержимое файла, если он был не пуст, стирается. Для того чтобы можно было делать запись в конец файла, и в том и в другом классе предусмотрен конструктор с двумя аргументами. Если второй аргумент равен true, то происходит дозапись в конец файла, если false, то файл заполняется новой информацией. Например:

FileWriter fw = new FileWriter("ch!8.txt", true);

FileOutputstream fos = new FileOutputstream("D:\\samples\\newfile.txt");

Внимание

Содержимое файла, открытого на запись конструктором с одним аргументом, стирается.

Сразу после выполнения конструктора можно читать файл:

fis.read(); fr.read();

или записывать в него:

fos.write((char)с); fw.write((char)с);

По окончании работы с файлом поток следует закрыть методом close ().

Преобразование потоков в классах FileReader и FileWriter выполняется по кодовым таблицам установленной на компьютере локали. Для правильного ввода кирилицы надо применять FileReader, a нe FileInputStream. Если файл содержит текст в кодировке, отличной от локальной кодировки, то придется вставлять "переходное кольцо" вручную, как это делалось для консоли, например:

InputStreamReader isr = new InputStreamReader(fis, "KOI8_R"));

Байтовый поток fis определен выше.


Получение свойств файла

В конструкторах классов файлового ввода/вывода, описанных в предыдущем разделе, указывалось имя файла в виде строки. При этом оставалось неизвестным, существует ли файл, разрешен ли к, нему доступ, какова длина файла.

Получить такие сведения можно от предварительно созданного экземпляра класса File, содержащего сведения о файле. В конструкторе этого класса

File(String filename)

указывается путь к файлу или каталогу, записанный по правилам операционной системы. В UNIX имена каталогов разделяются наклонной чертой /, в MS Windows — обратной наклонной чертой \, в Apple Macintosh — двоеточием :. Этот символ содержится в системном свойстве file.separator. Путь к файлу предваряется префиксом. В UNIX это наклонная черта, в MS Windows — буква раздела диска, двоеточие и обратная наклонная черта. Если префикса нет, то путь считается относительным и к нему прибавляется путь к текущему каталогу, который хранится в системном свойстве user.dir.

Конструктор не проверяет, существует ли файл с таким именем, поэтому после создания объекта следует это проверить логическим методом exists ().

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

Прежде всего, логическими методами isFileO, isDirectoryO можно выяснить, является ли путь, указанный в конструкторе, путем к файлу или каталогу.

Для каталога можно получить его содержимое — список имен файлов и подкаталогов— методом list о, возвращающим массив строк stringf]. Можно получить такой же список в виде массива объектов класса File[] методом listFilest). Можно выбрать из списка только некоторые файлы, реализовав интерфейс FileNameFiiter и обратившись к методу

list(FileNameFilter filter).

Если каталог с указанным в конструкторе путем не существует, его можно создать логическим методом mkdir(). Этот метод возвращает true, если каталог удалось создать. Логический метод mkdirso создает еще и все несуществующие каталоги, указанные в пути.

Пустой каталог удаляется методом delete ().

Для файла можно получить его длину в байтах методом length (), время последней модификации в секундах с 1 января 1970 г. методом lastModifiedo. Если файл не существует, эти методы возвращают нуль.

Логические методы canRead (), canwrite () показывают права доступа к файлу.

Файл можно переименовать логическим методом renameTo(Fiie newMame) или удалить логическим методом delete о. Эти методы возвращают true, если операция прошла удачно.

Если файл с указанным в конструкторе путем не существует, его можно создать логическим методом createNewFilet), возвращающим true, если файл не существовал, и его удалось создать, и false, если файл уже существовал.

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

createTempFile(String prefix, String suffix, File tmpDir) 

createTempFile(String prefix, String suffix)

можно создать временный файл с именем prefix и расширением suffix в каталоге tmpDir или каталоге, указанном в системном свойстве java.io.tmpdir. Имя prefix должно содержать не менее трех символов. Если suffix = null, то файл получит суффикс .tmp.

Перечисленные методы возвращают ссылку типа File на созданный файл. Если обратиться к методу deieteOnExit (), то по завершении работы JVM временный файл будет уничтожен.

Несколько методов getxxxo возвращают имя файла, имя каталога и другие сведения о пути к файлу. Эти методы полезны в тех случаях, когда ссылка на объект класса File возвращается другими методами и нужны сведения о файле. Наконец, метод toURL () возвращает путь к файлу в форме URL.

Ниже показан пример использования класса File.

import java.io.*;

class FileTest{

public static void main(String[] args) throws IOException{ 

PrintWriter pw = new PrintWriter(

new OutputStreamWriter(System.out, "Cp866"), true); 

File f = new File("FileTest.Java"); 

pw.println();

pw.println("Файл \"" + f.getName() + "\" " + 

(f.exists()?"":"не ") + "существует");

pw.println("Вы " + (f.canRead()?"":"не ") + "можете читать файл"); 

pw.println("Вы " + (f.canWrite()?"":"нe ") +

"можете записывать в файл"); 

pw.println("Длина файла " + f.length() + " б");

pw.println() ;

File d = new File(" D:\\jdkl.3\\MyProgs "); 

pw.println("Содержимое каталога:"); 

if (d.exists() && d.isDirectory()) { 

String[] s = d.list(); 

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

pw.println(s[i]); 

}


Поток простых типов Java

Класс DataOutputstream позволяет записать данные простых типов Java в выходной поток айтов методами writeBoolean (boolean b), writeBytefint b), writeShort(int h), writeChar(int c), writelnt"(int n), writeLong(long 1), writeFloat(float f), writeDouble(double d).

Кроме того, метод writeBytes(string s) записывает каждый символ строки s в один байт, отбрасывая старший байт кодировки каждого символа Unicode, а метод writecnarststring s) записывает каждый символ строки s в два байта, первый байт — старший байт кодировки Unicode, так же, как это делает метод writeChar ().

Еще один метод writeUTFtstring s) записывает строку s в выходной поток в кодировке UTF-8. Надо пояснить эту кодировку.


Кодировка UTF-8

Запись потока в байтовой кодировке вызывает трудности с использованием национальных символов, запись потока в Unicode увеличивает длину потока в два раза. Кодировка UTF-8 (Universal Transfer Format) является компромиссом. Символ в этой кодировке записывается одним, двумя или тремя байтами.

Символы Unicode из диапазона '\u0000' —'\u007F', в котором лежит английский алфавит, записываются одним байтом, старший байт просто отбрасывается.

Символы Unicode из диапазона '\u0080' —'\u07FF', в котором лежат наиболее распространенные символы национальных алфавитов, записываются двумя байтами следующим образом: символ Unicode с кодировкой 00000хххххуууууу записывается как 110ххххх10уууууу.

Остальные символы Unicode из диапазона '\u0800' —'\UFFFF' записываются тремя байтами по следующему правилу: символ Unicode с кодировкой xxxxyyyyyyzzzzzz записывается как 1110xxxx10yyyyyy10zzzzzz.

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

Так вот, метод writeUTF( string s) сначала записывает в поток в первые два байта потока длину строки s в кодировке UTF-8, а затем символы строки в этой кодировке. Читать эту запись потом следует парным методом readUTF() класса DatalnputStream.

Класс DatalnputStream преобразует входной поток байтов типа InputStream, составляющих данные простых типов Java, в данные этого типа. Такой поток, как правило, создается методами класса DataOutputstream. Данные из этого потока можно прочитать методами readBoolean(), readByte(), readShort(), readChar(), readlnt(), readLong(), readFloat(), readDouble(), возвращающими данные соответствующего типа.

Кроме того, методы readUnsignedByteO H readUnsignedShort () возвращают целое типа int, в котором старшие три или два байта нулевые, а младшие один или два байта заполнены байтами из входного потока.

Метод readUTF(), двойственный методу writeUTF(), возвращает строку типа string, полученную из потока, записанного методом writeUTF ().

Еще один, статический, метод readUTF(Datainput in) делает то же самое со входным потоком in, записанным в кодировке UTF-8. Этот метод можно применять, не создавая объект класса DatalnputStream.


Прямой доступ к файлу

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

В конструкторах этого класса

RandomAccessFile(File file, String mode) 

RandomAccessFile(String fileName, String mode)

вторым аргументом mode задается режим открытия файла. Это может быть строка "r" — открытие файла только для чтения, или "rw" — открытие файла для чтения и записи.

Этот класс собрал все полезные методы работы с файлом. Он содержит все методы классов Datainputstream и DataOutputstream, кроме того, позволяет прочитать сразу целую строку методом readidne () и отыскать нужные данные в файле.

Байты файла нумеруются, начиная с 0, подобно элементам массива. Файл снабжен неявным указателем (file pointer) текущей позиции. Чтение и запись производится, начиная с текущей позиции файла. При открытии файла конструктором указатель стоит на начале файла, в позиции 0. Текущую позицию можно узнать методом getFiiePointer(). Каждое чтение или запись перемещает указатель на длину прочитанного или записанного данного. Всегда можно переместить указатель в новую позицию, роз методом seek (long pos). Метод seek(0) перемещает указатель на начало файла.

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


Каналы обмена информацией

В предыдущей главе мы видели, каких трудов стоит организовать правильный обмен информацией между подпроцессами. В пакете java.io есть четыре класса pipedxxx, облегчающие эту задачу.

В одном подпроцессе — источнике информации — создается объект класса PipedWriter+ или PipedOutputstream, в который записывается информация методами write () этих классов.

В другом .подпроцессе —приемнике информации — формируется объект класса PipedReader или Pipedinputstream. Он связывается с объектом-источником с помощью конструктора или специальным методом connect (), и читает информацию методами read ().

Источник и приемник можно создать и связать в обратном порядке.

Так создается однонаправленный канал (pipe) информации. На самом деле это некоторая область оперативной памяти, к которой организован совместный доступ двух или более подпроцессов. Доступ синхронизируется, записывающие процессы не могут помешать чтению.

Если надо организовать двусторонний обмен информацией, то создаются два канала.

В листинге ниже метод run о класса source генерирует информацию, для простоты просто целые числа k, и передает £е в канал методом pw. write (k). Метод run() класса Target читает информацию из канала методом pr.read(). Концы канала связываются с помощью конструктора класса Target.

import java.io.*;

class Target extends Thread{ 

private PipedReader pr; 

Target(PipedWriter pw){ 

try{

pr = new PipedReader(pw); 

}catch(lOException e){

System.err.println("From Target(): " + e); 

}

PipedReader getStream(){ return pr;} 

public void run(){ 

while(true) 

try{

System.out.println("Reading: " + pr.read()); 

}catch(IOException e){

System.out.println("The job's finished."); 

System.exit(0); 

}

class Source extends Thread{ 

private PipedWriter pw; 

Source (){

pw = new PipedWriter(); 

}

PipedWriter getStream(){ return pw;} 

public void run(){

for (int k = 0; k < 10; k++)

try{

pw.write(k);

System.out.println("Writing: " + k); 

}catch(Exception e){

System.err.printlnf"From Source.run(): " + e) ; 

class PipedPrWr{

public static void main(String[] args){ 

Source s = new Source(); 

Target t = new Target(s.getStream()); 

s.start(); 

t.start(); 

)


Сериализация объектов

Методы классов ObjectlnputStream и ObjectOutputStream позволяют прочитать из входного байтового потока или записать в выходной байтовый поток данные сложных типов — объекты, массивы, строки — подобно тому, как методы классов Datainputstream и DataOutputstream читают и записывают данные простых типов.

Сходство усиливается- тем, Что классы Objeetxxx содержат методы как для чтений, так и записи простых типов. Впрочем, эти методы предназначены не для использования в программах, а для записи/чтения полей объектов и элементов массивов.

Процесс записи объекта в выходной поток получил название сериализации (serialization), а чтения объекта из входного потока и восстановления его в оперативной памяти — десериализации (deserialization).

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

Поэтому сериализации можно подвергнуть не каждый объект, а только тот, который реализует интерфейс seriaiizabie. Этот интерфейс не содержит ни полей, ни методов. Реализовать в нем нечего. По сути дела запись

class A implements Seriaiizabie{...}

это только пометка, разрешающая сериализацию класса А.

Как всегда в Java, процесс сериализации максимально автоматизирован. Достаточно создать объект класса ObjectOutputStream, связав его с выходным потоком, и выводить в этот поток объекты методом writeObject():

MyClass me = new MyClass("abc", -12, 5.67e-5);

int[] arr = {10, 20, 30};

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream("myobjects.ser")) ; 

oos.writeObject(me); 

oos.writeObject(arr); 

oos.writeObject("Some string"); 

oos.writeObject (new Date()); 

oos.flush();

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

Если в объекте присутствуют ссылки на другие объекты, то они тоже сериализуются, а в них могут быть ссылки на другие объекты, которые опять-таки сериализуются, и получается целое множество причудливо связанных между собой сериализуемых объектов. Метод writeObjecto распознает две ссылки на один объект и выводит его в выходной поток только один раз. К тому же, он распознает ссылки, замкнутые в кольцо, и избегает зацикливания.

Все классы объектов, входящих в такое сериализуемое множество, а также все их внутренние классы, должны реализовать интерфейс seriaiizabie, в противном случае будет выброшено исключение класса NotseriaiizabieException и процесс сериализации прервется. Многие классы J2SDK реализуют этот интерфейс. Учтите также, что все потомки таких классов наследуют реализацию. Например, класс java.awt.Component реализует интерфейс Serializable, значит, все графические компоненты можно сериализовать. Не реализуют этот интерфейс обычно классы, тесно связанные с выполнением программ, например, java.awt.Toolkit. Состояние экземпляров таких классов нет смысла сохранять или передавать по сети. Не реализуют интерфейс Serializable и классы, содержащие внутренние сведения Java "для служебного пользования".

Десериализация происходит так же просто, как и сериализация:

ObjectlnputStream ois = new ObjectInputStream(

new FilelnputStream("myobjects.ser")); 

MyClass mcl = (MyClass)ois.readObject(); 

int[] a = (int[])ois.readObject(); 

String s = (String)ois.readObject(); 

Date d = (Date)ois.readObject() ;

Нужно только соблюдать порядок чтения элементов потока. В листинге ниже мы создаем объект класса GregorianCaiendar с текущей датой и временем, сериализуем его в файл date.ser, через три секунды десериа-лизуем и сравниваем с текущим временем.

import java.io.*; 

import java.util.*;

class SerDatef

public static void main(String[] args) throws Exception{

GregorianCaiendar d - new GregorianCaiendar(); 

QbjectOutputStream oos = new ObjectOutputStream{

new FileOutputStream("date.ser")); 

oos.writeObject(d); 

oos.flush(); 

oos.close();

Thread.sleep(3000);

ObjectlnputStream ois = new ObjectlnputStream(

new FileInputStream("date.ser"));

GregorianCaiendar oldDate = (GregorianCaiendar)ois.readObject(); 

ois.close();

GregorianCaiendar newDate = new GregorianCaiendar();

System.out.println("Old time = " +

oldDate.get(Calendar.HOUR) + ":" +

oldDate.get(Calendar.MINUTE) +":" + 

oldDate.get(Calendar.SECOND) +"\nNew time = " + 

newDate.get(Calendar.HOUR) +":" + 

newDate.get(Calendar.MINUTE) +":" + 

newDate.get(Calendar.SECOND)); 

}

Если не нужно сериализовать какое-то поле, то достаточно пометить его служебным словом transient, например:

transient MyClass me = new MyClass("abc", -12, 5.67e-5);

Метод writeObjecto не записывает в выходной поток поля, помеченные static и transient. Впрочем, это положение можно изменить, переопределив метод writeObjecto или задав список сериализуемых полей.

Вообще процесс сериализации можно полностью настроить под свои нужды, переопределив методы ввода/вывода и воспользовавшись вспомогательными классами. Можно даже взять весь процесс на себя, реализовав не интерфейс Serializable, а интерфейс Externaiizabie, но тогда придется реали-зовать методы readExternai () и writeExternai о, выполняющие ввод/вывод.


Комментарии и вопросы

Опубликовать комментарий или вопрос

Copyright 2024 © ELTASK.COM
All rights reserved.