© 2008 Наталия Македа
Все материалы блога защищены авторским правом. Любая перепечатка или использование материалов этого блога в коммерческих целях возможна лишь с письменного согласия автора. При некоммерческом использовании ссылка на блог обязательна.

среда, 24 сентября 2008 г.

2.8 Условный оператор ? : (Выпуск 11)

Условный оператор ? :, известный также, как тройной оператор позволяет закодировать условие if/else в одну строку. Если условие истинно, то результат - левое выражение от двоеточия, иначе - правое. Заметьте, что выражения слева и справа от двоеточия должны быть приводимы к типу переменной, к которой эти выражения присваиваются. Например, пусть a, b, c - это переменные типа int, а x - переменная типа boolean. Тогда выражение a = x ? b : c эквивалентно следующему фрагменту кода:

1. if (x) {
2. a = b;
3. }
4. else {
5. a = c;
6. }

Конечно, вместо a, b, c и x могут быть использованы выражения гораздо более сложного вида.

Некоторые не любят условный оператор и в некоторых компаниях он и вовсе запрещён к использованию внутренними правилами компании. Да, этот оператор позволяет создавать компактный код. Но в большинстве случаев компилятор сгенерирует такой же компактный и эффективный код из более понятной конструкции if/else. Кроме того, приятный и компактный условный оператор приобретает иной лик, если использовать его несколько раз во вложенном виде, например:
a = b ? c ? d : e ? f : g : h ? i : j ? k : l;

Резюмируем тонкости использования условного оператора на примере выражения a = x ? b : c:

  • Типы выражений b и c должны быть совместимыми и даже идентичными, если их привести.
  • Тип выражения x должен быть boolean.
  • Типы выражений b и c должны быть совместимыми с типом переменной a.
  • Переменной a присваивается b, если x = true, и c - в противном случае (x = false).


Читать далее!

среда, 20 августа 2008 г.

2.7 Укороченные (short-circuit) логические операторы (Выпуск 10)

Укороченные (short-circuit) логические операторы && и || предназначаются для логических AND (И) и OR (ИЛИ) операций над выражениями типа boolean. Заметьте, что для XOR (исключающее ИЛИ) операции не существует укороченного логического оператора.

Укороченные логические операторы похожи на операторы & и |, но в отличие от них применяются только к выражениям типа boolean и никогда не применяются к интегральным типам. Тем не менее, && и || обладают замечательным свойством: они укорачивают вычисление выражения, если результат может быть дедуцирован из части выражения (чуть позже я поясню это на примерах). Благодаря этому свойству, операторы && и || широко используются для обработки null-выражений. Они также помогают увеличить эффективность всей программы.

Свойство укорачивания вытекает прямым образом из различий между &&/|| и &/|: последняя пара операторов должна получать на вход значения левого и правого операторов, в то время как для первой пары операторов иногда достаточно знать значение только левого операнда, чтобы вернуть значение всего выражения. Такое поведение укороченных логических операторов базируется на двух математических правилах логической таблицы истинности:

  • выражение с AND оператором ложно (false), если значение хотя бы одного из его операндов ложно;
  • выражение с OR оператором истинно (true), если значение хотя бы одного из его операндов истинно.
Иными словами:
  • false AND X = false
  • true OR X = true
То есть, если левый операнд AND выражения ложен, то и всё выражение ложно, вне зависимости от значения правого операнда. То есть при ложном левом операнде нет нужны вычислять значение правого операнда. Аналогичным образом, если левый операнд OR выражения истиннен, то истинно и всё выражения, вне зависимости от значения правого операнда, которое, следовательно, нам не нужно вычислять.

Рассмотрим пример кода, который выводит сообщение String, если строка не нулевая и более 20 символов длиной:

1. if ((s != null) && (s.length() > 20)) {
2.     System.out.println(s);
3. }

Эта же задача может быть закодиравана по-другому:
 
1. if (s != null) {
2.     if (s.length() > 20) {
3.          System.out.println(s);
4.     }
5. }

