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

понедельник, 24 ноября 2008 г.

3.2 Модификаторы доступа (Выпуск 14)

Модификаторы доступа определяют, могут ли классы использовать разнообразные элементы (сами классы, переменные-поля классов, методы классов и коснтрукторы) и как они могут их использовать.

Существует три модификатора доступа:

  • public
  • private
  • protected

Элементы (см. выше) могут иметь один и более модификаторов, либо не иметь их вовсе. Однако, среди всевозможных модификаторов элемента количество модификаторов доступа долно быть не больше одного.

Если элемент не имеет модификатора, то доступ к нему определяется по умолчанию, то есть default. Однако, ключевого слова default не существует! Другие наименования доступа по умолчанию, которые вы можете встретить в литературе, включают в себя friendly, package.

Ниже мы приводим примеры легально объявленых элементов с модификаторами или без них. Легальность некоторых объявлений зависит от контекста.

class Parser { ... }
public class EightDimensionalComplex { ... }
private int i;
Graphics offScreenGC;
protected double getChiSquared() { ... }
private class Horse { ... }

А вот примеры нелегальных объявлений элементов:

public protected int x; // Не более одного модификатора доступа!
default Button getBtn() {...} // "default" - это не ключевое слово


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

Глава 3: Модификаторы (Выпуск 14)

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

3.1 Обзор модификаторов

Наиболее распространённые модификаторы - модификаторы доступа: public, protected, private. О них мы поговорим сразу в следующей статье. Все остальные модификаторы не подпадают ни под какую чёткую классификацию. Мы их просто перечислим:

  • final
  • abstract
  • static
  • native
  • transient
  • synchronized
  • volatile

Каждому из этих модификаторов будет посвящена отдельная статья в будущем.


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

среда, 5 ноября 2008 г.

Ответы на тест по операторам (Выпуск 13)