Если бы строка s была null, то при вызове метода s.length() мы бы получили NullPointerException. Ни в одном из двух примеров кода, однако, такая ситауция не возникнет. В частности, во втором примере, s.length() не вызывается при s = null, благодаря использованию укороченного оператора &&. Если бы тест (s!=null) возвращал ложь (false), то есть s - несуществующая строка, то и всё выражение гарантированно ложно. Значит, отпадает необхеодимость вычислять значение второго операнда, то есть выражения (s.length()>20).

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

Рассмотрим пример:
// первый пример
1. int val = (int)(2 * Math.random());
2. boolean test = (val == 0) || (++val == 2);
3. System.out.println(“test = “ + test + “\nval = “ + val);

// второй пример
1. int val = (int)(2 * Math.random());
2. boolean test = (val == 0)|(++val == 2);
3. System.out.println(“test = “ + test + “\nval = “ + val);

Первый пример иногда будет выводить на печать вот это:
 
test = true
val = 0


А иногда вот это:
 
test = true
val = 2


Второй пример иногда будет выводить на печать вот это:
 
test = true
val = 1


А иногда вот это:
 
test = true
val = 2


А дело вот в чём. Если val равно 0, то второй операнд (++val) никогда не будет вычислен, то есть val останется равным нулю. Если же изначально val равен единице, то в результате эта переменная будет инкрементирована и мы увидим val = 2. Во втором примере, при использовании неукороченных операторов, инкремент выполняется всегда и результат будет всегда или 1 или 2 в зависимости от случайного значения выбранного на первом шаге. В обоих примерах переменная test принимает значение true, потому что либо val = 0, либо val = 1 и инкрементируется до значения 2.

Резюмируем информацию об укороченных операторах && и ||:
  • Они применяются к операндам типа boolean;
  • Они вычисляют значение правого операнда только если результат не может быть вычислен на основании значения левого операнда:
    • false AND X = false
    • true OR X = true


Читать далее!

воскресенье, 29 июня 2008 г.

Откуда скачать Java (Выпуск 9)

Получила первое письмо от читателя рассылки, в котором вопрос: откуда скачать Java. Скачать Java очень просто:

  1. Идём на сайт java.sun.com.
  2. На открывшейся странице в верхней менюшке наводим курсором на Downloads и из выпадающей менюшке выбираем Java SE (подсвечено оранжевым на рисунке ниже).
  3. На открывшейся странице (см. рис. ниже) нам нужно выбрать (кнопка Download) JDK 6 Update 6 (отмечено красным).
  4. На открывшейся странице необходимо выбрать платформу (Windows, Linux и т.д.). И опция Multi-language, по-моему, там единственная. Отметьте также галочкой License Agreement и нажмите оранжевую кнопку Continue.
  5. Наконец мы добрались до ссылки на скачивание. При этом вы можете выбрать либо пакет для он-лайн инсталляции, либо пакет для офф-лайн инсталляции. Он-лайн инсталляция, конечно, занимает мало (0.36Мб)по сравнению со вторым пакетом (71.49Мб), но это только кажимость. Всё равно все недостающие компоненты будут подкачиваться из интернета в процессе инсталляции. Так что все 70 мег вам так или иначе придётся скачать. Единственная разница в том, что в первом случае (он-лайн инсталляция) вы ещё окажетесь связанными по рукам, если вдруг у вса не окажется под рукой интернета при переустановки джавы. Так что я рекомендую скачивать офф-лайн инсталляцию (отмечено красным карандашом на рис. ниже).
  6. Скачиваемый файл в моём случае (винда) называется jdk-6u6-windows-i586-p.exe. И я егос охранила на диск (Salva, Save).

Скачивание длилось у меня где-то 10 минут.

Потом нужно просто запустить скачанный файл. Установится JRE (Java Run-time Edition), то есть всё то, что необходимо для функционирования джавы под платформу, выбранную на шаге 4. А также устанится JDK (Java Development Kit), то есть всё, что нужно для программирования: документация, библиотеки, примеры...


Читать далее!

2.6 Поразрядные операторы: &, ^ и | (Выпуск 9)