Вопросы теста можно посмотреть здесь.
  1. Ответ: С
    Вычисление производится в таком порядке: x = a + b; a = a + 1; b = b + 1; Поэтому x = 6 + 7 то есть 13. После чего переменные a и b инкрементируются до значений 7 и 8 соответственно.
  2. Ответ: B, С
    В варианте A использование оператора ! незаконно, поскольку x является переменной типа int, а не boolean. На этом часто попадаются C и C++ программисты. В варианте B выражение (x<3) имеет тип boolean, поэтому перед ним можно использовать оператор !. В ответе C поразрядный оператор ~ применен к целочисленному x, значение которого равно 6, что в битовом виде имеет форму 0..0110 (на месте двух точек должны стоять 27 нулей). После применения оператора ~ x примет значение 1..1001 (на месте двух точек подставьте 27 единиц).
  3. Ответ: A
    Битовое представление -1 состоит из единиц во всех разрядах. В варианте A эта строка из единиц сдвигается вправо на 5 позиций и освободившиеся 5 первых позиций заполняются нулями. Это в десятичном выражении имеет положительное значение 134217727. В варианте B аналогичный сдвиг происходит на 32 разряда и результат состоит из строки, у которой во всех позициях нули, то есть 0. Вариант C недопустим, потому что результат выражения x <<< 5 имеет тип int и не может быть присвоен переменной типа byte без явного приведения типов, которое в данном случае отсутствует. Наконец, в варианте D при сдвиге все освободившиеся позиции принимают значение наиболее значимого разряда исходного значения, то есть -1. Результат операции не отличается от исходного значения и равен -1.
  4. Ответ: A, C, E
    Варианты A и C эквивалентны, поскольку x+=y эквивалентно x = x+y. Выражение x+y "суммирует" строки и целое число, в результате из целого числа будет создан объект строкового типа и потом будет создана новая строка (ещё один новый объект строкового типа) со значением "Hello9". В варианте B сравнение x == y незаконно, потому что целочисленный тип не может сравниваться с указателем. В варианте D в результате вычисления выражения y+x также, как и в вариантах A и C создаётся новая строка. Но в последствии она присваивается переменной целочисленного типа, а это недопустимо. Вариант E самый оригинальный. В нём важны два момента: условный оператор :? и укороченный оператор &&. Левый операнд укороченого оператора, то есть выражение (x != null), всегда равен false. Следовательно правый операнд, то есть выражение (x.length() > 0), никогда не будет вычисляться, поскольку всё выражение заведомо равняется значению false. Это значение false затем используется в выражении условного оператора, результат которого будет равен 0 (операнд после двоеточия).
  5. Ответ: A, E
    Хотя int и float и не являются совместимыми с точки зрения присваиваний, они могут легко использоваться в левых и правых частях операторов сравнения, коим и является оператор ==. При этом int 100 приводится к float 100.0 и запросто сравнивается с другим float 100.0F. Поэтому вариант A проходит. В варианте B код не откомпилируется, потому что int и Integer (указатель) не могут быть приведены даже в случае оператора сравнения. В варианте C код компилируется и сравнение может быть проведено. Но выражение x == y сравнивает два различных указателя, и поэтому результат теста равен false. Аналогичные рассыждения можно провести для варианта D. Вариант E кажется эквивалентным варианту D, но это на самом деле не так. В варианте E для строковой константы "100" создаётся объект строкового типа, а x и y являются указателями на этот объект. То есть тест (x == y) равняется true и мы получаем сообщение "Equal".
  6. Ответ: AВ четвёртой строке кода результат вычисления (s.length()>5) равен false (поскольку длина строки "Hello" равна 5). Таким образом, всё выражения условия if равно false. В результате никаких манипуляций над строкой не производится и она остаётся прежней.
  7. Ответ: B
    Побитовый XOR оператор ^ возвращает 0, если оба биты одинаковы (оба равны 0 или оба равны 1), и 1 - в противном случае. Таким образом 00001010^00001111=00000101, то есть 5 в десятичном исчислении.
  8. Ответ: CВ операторе ?: используются операнды различных типов: 99.99 (double) и 9 (int). Поэтому int приводится к double и результат всего выражения будет типа double. Поскольку x = 4, то тест (x > 4) равен false. Следовательно, результат равен значению после двоеточия, то есть 9.0 (то есть типа double).
  9. Ответ: B
    Вычисление достаточно очевидно, поскольку участвуют только положительные числа. Деление 10 на 3 даёт 3 и в остатке 1, что и является ответом. Если бы мы имели дело с дробными числами, то алгоритм вычисления остатка от деления y на x был бы таким: вычитать x из y до тех пор, пока y не станет меньше x. Как только это случится, y - остаток от деления. Для отрицательных чисел можно забыть о знаках, но иметь в виду, что знак остатка от деления равен знаку делимого (левого операнда).
  10. Ответ: AОператор присваивания op= высчитывает левое выражение только один раз. Таким образом, декремент --x происходит только раз и в результате получается 0, а не -1. То есть не возникает никаких ошибок выхода за границы (out-of-bounds). В нулевой позиции массива находится значение Fred. При этом декремент имеет место перед операцией +=. Хотя объекты типа String не могут изменять своего значения (immutable), указатели, коими являются элементы массивы, могут изменяться. То есть мы можем создать новый объект в нулевой позиции массива name, который будет иметь значение "Fred.", то есть строка Fred, заканчивающаяся точкой.


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

воскресенье, 12 октября 2008 г.

Итоги главы 2 и тест по операторам (Выпуск 12)

Резюме главы 2 об операторах:

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

Различие между равенством объектов и равенством ссылок. Функциональность метода equal() в классах Object, String, Boolean (они сравнивают инкапсулированные данные).