Поразрядные операторы &, ^ и используются для выполнения побитовых операций AND, XOR и OR соответственно. Они применяются к целочисленным типам. Набор битов является эффективным средством экономии памяти, когда некое состояние (например, данные физических устройств) необходимо представить в виде вектора значений типа boolean.

Результат применения операторов &, ^ и к двум операндам описывается таблицами 1, 2 и 3 соответственно.

Таблица 1: Операция AND
Операнд1Операнд2Операнд1 AND Операнд2

0

0

0

0

1

0

1

0

0

1

1

1



Таблица 2: Операция OR
Операнд1Операнд2Операнд1 OR Операнд2

0

0

0

0

1

1

1

0

1

1

1

1



Таблица 3: Операция XOR
Операнд1Операнд2Операнд1 XOR Операнд2

0

0

0

0

1

1

1

0

1

1

1

0


Для AND операции результат будет истинным (1), если оба операнда истинны. Для OR оператора результат истинный, если хотя бы один операнд истинный. Для XOR оператора результат истинный, если один и только один (не оба!) операнд истинный. XOR - это исключающий (eXclusive) OR.

Сейчас мы рассмотрим работу этих операторов не над одним единственным битом, но над битовым представлением числа.

Пример 1.

    00110011
11110000
AND --------
00110000

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

Пример 2.

    00110011
11110000
XOR --------
11000011
   00110011
11110000
OR --------
11110011

Для вычисления результата операций XOR и OR необходимо воспользоваться таблицами 3 и 2 соответсвенно для соответсвующих разрядов первого (верхнего) и второго (нижнего) операндов. Заметьте, что операции AND, OR, XOR коммутативны, то есть результат изменится прежним, даже если мы поменяем операнды местами.

Аналогичным образом таблицы 1, 2, 3 можно применить и к значениям типа boolean, где 1 - true, 0 - false. При этом операнды должны быть булевого типа (например, a==10) и операторы &, ^ и применяются не побитово, а ко всему булевому выражению.

Оба операнда должны быть либо целочисленного типа (и тогда операторы &, ^ и применяются поразрядно), либо булевого типа (и тогда операторы применяются ко всему выражению). В Java нельзя привести тип к boolean. Вместо этого необходимо использовать булевы выражения, например, сравнения.


Читать далее!

среда, 28 мая 2008 г.

2.5 Операторы сравнения (Выпуск 8)

Операторы сравнения, <, >, <=, >=, ==, !=, возвращают результат типа boolean, то есть true или false. Эти операторы обычно используются в условных конструкциях (например, if () или циклы). Существует три типа сравнения:

  1. порядковое (ordinal) тестирует относительные значения числовых операндов.
  2. объектно-ориентированное определяет тип объекта во время исполнения программы.
  3. операторы равенства тестируют, одинаковы ли два значения, в том числе и нечисленные.

Рассмотрим их все подробнее.

Порядковые операторы (<, >, <=, >=)

К порядковым операторам относятся < (меньше), > (больше), <= (меньше либо равно), >= (больше либо равно). Они применяются ко всем численным типам и типу char и возвращают результат boolean. Например, если у нас имеются следующие объявления:

int p = 9;
int q = 65;
int r = -12;
float f = 9.0F;
char c = ‘A’;

то следующие тесты возвратят true:

p < q
f < q
f <= c
c > r
c >= q

Заметьте, что, когда эти операторы используются, применяется арифметическое распространение. Например, будет ошибкой присвоение значение 9.0F типа float переменной c типа char. Но к этой паре может быть применено сравнение! Для этого Java распространяет меньший тип к большему типу. То есть значение 'A' типа char (представляемое значением 65 в Unicode) распространяется до float 65.0F. Сравнение затем выполняется на результирующей паре значений float.

Порядковые сравнения не могут быть применены к объектным типам!

Оператор instanceof

Оператор instanceof тестирует класс объекта в момент исполнения программы (runtime). Левый операнд - любое выражение объектного типа (переменная или массив). Правый операнд - имя класса, интерфейса или массивный тип. Однако, левым операндом не может быть объект типа java.lang.Class, а правым операндом не может быть String.

Фрагмент кода ниже показывает пример использования оператора instanceof. Предположим, что существует класс Person с подклассом Parent:

1. public class Classroom {
2. private Hashtable inTheRoom = new Hashtable();
3. public void enterRoom(Person p) {
4. inTheRoom.put(p.getName(), p);
5. }
6. public Person getParent(String name) {
7. Object p = inTheRoom.get(name);
8. if (p instanceof Parent) {
9. return (Parent)p;
10. }
11. else {
12. return null;
13. }
14. }
15. }

Метод getParent() в строках 6-14 проверяет, содержит ли Hashtable родителя со специфическим именем. Для этого в Hashtable сначал ищется элемент со специфическим именем родителя. А потом проверяется, является ли этот элемент объектом типа Parent. Оператор instanceof проверяет, совпадает ли класс левого операнда с названием класса, заданного правым операндом или является его подклассом.

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

Опертор также используется для тестирования, является ли объект массивом. Поскольку массивы в Java являются объектами, то такое тестирование логично. Но тест проводится в два шага: (1) является ли объект массивом и (2) является ли тип элементов массива (под)классом правого аргумента. Это отражает идею, что массив, скажем, объектов типа Button (кнопки) является массивом объектов типа Component (компоненты), поскольку Button - это Component. Тест для такого массива будет выглядеть так:

if (x instanceof Component[])

Заметьте, что невозможно провести тестирование для "любого массива с элементами любого типа". То есть следующая строка недопустима:

if (x instanceof [])

Недопустимо также тестирование на массив с элементами типа Object:

if (x instanceof Object [])

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

А как же протестировать, является ли объект массивом без привязки к типу его элементов? Допустим myObject это массив. Тогда следующая строка возвратит true:

myObject.getClass().isArray()

Если левый операнд равен null, то instanceof возвратит false. Никокого исключения не возникнет.

Операторы равенства (== и !=)

Операторы == и != тестируют, соответственно, равенство или неравенство. Для примитивных типов концепция равенства или неравенства достаточно тривиальна. Как и в случае с операторами порядка, операнды распространяются до наибольшего. Например, значение 10.0 типа float равно значению 10 типа byte. Для значений объектного типа, сравниваемая величина - это ссылка на объект, то есть адрес памяти.

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

Метод equals() должен был определён в классе сравниваемых объектов. Чтобы быть уверенными в этом, проверьте документацию. В документации должно быть указано, что equals() определён в классе или переопределяет (overrides) метод equals() своего супер-класса. Если документация ничего по этому поводу не говорит, то метод equals() будет, скорее всего, работать неправильно. Метод equals() принимает аргумент типа Object. Но в реальности вы должны передавать ему аргумент того же типа, что и объект, для которого он вызывается. Например, если вы вызываете x.equals(y), то y instanceof x должно возвращать true. Если же это не так, то equals() вернёт false.
Если вы определяете equals() в вашем собственном классе, то вы должны соблюдать три правила:
  • Аргумент для equals() должен быть объект типа Object. Не поддавайтесь искушению вставить аргумент того же типа, что и класс. Иначе вы перегрузите (overload) метод, а не переопределите (override) его. В итоге функциональность других частей кода, зависящих от equals() будет неправильна. Например, итерации по HashMap, то есть containsKey() и get(), будут неправильны.
  • equals() должен быть коммутативным оператором. То есть результат x.equals(y) должен быть равен результату y.equals(x).
  • Если вы лпределяете equals() в своём классе, то вы должны определить и hashCode(), который возвращает одно и то же значение для объектов, сравниваемых методом equals(). Это, опять таки, нужно для правильного функционирования итераций по контейнерам. Минимально допустимое поведение hashCode() - это return 1. Конечно, при такой имплементации потеряются все преимущества "настоящего" хеширования и HashMap будет просто связным списком. Но, по крайней мере, функциональность будет корректной.


Читать далее!

четверг, 15 мая 2008 г.

2.4. Операторы сдвига <<, >> и >>>(Выпуск 7)

В Java есть операторы сдвига. Операторы << и >> позаимствованы из С/C++. Кроме того, Java обладает своим новым оператором сдвига >>>.