Тест

  1. После выполнения кода ниже каковы будут значения переменных x, a, и b?
    1. int x, a = 6, b = 7;
    2. x = a++ + b++;
    1. x = 15, a = 7, b = 8
    2. x = 15, a = 6, b = 7
    3. x = 13, a = 7, b = 8
    4. x = 13, a = 6, b = 7
  2. Какие из нижеследующих выражений допустимы? (Выберите все допустимые)
    1. int x = 6; x = !x;
    2. int x = 6; if (!(x > 3)) {}
    3. int x = 6; x = ~x;
  3. Какие выражения возвратят положительное значение переменной x?
    1. int x = -1; x = x >>> 5;
    2. int x = -1; x = x >>> 32;
    3. byte x = -1; x = x >>> 5;
    4. int x = -1; x = x >> 5;
  4. Какие из нижеследующих выражений допустимы? (Выберите все допустимые)
    1. String x = “Hello”; int y = 9; x += y;
    2. String x = “Hello”; int y = 9; if (x == y) {}
    3. String x = “Hello”; int y = 9; x = x + y;
    4. String x = “Hello”; int y = 9; y = y + x;
    5. String x = null;int y = (x != null) && (x.length() > 0) ? x.length() : 0;
  5. Какие из нижеследующих выражений откомпилируются и напечатают “Equal” во время выполнения? (Выберите все допустимые)
    1. int x = 100; float y = 100.0F;
      if (x == y)
      System.out.println("Equal");
    2. int x = 100; Integer y = new Integer(100);
      if (x == y)
      System.out.println("Equal");
    3. Integer x = new Integer(100);
      Integer y = new Integer(100);
      if (x == y)
      System.out.println("Equal");
    4. String x = new String("100");
      String y = new String("100");
      if (x == y)
      System.out.println("Equal");
    5. String x = "100"; String y = "100";
      if (x == y)
      System.out.println("Equal");
  6. Каков результат выполнения кода ниже?
    1. public class Short {
    2. public static void main(String args[]) {
    3. StringBuffer s = new StringBuffer(“Hello”);
    4. if ((s.length() > 5) &&
    5. (s.append(“ there”).equals(“False”)))
    6. ; // do nothing
    7. System.out.println(“value is “ + s);
    8. }
    9. }
    1. Hello
    2. Hello there
    3. Ошибка компиляции в 4-й или 5-й строке
    4. Ничего не выводится
    5. Исключение NullPointerException
  7. Каков результат выполнения кода ниже?
    1. public class Xor {
    2. public static void main(String args[]) {
    3. byte b = 10; // 00001010 binary
    4. byte c = 15; // 00001111 binary
    5. b = (byte)(b ^ c);
    6. System.out.println(“b contains ” + b);
    7. }
    8. }
    1. b contains 10
    2. b contains 5
    3. b contains 250
    4. b contains 245
  8. Каков результат компиляции и выполнения кода ниже?
    1. public class Conditional {
    2. public static void main(String args[]) {
    3. int x = 4;
    4. System.out.println(“value is “ +
    5. ((x > 4) ? 99.99 : 9);
    6. }
    7. }
    1. value is 99.99
    2. value is 9
    3. value is 9.0
    4. Ошибка компиляции в пятой строке
  9. Каков результат выполнения кода ниже?
    1. int x = 3; int y = 10;
    2. System.out.println(y % x);
    1. 0
    2. 1
    3. 2
    4. 3
  10. Каков результат выполнения кода ниже?
    1. int x = 1;
    2. String [] names = { “Fred”, “Jim”, “Sheila” };
    3. names[--x] += “.”;
    4. for (int i = 0; i < names.length; i++) {
    5. System.out.println(names[i]);
    6. }
    1. Fred.\nJim\nSheila
    2. Fred\nJim.\nSheila
    3. Fred\nJim\nSheila.
    4. Fred\nJim\nSheila
    5. Исключение ArrayIndexOutOfBoundsException


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

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

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

Операторы присваивания устанавливают значение переменной или выражения в новое значение. Присваивание поддерживается рядом других операторов. Простое присваивание использует знак "=". Такие операторы, как, например, "*=", "+=" выполняют композиционную функцию, имеющую значение "вычислить и присвоить". Такие композиционные операторы имеют общую форму op=, где op - любой бинарный небулевый оператор, рассмотренный в данной главе.

Для любых выражений x и y совместимого типа выражение x op= y является эквиваленным выражению x = x op y. Но есть и отличия:

  1. В первом случае x вычисляется только раз, а не два раза, как во втором, традиционном, случае.
  2. Оператор присваивания содержит в себе неявное приведение типов.

Например:

1. byte x = 2;
2. x += 3;

В расширенном варианте это выглядит так:

1. byte x = 2;
2. x = (byte)(x + 3);

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

Выражение x+=2 всего лишь на два символа короче своей расширенной формы x = x + 2. Но представьте себе, что x имеет более сложный вид, например:
target[temp.calculateOffset(1.9F) + depth++].item. И тогда читаемость этого выражения существенно возрастает!

Любое присваивание обладает значением

Все операторы, которые мы обсудили в данной главе возвращают некое значение в результате выполнения операции. Например, выражение 1+2 возвращает значение 3 в результате выполнения операции сложения. И это значение может быть использовано на следующих шагах программы, например, оно может быть присвоено некоторой переменной. Оператор присваивания также возвращает значение. Например, если нам даны три переменные a, b, c, то допустимо выражение a = b = c = 0. Это выражение вычисляется справа налево, то есть ноль сначала присваивается переменной c. И присваивание возвращает значение 0, которое присваивается переменной b. А потом аналогичным образом ноль присваивается переменной a.

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


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

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.


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

воскресенье, 27 апреля 2008 г.

2.3 Арифметические операторы: умножение, деление, деление по модулю

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

По приоритетности за унарными операторами следуют арифметические операторы. Эта группа включает в себя четыре наиболее распространённых оператора: сложение, вычитание, умножение, деление. И не только их. Существует также оператор деления по модулю, который обозначается знаком %. Арифметические операторы разделены на две группы. В первой, более приоритетной, группе находятся *, /, %. Во второй, соответственно, + и -.

Умножение и деление (* и /)

Операторы * и / выполняют умножение и деление над всеми примитивными числовыми типами и char. При делении на ноль возникает ArithmeticException.

Вы, наверное, недоумеваете, зачем я вам рассказываю про умножение и деление известное вам с первого класса. Однако, в программировании мы имеем дело с некоторыми ограничениями, связанными с представлением чисел в компьютере. Эти ограничения накладываются на все числовые форматы, от byte до double. Но наиболее заметны они для целочисленного типа int.

Если вы умножаете или делите два числа, результат вычисляется посредством целочисленной арифметики и сохраняется либо в int, либо в long. Если числа очень большие, то результат будет больше максимального числа, которое можно представить в этих числах. А значит, результат не сможет правильно закодироваться компьютером и не будет иметь смысла. Например, тип byte используется для представления чисел в диапазоне от -128 до 127. Если мы умножим 64 и 4, то результат 256, имеющий в двоичной записи 100000000 девять символов, будет закодирован, как 0, потому что byte использует лишь 8 символов.

Рассмотрим деление. Если вы делите в целочисленной арифметике, результат должен быть обязательно целочисленным. И значит, дробная часть будет потеряна. Например, 7/4 даёт нам 1.75, но в целочисленной арифметике это будет 1.

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

1. int a = 12345, b = 234567, c, d;
2. long e, f;
3.
4. c = a * b / b; // должно равняться а=12345
5. d = a / b * b; // тоже должно равняться а=12345
6. System.out.println(“a is “ + a +
7. “\nb is “ + b +
8. “\nc is “ + c +
9. “\nd is “ + d);
10.
11. e = (long)a * b / b;
12. f = (long)a / b * b;
13. System.out.println(
14. “\ne is “ + e +
15. “\nf is “ + f);

Результат работы данного фрагмента выдаст следующее:

a is 12345
b is 234567
c is -5965
d is 0
e is 12345
f is 0

Пусть вас не смущают числовые значения данного примера. Важно то, что при выполнении умножения первым мы получили переполнение (c is -5965), когда закодировали его в тип int. Однако мы можем получить правильный результат, если закондируем его в более длинный тип, как, например, long. В обоих случаях применение первым деления будет катастрофическим для результата, независимо от длины его типа.

Деление по модулю %

Результат деления по модулю - остаток от деления. Например, 7/4 равно 1 с остатком 3. Поэтому 7%4 = 3. Обычно операнды имеют целочисленный тип, но иногда оператор применяется и к числам с плавающей точкой. Также следует знать некоторые особенности данного оператора, когда операнды отрицательные.

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

17%5 = ? 17-5=12>5; 12-5=7>5; 7-5=2<5. Значит 17%5 = 2

21%7? 21-7=14>7; 14-7=7=7; 7-7=0<7. Значит 21%7 = 0

7.6%2.9? 7.6-2.9=4.7>2.9; 4.7-2.9=1.8<2.9. Значит 7.6%2.9=1.8

Заметьте: знак результата (положительный или отрицательный) целиком и полностью определён знаком левого операнда, то есть делимого.

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

Простое правило для отрицательных операндов такое: отбросьте знак минуса от операндов, произведите деление по модулю с положительными операндами, а затем поставьте перед результатом минус, если левый операнд (делимое) был отрицательным.

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


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

2. Операторы и присваивания (Выпуск 4)

2. Операторы и присвоения" (Operators and assignments)

Операторы в Java используются для разнообразных операций (всех необходимых). Все Javaоператоры перечислены в Таблице 2.1:


Таблица 2.1: Операторы Java в нисходящем порядке приоритетности массива

КатегорияОператоры
Унарные++ -- + - ! ~ ()
Арифметические* / %
+ -
Сдвиг<< >> >>>
Сравнение< <= > >= instanceof
== !=
Поразрядные& ^
Быстрого принятия решения&&
Условные?:
Присвоение= op=

2.1. Порядок вычисления

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

1. int [] a = { 4, 4 };
2. int b = 1;
3. a[b] = b = 0;

Какой элемент массива модифицирован? Какое значение b использовано для выбора элемента массива: 0 или 1? Согласно правилу вычисления слева направо самое левое выражение a[b] должно быть вычислено первым. Так что это a[1]. Затем вычисляется b, то есть активируется ссылка на переменную b. Затем вычисляется константа 0, что не требует никаких операций. Затем в вычисление включаются операторы. Вычисление при этом проводится в порядке приоритетности и ассоциацивности. Для присвоений ассоциативность - справа налево. Таким образом, сначала значение 0 присваивается переменной b, которая, в свою очередь, затем присваивается последнему элементу массива a.

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

2.2. Унарные операторы

Большинство оперторов имеют два операнда. Например, когда мы производим умножение, то делаем это с двумя числами. Однако, унарные операторы применяются только к одному операнду. В Java представлены семь унарных операторов:

  • Инкремент (увеличение значения на 1) и декремент (уменьшение значения на 1): ++ и --
  • Унарный плюс и минус: + и -
  • Поразрядное инвертирование: ~
  • Логическое дополнение: !
  • Приведение типа: ( )

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

Инкремент (увеличение значения на 1) и декремент (уменьшение значения на 1): ++ и --

Эти операторы изменяют значение выражения добавлением или вычитанием 1. Например, если переменная x типа int равна 10, то ++x (--x) равно 11 (соответственно, 9). Результат записывается в x.

В предыдущих примерах операторы находились перед переменной. Они могут находиться и после неё. И ++x, и x++ дают один и тот же результат, сохраняемый в x. Но значение всего выражения отличается. Например, если y = x++, то значение y равно исходному значению x. Если же y = ++x, то значение y на единицу больше, чем исходное значение x. В обоих случаях значение x увеличивается на 1.

Если инкремент (декремент) расположен слева от выражения, то выражение модифицируется перед тем (после того), как оно начинает участвовать в остальном вычислении. Это называется пре-инкремент (пре-декремент). Соответсвенно, если оператор находится справа от выражения, то в остальном вычислении участвует исходное значение этого выражения. И инкремент (декремент) происходит после того, как вычисление всего выражения завершено.

Таблица 2.2. показывает значения x и y после применения инкремента и декремента справа и слева.


Таблица 2.2: Примеры премодификации и постмодификации инкрементом и декрементом

КатегорияОператоры
Начальное значение xВыражениеИтоговое значение yИтоговое значение x
5y = x++56
5y = ++x66
5y = x--54
5y = --x44

Унарный плюс и минус: + и -

Унарные операторы + и - отличаются от обычных бинарных операторов + и -, которые трактуются, как сложение и вычитание. Унарный + не имеет никакого эфекта, кроме подчёркивания положительной природы численного литерала. Унарный - отрицает выражение (было положительным - стало отрицательным, было отрицательным - стало положительным). Примеры:

1. x = -3;
2. y = +3;
3. z = -(y + 6);

В примере единственное обоснование использования унарного плюса - подчёркивание, что переменной y присваивается положительное число. Общее выравнивание кода также более красиво с эстетической точки зрения :). Заметим, что в третьей строке унарный оператор применяется не к литералу, а к выражению. Таким образом, значение переменной z присваивается -9.