Операторы сдвига присущи системам, которые могут выравнивать биты, прочтённые из IO портов или зартсываемые в IO порты. Это также быстрое умножение или деление на степень двойки. Преимущество операторов сдвига в Java - это независимость от платформы. Поэтому вы можете использовать их не беспокоясь ни о чём.

Основы сдвига

Сдвиг - это, по сути, простейшая операция: мы берём последовательность битов и двигаем её влево или вправо. Больше всего конфуза вызывает оператор >>>. Но о нём мы поговорим чуть позже.

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

Таблица 1: Идея сдвига
Исходные данные

192

Бинарное представление00000000000000000000000011000000
Сдвиг влево на 1 бит

0

0000000000000000000000011000000?
Сдвиг вправо на бит?00000000000000000000000011000000
Сдвиг влево на 4 бита00000000000000000000000011000000????
Исходные данные

-192

Бинарное представление11111111111111111111111101000000
Сдвиг влево на 1 бит

1

1111111111111111111111101000000?
Сдвиг вправо на бит?11111111111111111111111101000000

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

Однако, глядя на таблицу, возникают три вопроса вопроса:

  1. Что происходит, если мы сдвигаем влево и при этом часть бинарной записи выходит за границу слева, а часть - остаётся пустой справа?
  2. Что происходит, когда справа - выход за границы, а слева - пустое место?
  3. Какое истинное значение принимает знак "?"?.

Ответим на часть этих вопросов. Биты, вышедшие за границы, просто теряются. Мы о них забываем.

В некоторых языках, типа ассемблер, есть операция ротации, когда при сдвиге вышедшие за границы биты не теряются, но ставятся на освободившееся место (вместо вопросиков). Однако языки высокого уровня, типа Java, не имеют в своём арсенале такой операции.

Сдвиг отрицательных чисел

Ответ на вопрос о значении символов "?" в приведенной выше таблице требует отдельного рассмотрения.

В случае сдвига влево << и беззнакового сдвига вправо >>> новые биты просто устанавливаются в ноль. В случае сдвига вправо со знаком >> новые биты принимают значение старшего (самого левого) бита перед сдвигом. Следующая таблица демонстрирует это:

Таблица 2: Сдвиг положительных и отрицательных чисел
Исходные данные

192

Бинарное представление00000000000000000000000011000000
Сдвиг вправо на 1 бит 00000000000000000000000001100000
Сдвиг вправо на 7 бит00000000000000000000000000000001
Исходные данные

-192

Бинарное представление11111111111111111111111101000000
Сдвиг вправо на 1 бит 11111111111111111111111110100000
Сдвиг вправо на 7 бит11111111111111111111111111111110

Заметьте: в том, случае, где старший бит был 0 перед сдвигом, новые биты стали тоже 0. Там где старший бит перед сдвигом был 1, новые биты тоже заполнились 1.

Это правило может показаться странным на первый взгляд. Но оно имеет под собой очень серьёзное обоснование. Если мы сдвигаем бинарное число влево на одну позицию, то в десятичной записи мы умножаем его на два. Если мы сдвигаем влево на n позиций, то умножение происходит на 2n, то есть на 2, 4, 8, 16 и т.д.

Сдвиг вправо даёт деление на степени двойки. При этом, добавление слева нулей на появившиеся биты на самом деле даёт деление на степени двойки лишь в случае положительных чисел. Но для отрицательных чисел всё совсем по другому!

Как известно, старший бит отрицательных чисел равен единице, 1. Для того, чтобы сохранить старшинство единицы при сдвиге, то есть сохранить отрицательный знак результата деления отрицательного числа на положительное (степень двойки), нам нужно подставлять единицы на освободившиеся места.

Если мы посмотрим на Таблицу 2, то заметим, что 192, сдвинутое на 1 бит вправо - это 192/2=96, а сдвинутое на 7 битов вправо - это 192/27=192/128=1 по законам целочисленной арифметики. С другой стороны, -192 сдвинутое на 1 бит вправо - это 192/2=-96 и т.д.

Есть, однако пример, когда реультат сдвига вправо отличается от результата целочисленного деления на 2. Это случай, когда аргумент = -1. При целочисленном делении мы имеем: -1/2=0. Но результат сдвига вправо нам даёт -1. Это можно трактовать так: целочисленное деление округляет к нулю, а сдвиг округляет к -1.

Таким образом, сдвиг вправо имеет две ипостаси: одна (>>>) просто сдвигает битовый паттерн "в лоб", а другая (>>) сохраняет эквивалентность с операцией деления на 2.

Зачем же Java потребовался беззнаковый сдвиг вправо (сдвиг "в лоб"), когда ни в С, ни в С++ его не существует? Ответ прост, потому что в С и С++ сдвиг всегда беззнаковый. То есть >>>> в Java - это и есть сдвиг вправо в C и C++. Но, поскольку в Java все численные типы со знаком (за исключением char), то и результаты сдвигов должны иметь знаки.

Сокращение (reduction) правого операнда

На самом деле у операторов сдвига есть правый операнд - число позиций, на которое нужно произвести сдвиг. Для корректного сдвига это число должно быть меньше, чем количество битов в результате сдвига. Если число типа int (long), то сдвиг не может быть сделан более, чем на 32 (64) бита.

Оператор же сдвига не делает никаких проверок данного условия и допускает операнды, его нарушающие. При этом правый операнд сокращается по модулю от нужного количества битов. Например, если вы захотите сдвинуть целое число на 33 бита, то сдвиг произойдёт на 33%32=1 бит. В результатае такого сдвига мы легко можем получить аномальные результаты, то есть результаты, которых мы не ожидали. Например, при сдвиге на 33 бита мы ожидаем получить 0 или -1 (в знаковой арифметике). Но это не так.

Почему Java сокращает правый операнд оператора сдвига или грустная история о заснувшем процессоре

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

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

Основным применением данных процессоров был контроль систем реального времени. В данных системах самый быстрый ответ на внешнее событие должно занимать не более задержки на прерывание (interrupt latency). Отдельные инскрукции таких процессоров были неделимы. Поэтому выполнение длинных операций (сдвига на несколько бит и ротации) нарушало эффективную работу процессора.

Следующая версия процессора имплементировала эти операции уже по-другому: размер правого операнда сократился. Задержка на прерывание восстанавилась. И многие процессоры переняли данную практику.

Арифметическое распространение (promotion) операндов

Апифметическое распространение операндов происходит перед применением оперции сдвига и гарантирует, что операнды по крайней мере типа int. Это явление имеет особый эффект на беззнаковый сдвиг вправо, когда сдвигаемое число меньше, чем int: мы получаем не тот результат, который ожидали.

Следующая таблица показывает пример аномалии:

Таблица 3: Арифметическое распространение для беззнакового сдвига вправо, когда операнд меньше, чем int
Исходные данные (-64 в десятичной записи)

11000000

Распространение до int11111111111111111111111111000000
Сдвиг вправо на 4 битa 00001111111111111111111111111100
Сокращение до байта11111100
Ожидаемый результат был00001100


Читать далее!

суббота, 3 мая 2008 г.

Сложение и вычитание, NaN (Выпуск 6)

Сложение и вычитание (+ и -)

За сложение и вычитание отвечают соответсвенно операторы + и -. Они применимы для операндов любых численных типов. Помимо этого, операция + применяется и для операндов типа String. Когда один из операндов типа String, то результат будет объектом типа String.

Особенности оператора +

В Java не допускается определять перегрузку операторов (то есть перепрограммирование операторов в зависимости от типа), как в С/С++. Но сам язык Java перегружает операторы автоматически. И это в общем-то не ново, поскольку многие языки программирования, которые поддерживают множественные арифметические типы, определяют автоматическую перегрузку арифметических операторов для примитивных типов. Java, помимо этого, имеет перегрузку оператора + для строкового типа (String). И результат действия оператора + в данном случае - это конкатенация, сцепление строк. Если один из операндов не строковый, то к нему будет предварительно применено приведение типа к строке.