Поразрядное инвертирование: ~

Каждый примитивный тип Java представляется в виртуальной машине так, что представление не зависит от платформы. Это означает, что битовый шаблон, используемый для представления некоего отдельного числа, будет всегда тем же самым. Таким образом и манипулирование битами - процесс более эффективный, в силу независимости от платформы. Поразрядное инвертирование означает, что в двоичном представлении числа 0 заменяется на 1, а 1 - на 0.

Например, применение этого оператора к байту с содержимым 00001111 даст 11110000.

Логическое дополнение: !

Оператор ! инвертирует логическое значение выражения. Например, !true = false. Этот оператор часто используется в тестовой части if () конструкции. Эффект этого - изменение значения логического выражения. То есть обработчики if () и else могут быть легко обменяться местами. Рассмотрим два эквивалентных фрагмента кода:

1. public Object myMethod(Object x) {
2. if (x instanceof String) {
3. // do nothing
4. }
5. else {
6. x = x.toString();
7. }
8. return x;
9. }

и

1. public Object myMethod(Object x) {
2. if (!(x instanceof String)) {
3. x = x.toString();
4. }
5. return x;
6. }

В первом фрагменте, тестирование происходит в строке 2, но присвоение - в строке 6, если тест не пройдёт. Это сделано немного громоздко при помощи ветви else конструкции if/else. Второй фрагмент использует оператор логического дополнения, поэтому во второй строке тест инвертирован и может быть прочтён как Если x не строка. Если тест проходит, то обработка происходит в третьей строке и никакого отдельного else не требуется и результирующий код более краток и более читаем.

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

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