Перегрузка (overloading) - это термин, упоминаемый в том случае, когда одна и та же операция (оператор) используется для операндов (аргументов) различного типа. При этом поведение операции (оператора) определяется типом операндов, к котором она должна быть применена. Например, метод println() может применяться как для операндов строкового, так и целочисленного типа. Эти два использования, на самом деле, относятся к совершенно различным методам. Просто было использовано одно и то же имя операции. Аналогично сложение (+) используется, как для целочисленных, так и для дробных типов. Но код, реализующий это сложение совершенно различный.

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

Если переполнение происходит при арифметическом сложении или вычитании, никаких сообщений об ошибках или исключений мы не получаем. Дело обстоит совсем по-другому, если мы конкатенируем строки. Если оба оператора строки, то результат - это "скленная" строка. Если один из операторов строка, а второй - число, то число будет приведено к типу String.

Приведение численных типов к строке

Для объектных типов приобразование к типу String происходит посредством вызова метода toString(). Этот метод определён в классе java.lang.Object, который является прародителем всех остальных классов. Поэтому все объекты наследуют от Object метод toString(). Однако, этот метод выдаёт некое зашифрованное значение объекта. Например, если мы рассмотрим такой код:

1. public class Test {
2. private static class MyClass {
3. private String name;
4. private int age;
5. public MyClass() {
6. name = "Natalia Macheda";
7. age = 28;
8. }
9.
10. }
11. public static void main (String[] args) {
12. MyClass myObj = new MyClass();
13. System.out.println(myObj.toString());
14. }
15.}

То результат, выводимый 13-й строкой будет такого вида: Test$MyClass@c17164. Здесь мы видим имена классов объекта (разделённые символом $) и какой-то идентификатор после символа @. Идентификатор - это, как правило, ссылка на объект.

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

9.       public String toString(){
9'. return (name + " " + age);
9''. }

то результат будет Natalia Macheda 28. Заметьте, что втрой аргумент - целочисленного типа. Как же произошло его преобразование в строку?

Преобразование чесленных аргументов к строчному типу неявно использует метод toString() классов-оболочек (wrapper). Например, значение типа int конвертируется вызовом статической функции Integer.toString().

Резюмируем сведения о сложении. Сложение двух числовых значений примтивных типов даёт результат:

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

Если один из операндов - НЕпримитивного типа:

  • второй операнд должен быть строкового типа. Иначе операция нелегальна;
  • операнд НЕстрокового типа приводится к типу String и результат - сцепление двух строк.

Приведение операнда объектного типа к строке производится путём вызова метода toString().

Приведение операнда примитивного типа к строке производится неявно путём вызова статичского метода класса-оболочки.

Если вы хотите держать под контролем форматирование результата приведения типа, обратитесь к коду пакета java.text.

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

Арифметические ошибки

Мы ожидаем от арифметических операций результата, который выдаёт знаяение с математическим смыслом. Но, как уже было сказано выше, результат может не соответсвовать нашим ожиданиям в виду ограниченности ресурсов компьютера. В частности:

  • Деление на ноль (в том числе и для операции %) выдаёт ArithmeticException;
  • Никаких других исключений для арифметических операций не выдаётся, но результат может быть арифметически неправильным ввиду переполнения.
  • Дробные типы представляют абстрактные значения при помощи следующих значений: IEEE 754 бесконечности, минус бесконечности и Не-числа NaN (Not a Number). Именные константы, представляющие эти значения, объявлены в классах Float и Double.

NaN сигнализирует о том, что вычисление не имеет результата в математическом понимании. Например, бесконечность или вычисление квадратного корня из отрицательного числа.

Сравнение с НЕ-числом

Два НЕ-числа NaN определены в пакете java.lang. Это Float.NaN и Double.NaN. Следующие сравнения всегда дают false, даже если x равен NaN:

x < Float.NaN
x <= Float.NaN
x == Float.NaN
x > Float.NaN
x >= Float.NaN

В тестах Float.NaN != Float.NaN и Doube.NaN != Double.NaN результат будет true.

Если же вы хотите проверить, что число (не) является NaN, то нужно использовать статические методы Float.isNaN(float) или Double.isNaN(double), определённые в пакете java.lang.


Читать далее!