Приведение типа применяется для изменения типа значений примитивного типа. Например, мы можем форсировать конвертацию double к int как, например, в следующем фрагменте:

int circum = (int)(Math.PI * diameter);

Здесь приведение типа выражается фрагментом (int). Если бы этот фрагмент отсутсвовал, то компилятор бы выдал ошибку, поскольку double значение, возвращаемое арифметическим выражением не может быть точно представлено int значением, к которому присваивается. Присвоение типа - это способ, которым программист говорит компилятору: "Я знаю, что такое присвоение может быть рискованным, но верь мне, ведь я - специалист". Конечно, если результат при этом теряет точность так, что программа не функционирует должным образом, - это ответсвенность программиста.

Присвоение типа может быть применено и к объектным (непримитивным) типам. Это типично, например, для случаев, когда используются контейнеры типа Vector. Если вы помещаете объект типа String в Vector, то когда вы его извлекаете, тип, возвращаемый методом elementAt(), будет Object. Для использования извлечённого объекта как String нужно применить приведение типа, как, например, в следующем фрагменте кода:

1. Vector v = new Vector();
2. v.add ("Hello");
3. String s = (String)v.get(0);

В данном примере приведение типа имеет место в третьей строке (конструкция (String)). И хотя компилятор допускает такое приведение типа, во время выполнения программы всё равно проверяется, является объект извлечённый из Vector на самом деле объектом типа String. В будущих выпусках я расскажу о допустимых приведениях типов.


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