Глава 3 Thinking in Java 4th edition |
ОПЕРАТОРЫ
На нижнем уровне операции с данными в Java осуществляются посредством операторов. Язык Java создавался на основе C++, поэтому большинство этих операторов и конструкций знакомы программистам на C и C++. Также в Java были добавлены некоторые улучшения и упрощения. Если вы .знакомы с синтаксисом C или C++, бегло просмотрите эту и следующую главу, останавливаясь на тех местах, в которых Java отличается от этих языков. Если чтение дается вам с трудом, попробуйте обратиться к мультимедийному семинару Thinking in С, свободно загружаемому с сайта www.MindView.net. Он содержит аудиолекции, слайды, упражнения и решения, специально разработанные для быстрого ознакомления с синтаксисом C, необходимым для успешного овладения языком Java.Чего не видят фанатики обоих лагерей? Смотрите с минуты 25:40
ОПЕРАТОРЫ
На нижнем уровне операции с данными в Java осуществляются посредством операторов.
Язык Java создавался на основе C++, поэтому большинство этих операторов и конструкций знакомы программистам на C и C++. Также в Java были добавлены некоторые улучшения и упрощения.
Если вы .знакомы с синтаксисом C или C++, бегло просмотрите эту и следующую главу, останавливаясь на тех местах, в которых Java отличается от этих языков. Если чтение дается вам с трудом, попробуйте обратиться к мультимедийному семинару Thinking in С, свободно загружаемому с сайта www.MindView.net. Он содержит аудиолекции, слайды, упражнения и решения, специально разработанные для быстрого ознакомления с синтаксисом C, необходимым для успешного овладения языком Java.
Простые команды печати
В предыдущей главе была представлена команда печати Java
System.out.println("Какая длинная команда...");
Вероятно, вы заметили, что команда не только получается слишком длинной, но и плохо читается. Во многих языках до и после Java используется более простой подход к выполнению столь распространенной операции.
В главе 6 представлена концепция статического импорта, появившаяся в Java SE5, а также крошечная библиотека, упрощающая написание команд печати. Тем не менее для использования библиотеки не обязательно знать все подробности. Программу из предыдущей главы можно переписать в следующем виде:
//: operators/HelloDate.java
import java.util.*;
import static net.mindview.util.Print.*;
public class HelloDate {
public static void main(String[] args) {
print("Привет, сегодня: ");
print(new Date());
}
}
<spoiler text="Output:">
Привет, сегодня
Wed Oct 05 14-39 36 MDT 2005
</spoiler>
Результат смотрится гораздо приятнее. Обратите внимание на ключевое слово static во второй команде import.
Чтобы использовать эту библиотеку, необходимо загрузить архив с примерами кода. Распакуйте его и включите корневой каталог дерева в переменную окружения CLASSPATH вашего компьютера. Хотя использование net.mindview.util.Print упрощает программный код, оно оправданно не везде. Если программа содержит небольшое количество команд печати, я отказываюсь от import и записываю полный вызов System.out.println().
Операторы Java
Оператор получает один или несколько аргументов и создает на их основе новое значение. Форма передачи аргументов несколько иная, чем при вызове метода, но эффект тот же самый. Сложение (+), вычитание и унарный минус (-), умножение (*), деление (/) и присвоение (=) работают одинаково фактически во всех языках программирования.
Все операторы работают с операндами и выдают какой-то результат. Вдобавок некоторые операторы могут изменить значение операнда. Это называется побочным эффектом. Как правило, операторы, изменяющие значение своих операндов, используются именно ради побочного эффекта, но вы должны помнить, что полученное значение может быть использовано в программе и обычным образом, независимо от побочных эффектов.
Почти все операторы работают только с примитивами. Исключениями являются =, ==, !=, которые могут быть применены к объектам (и создают немало затруднений). Кроме того, класс String поддерживает операции + и +=.
Приоритет
Приоритет операций определяет порядок вычисления выражений с несколькими операторами. В Java существуют конкретные правила для определения очередности вычислений. Легче всего запомнить, что деление и умножение выполняются раньше сложения и вычитания. Программисты часто забывают правила предшествования, поэтому для явного задания порядка вычислений следует использовать круглые скобки. Например, взгляните на команды (1) и (2):
//: operators/Precedence.java
public class Precedence {
public static void main(String[] args) {
int x = 1, y = 2, z = 3;
int a = x + y - 2/2 + z; // (1)
int b = x + (y - 2)/(2 + z); // (2)
System.out.println("a = " + a + " b = " + b);
}
}
<spoiler text="Output:">
a = 5 b = 1
</spoiler>
Команды похожи друг на друга, но из результатов хорошо видно, что они имеют разный смысл в зависимости от присутствия круглых скобок.
Обратите внимание на оператор + в команде System.out.println. В данном контексте + означает конкатенацию строк, а не суммирование. Когда компилятор встречает объект String, за которым следует + и объект, отличный от String, он пытается преобразовать последний объект в String. Как видно из выходных данных, для а и b тип int был успешно преобразован в String.
Присвоение
Присвоение выполняется оператором =
Трактуется он так: «взять значение из правой части выражения (часто называемое просто значением) и скопировать его в левую часть (часто называемую именующим выражением)». Значением может быть любая константа, переменная или выражение, но в качестве именующего выражения обязательно должна использоваться именованная переменная (то есть для хранения значения должна выделяться физическая память). Например, вы можете присвоить постоянное значение переменной:
а = 4;
но нельзя присвоить что-либо константе — она не может использоваться в качестве именующего выражения (например, запись 4 = а недопустима).
Для примитивов присвоение выполняется тривиально. Так как примитивный тип хранит данные, а не ссылку на объект, то присвоение сводится к простому копированию данных из одного места в другое. Например, если команда а = b выполняется для примитивных типов, то содержимое b просто копируется в а. Естественно, последующие изменения а никак не отражаются на b. Для программиста именно такое поведение выглядит наиболее логично.
При присвоении объектов все меняется. При выполнении операций с объектом вы в действительности работаете со ссылкой, поэтому присвоение «одного объекта другому» на самом деле означает копирование ссылки из одного места в другое. Это значит, что при выполнении команды c = d для объектов в конечном итоге с и d указывают на один объект, которому изначально соответствовала только ссылка d. Сказанное демонстрирует следующий пример:
//: operators/Assignment.java
// Присвоение объектов имеет ряд хитростей
import static net.mindview.util.Print.*;
class Tank {
int level;
}
public class Assignment {
public static void main(String[] args) {
Tank t1 = new Tank();
Tank t2 = new Tank();
t1.level = 9;
t2.level = 47;
print("1: t1.level: " + t1.level +
", t2.level: " + t2.level);
t1 = t2;
print("2: t1.level: " + t1.level +
", t2.level: " + t2.level);
t1.level = 27;
print("3: t1.level: " + t1.level +
", t2.level: " + t2.level);
}
}
<spoiler text="Output:">
1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27
</spoiler>
Класс Tank предельно прост, и два его экземпляра (t1 и t2) создаются внутри метода main(). Переменной level для каждого экземпляра придаются различные значения, а затем ссылка t2 присваивается t1, в результате чего t1 изменяется.
Во многих языках программирования можно было ожидать, что t1 и t2 будут независимы все время, но из-за присвоения ссылок изменение объекта t1 отражается на объекте t2!
Это происходит из-за того, что t1 и t2 содержат одинаковые ссылки, указывающие на один объект. (Исходная ссылка, которая содержалась в t1 и указывала на объект со значением 9, была перезаписана во время присвоения и фактически потеряна; ее объект будет вскоре удален сборщиком мусора.)
Этот феномен совмещения имен часто называют синонимией (aliasing), и именно она является основным способом работы с объектами в Java. Но что делать, если совмещение имен нежелательно? Тогда можно пропустить присвоение и записать
t1.level = t2.level;
При этом программа сохранит два разных объекта, а не «выбросит» один из них, «привязав» ссылки t1 и t2 к единственному объекту. Вскоре вы поймете, что прямая работа с полями данных внутри объектов противоречит принципам объектно-ориентированной разработки. Впрочем, это непростой вопрос, так что пока вам достаточно запомнить, что присвоение объектов может таить в себе немало сюрпризов.
Совмещение имен во время вызова методов
Совмещение имен также может происходить при передаче объекта методу:
//: operators/PassObject.java
// Передача объектов методам может работать
// не так. как вы привыкли.
import static net.mindview.util.Print.*;
class Letter {
char c;
}
public class PassObject {
static void f(Letter y) {
y.c = 'z';
}
public static void main(String[] args) {
Letter x = new Letter();
x.c = 'a';
print("1: x.c: " + x.c);
f(x);
print("2: x.c: " + x.c);
}
}
<spoiler text="Output:">
1: x.c: a
2: x.c: z
</spoiler>
Во многих языках программирования метод f() создал бы копию своего параметра Letter у внутри своей области действия. Но из-за передачи ссылки строка
у.с = 'z';
на самом деле изменяет объект за пределами метода f().
Совмещение имен и решение этой проблемы — сложные темы. Будьте очень внимательными в таких случаях во избежание ловушек.
Арифметические операторы
Основные математические операторы
Основные математические операторы остаются неизменными почти во всех языках программирования:
- сложение ( + );
- вычитание ( - );
- деление ( / );
- умножение ( * )
- остаток от деления нацело ( % );
Деление нацело обрезает, а не округляет результат.
В Java также используется укороченная форма записи для того, чтобы одновременно произвести операцию и присвоение. Она обозначается оператором с последующим знаком равенства и работает одинаково для всех операторов языка (когда в этом есть смысл). Например, чтобы прибавить 4 к переменной х и присвоить результат х, используйте команду х += 4.
Следующий пример демонстрирует использование арифметических операций:
//: operators/MathOps.java
// Демонстрация математических операций.
import java.util.*;
import static net.mindview.util.Print.*;
public class MathOps {
public static void main(String[] args) {
// Создание и раскрутка генератора случайных чисел:
Random rand = new Random(47);
int i, j, k;
// Choose value from 1 to 100:
j = rand.nextInt(100) + 1;
print("j : " + j);
k = rand.nextInt(100) + 1;
print("k : " + k);
i = j + k;
print("j + k : " + i);
i = j - k;
print("j - k : " + i);
i = k / j;
print("k / j : " + i);
i = k * j;
print("k * j : " + i);
i = k % j;
print("k % j : " + i);
j %= k;
print("j %= k : " + j);
// Тесты для вещественных чисел:
float u, v, w; // также можно использовать double
v = rand.nextFloat();
print("v : " + v);
w = rand.nextFloat();
print("w : " + w);
u = v + w;
print("v + w : " + u);
u = v - w;
print("v - w : " + u);
u = v * w;
print("v * w : " + u);
u = v / w;
print("v / w : " + u);
// следующее также относится к типам
// char, byte, short, int. long и double:
u += v;
print("u += v : " + u);
u -= v;
print("u -= v : " + u);
u *= v;
print("u *= v : " + u);
u /= v;
print("u /= v : " + u);
}
}
<spoiler text="Output:">
j : 59
k : 56
j + k : 115
j - k : 3
k / j : 0
k * j : 3304
k % j : 56
j %= k : 3
v : 0.5309454
w : 0.0534122
v + w : 0.5843576
v - w : 0.47753322
v * w : 0.028358962
v / w : 9.940527
u += v : 10.471473
u -= v : 9.940527
u *= v : 5.2778773
u /= v : 9.940527
</spoiler>
Для получения случайных чисел создается объект Random. Если он создается без параметров, Java использует текущее время для раскрутки генератора, чтобы при каждом запуске программы выдавались разные числа.
Программа генерирует различные типы случайных чисел, вызывая соответствующие методы объекта Random: nextInt() и nextFloat() (также можно использовать nextLong() и nextDouble()). Аргумент nextInt() задает верхнюю границу генерируемых чисел. Нижняя граница равна 0, но для предотвращения возможного деления на 0 результат смещается на 1.
Унарные операторы плюс и минус
Унарные минус (-) и плюс (+) внешне не отличаются от аналогичных бинарных операторов. Компилятор выбирает нужный оператор в соответствии с контекстом использования. Например, команда
х = -а;
имеет очевидный смысл. Компилятор без труда разберется, что значит
х = а * -b;
но читающий код может запутаться, так что яснее будет написать так:
х = а * (-b);
Унарный минус меняет знак числа на противоположный. Унарный плюс существует «для симметрии», хотя и не производит никаких действий.
Автоувеличение и автоуменьшение
В Java, как и в C, существует множество различных сокращений. Сокращения могут упростить написание кода, а также упростить или усложнить его чтение.
Два наиболее полезных сокращения — это операторы увеличения (инкремента) и уменьшения (декремента) (также часто называемые операторами автоматического приращения и уменьшения). Оператор декремента записывается в виде -- и означает «уменьшить на единицу».
Оператор инкремента обозначается символами ++ и позволяет «увеличить на единицу». Например, если переменная а является целым числом, то выражение ++а будет эквивалентно (а = а + 1). Операторы инкремента и декремента не только изменяют переменную, но и устанавливают ей в качестве результата новое значение.
Каждый из этих операторов существует в двух версиях: префиксной и постфиксной. Префиксный инкремент значит, что оператор ++ записывается перед переменной или выражением, а при постфиксном инкременте оператор следует после переменной или выражения. Аналогично, при префиксном декременте оператор -- указывается перед переменной или выражением, а при постфиксном - после переменной или выражения.
Для префиксного инкремента и декремента (то есть ++а и --а) сначала выполняется операция, а затем выдается результат. Для постфиксной записи (а++ и а--) сначала выдается значение, и лишь затем выполняется операция. Например:
//: operators/AutoInc.java
// Demonstrates the ++ and -- operators.
import static net.mindview.util.Print.*;
public class AutoInc {
public static void main(String[] args) {
int i = 1;
print("i : " + i);
print("++i : " + ++i); // Pre-increment
print("i++ : " + i++); // Post-increment
print("i : " + i);
print("--i : " + --i); // Pre-decrement
print("i-- : " + i--); // Post-decrement
print("i : " + i);
}
}
<spoiler text="Output:">
i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1
</spoiler>
Вы видите, что при использовании префиксной формы результат получается после выполнения операции, тогда как с постфиксной формой он доступен до выполнения операции. Это единственные операторы (кроме операторов присваивания), которые имеют побочный эффект. (Иначе говоря, они изменяют свой операнд вместо простого использования его значения.)
Оператор инкремента объясняет происхождение названия языка C++; подразумевается «шаг вперед по сравнению с C». В одной из первых речей, посвященных Java, Билл Джой (один из его создателей) сказал, что «Java=C++--» («Си плюс плюс минус минус»). Он имел в виду, что Java — это C++, из которого убрано все, что затрудняет программирование, и поэтому язык стал гораздо проще. Продвигаясь вперед, вы увидите, что отдельные аспекты языка, конечно, проще, и все же Java не настолько проще C++.
Операторы сравнения и логические операторы
Операторы сравнения выдают логический (boolean) результат. Они проверяют, в каком отношении находятся значения их операндов. Если условие проверки истинно, оператор выдает true, а если ложно — false. К операторам сравнения относятся следующие: «меньше чем» (<), «больше чем» (>), «меньше чем или равно» (<=), «больше чем или равно» (>=), «равно» (==) и «не равно» (!=). «Равно» и «не равно» работают для всех примитивных типов данных, однако остальные сравнения не применимы к типу boolean.
Проверка объектов на равенство
Операции отношений == и != также работают с любыми объектами, но их смысл нередко сбивает с толку начинающих программистов на Java. Пример:
//: operators/Equivalence.java
public class Equivalence {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1 == n2);
System.out.println(n1 != n2);
}
}
<spoiler text="Output:">
false
true
</spoiler>
Выражение System.out.println(n1 == n2) выведет результат логического сравнения, содержащегося в скобках. Казалось бы, в первом случае результат должен быть истинным (true), а во втором — ложным (false), так как оба объекта типа Integer имеют одинаковые значения. Но в то время как содержимое объектов одинаково, ссылки на них разные, а операторы != и == сравнивают именно ссылки. Поэтому результатом первого выражения будет false, а второго — true. Естественно, такие результаты поначалу ошеломляют.
А если понадобится сравнить действительное содержимое объектов? Придется использовать специальный метод equals(), поддерживаемый всеми объектами (но не примитивами, для которых более чем достаточно операторов == и !=). Вот как это делается:
//: operators/EqualsMethod.java
public class EqualsMethod {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1.equals(n2));
}
}
<spoiler text="Output:">
true
</spoiler>
На этот раз результат окажется «истиной» (true), как и предполагалось. Но все не так просто, как кажется. Если вы создадите свой собственный класс вроде такого:
//: operators/EqualsMethod2.java
// Метод equals() по умолчанию не сравнивает содержимое.
class Value {
int i;
}
public class EqualsMethod2 {
public static void main(String[] args) {
Value v1 = new Value();
Value v2 = new Value();
v1.i = v2.i = 100;
System.out.println(v1.equals(v2));
}
}
<spoiler text="Output:">
false
</spoiler>
мы вернемся к тому, с чего начали: результатом будет false. Дело в том, что метод equals() по умолчанию сравнивает ссылки. Следовательно, пока вы не переопределите этот метод в вашем новом классе, не получите желаемого результата. К сожалению, переопределение будет рассматриваться только в главе 8, а пока осторожность и общее понимание принципа работы equals() позволит избежать некоторых неприятностей.
Большинство классов библиотек Java реализуют метод equals() по-своему, сравнивая содержимое объектов, а не ссылки на них.
Логические операторы
Логические операторы И (&&), ИЛИ (||) и НЕ (!) производят логические значения true и false, основанные на логических отношениях своих аргументов. В следующем примере используются как операторы сравнения, так логические операторы:
//: operators/Bool.java
// Операторы сравнений и логические операторы.
import java.util.*;
import static net.mindview.util.Print.*;
public class Bool {
public static void main(String[] args) {
Random rand = new Random(47);
int i = rand.nextInt(100);
int j = rand.nextInt(100);
print("i = " + i);
print("j = " + j);
print("i > j is " + (i > j));
print("i < j is " + (i < j));
print("i >= j is " + (i >= j));
print("i <= j is " + (i <= j));
print("i == j is " + (i == j));
print("i != j is " + (i != j));
// Treating an int as a boolean is not legal Java:
//! print("i && j is " + (i && j));
//! print("i || j is " + (i || j));
//! print("!i is " + !i);
print("(i < 10) && (j < 10) is "
+ ((i < 10) && (j < 10)) );
print("(i < 10) || (j < 10) is "
+ ((i < 10) || (j < 10)) );
}
}
<spoiler text="Output:">
i = 58
j = 55
i > j is true
i < j is false
i >= j is true
i <= j is false
i == j is false
i != j is true
(i < 10) && (j < 10) is false
(i < 10) || (j < 10) is false
</spoiler>
Операции И, ИЛИ и НЕ применяются только к логическим (boolean) значениям. Нельзя использовать в логических выражениях не-boolean-типы в качестве булевых, как это разрешается в C и C++. Неудачные попытки такого рода видны в строках, помеченных особым комментарием //! (этот синтаксис позволяет автоматически удалять комментарии для удобства тестирования). Последующие выражения вырабатывают логические результаты, используя операторы сравнений, после чего к полученным значениям примененяются логические операции.
Заметьте, что значение boolean автоматически переделывается в подходящее строковое представление там, где предполагается использование строкового типа String.
Определение int в этой программе можно заменить любым примитивным типом, за исключением boolean. Впрочем, будьте осторожны с вещественными числами, поскольку их сравнение проводится с крайне высокой точностью. Число, хотя бы чуть-чуть отличающееся от другого, уже считается неравным ему. Число, на тысячную долю большее нуля, уже не является нулем.
Ускоренное вычисление
При работе с логическими операторами можно столкнуться с феноменом, называемым «ускоренным вычислением». Это значит, что выражение вычисляется только до тех пор, пока не станет очевидно, что оно принимает значение «истина» или «ложь». В результате, некоторые части логического выражения могут быть проигнорированы в процессе сравнения. Следующий пример демонстрирует ускоренное вычисление:
//: operators/ShortCircuit.java
// Демонстрация ускоренного вычисления
// при использовании логических операторов
import static net.mindview.util.Print.*;
public class ShortCircuit {
static boolean test1(int val) {
print("test1(" + val + ")");
print("result: " + (val < 1));
return val < 1;
}
static boolean test2(int val) {
print("test2(" + val + ")");
print("result: " + (val < 2));
return val < 2;
}
static boolean test3(int val) {
print("test3(" + val + ")");
print("result: " + (val < 3));
return val < 3;
}
public static void main(String[] args) {
boolean b = test1(0) && test2(2) && test3(2);
print("expression is " + b);
}
}
<spoiler text="Output:">
test1(0)
result: true
test2(2)
result: false
expression is false
</spoiler>
Каждый из методов test() проводит сравнение своего аргумента и возвращает либо true, либо false. Также они выводят информацию о факте своего вызова. Эти методы используются в выражении:
testl(0) && test2(2) && test3(2)
Естественно было бы ожидать, что все три метода должны выполняться, но результат программы показывает другое. Первый метод возвращает результат true, поэтому вычисление выражения продолжается. Однако второй метод выдает результат false. Так как это автоматически означает, что все выражение будет равно false, зачем продолжать вычисления? Только лишняя трата времени. Именно это и стало причиной введения в язык ускоренного вычисления; отказ от лишних вычислений обеспечивает потенциальный выигрыш в производительности.
Литералы
Обычно, когда вы записываете в программе какое-либо значение, компилятор точно знает, к какому типу оно относится. Однако в некоторых ситуациях однозначно определить тип не удается. В таких случаях следует помочь компилятору определить точный тип, добавив дополнительную информацию в виде определенных символьных обозначений, связанных с типами данных. Эти обозначения используются в следующей программе:
//: operators/Literals.java
import static net.mindview.util.Print.*;
public class Literals {
public static void main(String[] args) {
int i1 = 0x2f; // Hexadecimal (lowercase)
print("i1: " + Integer.toBinaryString(i1));
int i2 = 0X2F; // Hexadecimal (uppercase)
print("i2: " + Integer.toBinaryString(i2));
int i3 = 0177; // Octal (leading zero)
print("i3: " + Integer.toBinaryString(i3));
char c = 0xffff; // max char hex value
print("c: " + Integer.toBinaryString(c));
byte b = 0x7f; // max byte hex value
print("b: " + Integer.toBinaryString(b));
short s = 0x7fff; // max short hex value
print("s: " + Integer.toBinaryString(s));
long n1 = 200L; // long suffix
long n2 = 200l; // long suffix (but can be confusing)
long n3 = 200;
float f1 = 1;
float f2 = 1F; // float suffix
float f3 = 1f; // float suffix
double d1 = 1d; // double suffix
double d2 = 1D; // double suffix
// (Hex and Octal also work with long)
}
}
<spoiler text="Output:">
i1: 101111
i2: 101111
i3: 1111111
c: 1111111111111111
b: 1111111
s: 111111111111111
</spoiler>
Последний символ обозначает тип записанного литерала. Прописная или строчная буква L определяет тип long (впрочем, строчная l может создать проблемы, потому что она похожа на цифру 1); прописная или строчная F соответствует типу float, а заглавная или строчная D подразумевает тип double.
Шестнадцатеричное представление (основание 16) работает со всеми встроенными типами данных и обозначается префиксом 0x или 0X с последующим числовым значением из цифр 0-9 и букв a-f, прописных или строчных. Если при определении переменной задается значение, превосходящее максимально для нее возможное (независимо от числовой формы), компилятор сообщит вам об ошибке. В программе указаны максимальные значения для типов char, byte и short. При выходе за эти границы компилятор автоматически сделает значение типом int и сообщит вам, что для присвоения понадобится сужающее приведение.
Восьмеричное представление (по основанию 8) обозначается начальным нулем в записи числа, состоящего из цифр 0 - 7. Для литеральной записи чисел в двоичном представлении в Java, C и C++ поддержки нет. Впрочем, при работе с шестнадцатеричныыми и восьмеричными числами часто требуется получить двоичное представление результата. Задача легко решается методами static toBinaryString() классов Integer и Long.
Экспоненциальная запись
Экспоненциальные значения записываются, по-моему, очень неудачно: 1.39e-47f. В науке и инженерном деле символом е обозначается основание натурального логарифма, равное примерно 2.718. (Более точное значение этой величины можно получить из свойства Math.E.)
Оно используется в экспоненциальных выражениях, таких как 1.39 * е exp47, что фактически значит 1.39 * 2.718 exp47. Однако во время изобретения языка FORTRAN было решено, что е будет обозначать «десять в степени», что достаточно странно, поскольку FORTRAN разрабатывался для науки и техники и можно было предположить, что его создатели обратят внимание на подобную неоднозначность.
Так или иначе, этот обычай был перенят в C, C++, а затем перешел в Java. Таким образом, если вы привыкли видеть в е основание натурального логарифма, вам придется каждый раз делать преобразование в уме: если вы увидели в Java выражение 1.39e-43f, на самом деле оно значит 1.39 * 10 exp43.
Если компилятор может определить тип автоматически, наличие завершающего суффикса типа не обязательно. В записи
long n3 = 200;
не существует никаких неясностей, и поэтому использование символа L после значения 200 было бы излишним. Однако в записи
float f4 = 1e-43f; // десять в степени
компилятор обычно трактует экспоненциальные числа как double. Без завершающего символа f он сообщит вам об ошибке и необходимости использования приведения для преобразования double к типу float.
Поразрядные операторы
Поразрядные логические операторы
Поразрядные операторы манипулируют отдельными битами в целочисленных примитивных типах данных. Результат определяется действиями булевой алгебры с соответствующими битами двух операндов.
Эти битовые операторы происходят от низкоуровневой направленности языка C, где часто приходится напрямую работать с оборудованием и устанавливать биты в аппаратных регистрах. Java изначально разрабатывался для управления телевизионными приставками, поэтому эта низкоуровневая ориентация все еще была нужна. Впрочем, вам вряд ли придется часто использовать эти операторы.
- Поразрядный оператор И ( & ) заносит 1 в выходной бит, если оба входных бита были равны 1; в противном случае результат равен 0.
- Поразрядный оператор ИЛИ ( | ) заносит 1 в выходной бит, если хотя бы один из битов операндов был равен 1; результат равен 0 только в том случае, если оба бита операндов были нулевыми.
- Оператор ИСКЛЮЧАЮЩЕЕ ИЛИ, XOR, ( ^ ) имеет результатом единицу тогда, когда один из входных битов был единицей, но не оба вместе.
- Поразрядный оператор НЕ ( ~ ), также называемый оператором двоичного дополнения, является унарным оператором, то есть имеет только один операнд. Поразрядное НЕ производит бит, «противоположный» исходному — если входящий бит является нулем, то в результирующем бите окажется единица, если входящий бит — единица, получится ноль.
Поразрядные операторы и логические операторы записываются с помощью одних и тех же символов, поэтому полезно запомнить мнемоническое правило: так как биты «маленькие», в поразрядных операторах используется всего один символ.
Поразрядные операторы могут комбинироваться со знаком равенства =, чтобы совместить операцию и присвоение: &=, |= и ^= являются допустимыми сочетаниями. (Так как ~ является унарным оператором, он не может использоваться вместе со знаком "=".)
Тип boolean трактуется как однобитовый, поэтому операции с ним выглядят по-другому. Вы вправе выполнить поразрядные И, ИЛИ и ИСКЛЮЧАЮЩЕЕ ИЛИ, но НЕ использовать запрещено (видимо, чтобы предотвратить путаницу с логическим НЕ). Для типа boolean поразрядные операторы производят тот же эффект, что и логические, за одним исключением — они не поддерживают ускоренного вычисления.
Кроме того, в число поразрядных операторов для boolean входит оператор ИСКЛЮЧАЮЩЕЕ ИЛИ, отсутствующий в списке логических операторов. Для булевых типов не разрешается использование операторов сдвига, описанных в следующем разделе.
Операторы сдвига
Операторы сдвига также манипулируют битами и используются только с примитивными целочисленными типами. Оператор сдвига влево (<<) сдвигает влево операнд, находящийся слева от оператора, на количество битов, указанное после оператора. Оператор сдвига вправо (>>) сдвигает вправо операнд, находящийся слева от оператора, на количество битов, указанное после оператора. При сдвиге вправо используется заполнение знаком: при положительном значении новые биты заполняются нулями, а при отрицательном — единицами.
В Java также поддерживается беззнаковый сдвиг вправо (>>>), использующий заполнение нулями: независимо от знака старшие биты заполняются нулями. Такой оператор не имеет аналогов в C и C++.
Если сдвигаемое значение относится к типу char, byte или short, эти типы приводятся к int перед выполнением сдвига, и результат также получится int. При этом используется только пять младших битов с «правой» стороны. Таким образом, нельзя сдвинуть битов больше, чем вообще существует для целого числа int. Если вы проводите операции с числами long, то получите результаты типа long. При этом будет задействовано только шесть младших битов с «правой» стороны, что предотвращает использование излишнего числа битов.
Сдвиги можно совмещать со знаком равенства (<<=, или >>=, или >>>=). Именующее выражение заменяется им же, но с проведенными над ним операциями сдвига. Однако при этом возникает проблема с оператором беззнакового правого сдвига, совмещенного с присвоением. При использовании его с типом byte или short вы не получите правильных результатов. Вместо этого они сначала будут преобразованы к типу int и сдвинуты вправо, а затем обрезаны при возвращении к исходному типу, и результатом станет -1. Следующий пример демонстрирует это:
//: operators/URShift.java
// Проверка беззнакового сдвига вправо
import static net.mindview.util.Print.*;
public class URShift {
public static void main(String[] args) {
int i = -1;
print(Integer.toBinaryString(i));
i >>>= 10;
print(Integer.toBinaryString(i));
long l = -1;
print(Long.toBinaryString(l));
l >>>= 10;
print(Long.toBinaryString(l));
short s = -1;
print(Integer.toBinaryString(s));
s >>>= 10;
print(Integer.toBinaryString(s));
byte b = -1;
print(Integer.toBinaryString(b));
b >>>= 10;
print(Integer.toBinaryString(b));
b = -1;
print(Integer.toBinaryString(b));
print(Integer.toBinaryString(b>>>10));
}
}
<spoiler text="Output:">
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
</spoiler>
В последней команде программы полученное значение не приводится обратно к b, поэтому получается верное действие.
Следующий пример демонстрирует использование всех операторов, так или иначе связанных с поразрядными операциями:
//: operators/BitManipulation.java
// Использование поразрядных операторов.
import java.util.*;
import static net.mindview.util.Print.*;
public class BitManipulation {
public static void main(String[] args) {
Random rand = new Random(47);
int i = rand.nextInt();
int j = rand.nextInt();
printBinaryInt("-1", -1);
printBinaryInt("+1", +1);
int maxpos = 2147483647;
printBinaryInt("maxpos", maxpos);
int maxneg = -2147483648;
printBinaryInt("maxneg", maxneg);
printBinaryInt("i", i);
printBinaryInt("~i", ~i);
printBinaryInt("-i", -i);
printBinaryInt("j", j);
printBinaryInt("i & j", i & j);
printBinaryInt("i | j", i | j);
printBinaryInt("i ^ j", i ^ j);
printBinaryInt("i << 5", i << 5);
printBinaryInt("i >> 5", i >> 5);
printBinaryInt("(~i) >> 5", (~i) >> 5);
printBinaryInt("i >>> 5", i >>> 5);
printBinaryInt("(~i) >>> 5", (~i) >>> 5);
long l = rand.nextLong();
long m = rand.nextLong();
printBinaryLong("-1L", -1L);
printBinaryLong("+1L", +1L);
long ll = 922337207L;
printBinaryLong("maxpos", ll);
long lln = -922337208L;
printBinaryLong("maxneg", lln);
printBinaryLong("l", l);
printBinaryLong("~l", ~l);
printBinaryLong("-l", -l);
printBinaryLong("m", m);
printBinaryLong("l & m", l & m);
printBinaryLong("l | m", l | m);
printBinaryLong("l ^ m", l ^ m);
printBinaryLong("l << 5", l << 5);
printBinaryLong("l >> 5", l >> 5);
printBinaryLong("(~l) >> 5", (~l) >> 5);
printBinaryLong("l >>> 5", l >>> 5);
printBinaryLong("(~l) >>> 5", (~l) >>> 5);
}
static void printBinaryInt(String s, int i) {
print(s + ", int: " + i + ", binary:\n " +
Integer.toBinaryString(i));
}
static void printBinaryLong(String s, long l) {
print(s + ", long: " + l + ", binary:\n " +
Long.toBinaryString(l));
}
}
<spoiler text="Output:">
-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
1
maxpos, int: 2147483647, binary:
1111111111111111111111111111111
maxneg, int: -2147483648, binary:
0
i, int: -1172028779, binary:
1
~i, int: 1172028778, binary:
101101010
-i, int: 1172028779, binary:
101101011
j, int: 1717241110, binary:
1100010110
i & j, int: 570425364, binary:
000010100
i | j, int: -25213033, binary:
1111110111
i ^ j, int: -595638397, binary:
1111000011
i << 5, int: 1149784736, binary:
010100000
i >> 5, int: -36625900, binary:
11111010100
(~i) >> 5, int: 36625899, binary:
1011
i >>> 5, int: 97591828, binary:
010100
(~i) >>> 5, int: 36625899, binary:
1011
</spoiler>
Два метода в конце, printBinaryInt() и printBinaryLong(), получают в качестве параметров, соответственно, числа int и long и выводят их в двоичном формате вместе с сопроводительным текстом. Вместе с демонстрацией поразрядных операций для типов int и long этот пример также выводит минимальное и максимальное значение, +1 и -1 для этих типов, чтобы вы лучше понимали, как они выглядят в двоичном представлении. Заметьте, что старший бит обозначает знак: 0 соответствует положительному и 1 — отрицательному числам. Результат работы для типа int приведен в конце листинга
Тернарный оператор «если-иначе»
Тернарный оператор необычен тем, что он использует три операнда. И все же это действительно оператор, так как он производит значение, в отличие от обычной конструкции выбора if-else, описанной в следующем разделе. Выражение записывается в такой форме:
логическое-условие ? выражение0 : выражение1
Если логическое-условие истинно (true), то затем вычисляется выражение0, и именно его результат становится результатом выполнения всего оператора. Если же логическое-условие ложно (false), то вычисляется выражение1, и его значение становится результатом работы оператора.
Пример использования тернарного оператора:
//: operators/TernaryIfElse.java
import static net.mindview.util.Print.*;
public class TernaryIfElse {
static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}
static int standardIfElse(int i) {
if(i < 10)
return i * 100;
else
return i * 10;
}
public static void main(String[] args) {
print(ternary(9));
print(ternary(10));
print(standardIfElse(9));
print(standardIfElse(10));
}
}
<spoiler text="Output:">
900
100
900
</spoiler>
Конечно, здесь можно было бы использовать стандартную конструкцию if-else (описываемую чуть позже), но тернарный оператор гораздо компактнее. Хотя C (где этот оператор впервые появился) претендует на звание лаконичного языка, и тернарный оператор вводился отчасти для достижения этой цели, будьте благоразумны и не используйте его всюду и постоянно — он может ухудшить читаемость программы.
Операторы + и += для String
В Java существует особый случай использования оператора: операторы + и += могут применяться для конкатенации (объединения) строк, и вы уже это видели. Такое действие для этих операторов выглядит вполне естественно, хотя оно и не соответствует традиционным принципам их использования.
При создании C++ в язык была добавлена возможность перегрузки операторов, позволяющей программистам C++ изменять и расширять смысл почти любого оператора. К сожалению, перегрузка операторов, в сочетании с некоторыми ограничениями C++, создала немало проблем при проектировании классов. Хотя реализацию перегрузки операторов в Java можно было осуществить проще, чем в C++ (это доказывает язык C#, где существует простой механиз перегрузки), эту возможность все же посчитали излишне сложной, и поэтому программистам на Java не дано реализовать свои собственные перегруженные операторы, как это делают программисты на C++.
Использование + и += для строк (String) имеет интересные особенности. Если выражение начинается строкой, то все последующие операнды также будут предварительно преобразованы в строчное представление! (помните, что компилятор превращает символы в кавычках в объект String).
int х = 0; у = 1; z = 2;
String s = "х, у, z";
System.out.println(s + x + у + z),
В данном случае компилятор Java приводит переменные х, у и z к их строковому представлению, вместо того чтобы сначала арифметически сложить их. А если вы запишете
System.out.println(x + s);
то и здесь Java преобразует x в строку.
Типичные ошибки при использовании операторов
Многие программисты склонны второпях записывать выражение без скобок, даже когда они не уверены в последовательности вычисления выражения. Это верно и для Java.
Еще одна распространенная ошибка в C и C++ выглядит следующим образом:
while(x = у) { // .
}
Программист хотел выполнить сравнение (==), а не присвоение. В C и C++ результат этого выражения всегда будет истинным, если только у не окажется нулем; вероятно, возникнет бесконечный цикл. В языке Java результат такого выражения не будет являться логическим типом (boolean), а компилятор ожидает в этом выражении именно boolean и не разрешает использовать целочисленный тип int, поэтому вовремя сообщит вам об ошибке времени компиляции, упредив проблему еще перед запуском программы. Поэтому подобная ошибка в Java никогда не происходит. (Программа откомпилируется только в одном случае: если х и у одновременно являются типами boolean, и тогда выражение х = у будет допустимо, что может привести к ошибке.)
Похожая проблема возникает в C и C++ при использовании поразрядных операторов И и ИЛИ вместо их логических аналогов. Поразрядные И и ИЛИ записываются одним символом (& и |), в то время как логические И и ИЛИ требуют в написании двух символов (&& и ||). Так же, как и в случае с операторами = и ==, легко ошибиться и набрать один символ вместо двух. В Java компилятор предотвращает такие ошибки, так как он не позволяет использовать тип данных в неподходящем контексте.
Операторы приведения
Слово приведение используется в смысле «приведение к другому типу». В определенных ситуациях Java самостоятельно преобразует данные к другим типам. Например, если вещественной переменной присваивается целое значение, компилятор автоматически выполняет соответствующее преобразование (int преобразуется во float). Приведение позволяет сделать замену типа более очевидной или выполнить ее принудительно в случаях, где это не происходит в обычном порядке.
Чтобы выполнить приведение явно, запишите необходимый тип данных (включая все модификаторы) в круглых скобках слева от преобразуемого значения. Пример:
//: operators/Casting.java
public class Casting {
public static void main(String[] args) {
int i = 200;
long lng = (long)i;
lng = i; // "Расширение", явное преобразование не обязательно
long lng2 = (long)200;
lng2 = 200;
// "Сужающее" преобразование:
i = (int)lng2; // Преобразование необходимо
}
}
Как видите, приведение может выполняться и для чисел, и для переменных. Впрочем, в указанных примерах приведение является излишним, поскольку компилятор при необходимости автоматически преобразует целое int к типу long. Однако это не мешает вам выполнять необязательные приведения — например, чтобы подчеркнуть какое-то обстоятельство или просто для того, чтобы сделать программу более понятной. В других ситуациях приведение может быть необходимо для нормальной компиляции программы.
В C и C++ приведение могло стать источником ошибок и неоднозначности. В Java приведение безопасно, за одним исключением: при выполнении так называемого сужающего приведения (то есть от типа данных, способного хранить больше информации, к менее содержательному типу данных), то есть при опасности потери данных. В таком случае компилятор заставляет вас выполнить явное приведение; фактически он говорит: «это может быть опасно, но, если вы уверены в своей правоте, опишите действие явно». В случае с расширяющим приведением явное описание не понадобится, так как новый тип данных способен хранить больше информации, чем прежний, и поэтому потеря данных исключена.
В Java разрешается приводить любой простейший тип данных к любому другому простейшему типу, но это не относится к типу boolean, который вообще не подлежит приведению. Классы также не поддерживают произвольное приведение. Чтобы преобразовать один класс в другой, требуются специальные методы. (Как будет показано позднее, объекты можно преобразовывать в рамках семейства типов; объект Дуб можно преобразовать в Дерево и наоборот, но не к постороннему типу вроде Камня.)
Округление и усечение
При выполнении сужающих преобразований необходимо обращать внимание на усечение и округление данных. Например, как должен действовать компилятор Java при преобразовании вещественного числа в целое? Скажем, если значение 29.7 приводится к типу int, что получится — 29 или 30 ?
Ответ на этот вопрос может дать следующий пример:
//: operators/CastingNumbers.java
// Что происходит при приведении типов
// float или double к целочисленным значениям?
import static net.mindview.util.Print.*;
public class CastingNumbers {
public static void main(String[] args) {
double above = 0.7, below = 0.4;
float fabove = 0.7f, fbelow = 0.4f;
print("(int)above: " + (int)above);
print("(int)below: " + (int)below);
print("(int)fabove: " + (int)fabove);
print("(int)fbelow: " + (int)fbelow);
}
}
<spoiler text="Output:">
(int)above: 0
(int)below: 0
(int)fabove: 0
(int)fbelow: 0
</spoiler>
Отсюда и ответ на наш вопрос — приведение от типов с повышенной точностью double и float к целочисленным значениям всегда осуществляется с усечением целой части. Если вы предпочитаете, чтобы результат округлялся, используйте метод round() из java.lang.Math. Так как этот метод является частью java.lang, дополнительное импортирование не потребуется.
Повышение
Вы можете обнаружить, что при проведении любых математических и поразрядных операций примитивные типы данных, меньшие int (то есть char, byte и short), приводятся к типу int перед проведением операций, и получаемый результат имеет тип int. Поэтому, если вам снова понадобится присвоить его меньшему типу, придется использовать приведение. (И тогда возможна потеря информации.) В основном самый емкий тип данных, присутствующий в выражении, и определяет величину результата этого выражения; так, при перемножении float и double результатом станет double, а при сложении long и int вы получите в результате long.
В Java отсутствует sizeof()
В C и C++ оператор sizeof() выдает количество байтов, выделенных для хранения данных. Главная причина для использования sizeof() — переносимость программы. Различным типам данных может отводиться различное количество памяти на разных компьютерах, поэтому для программиста важно определить размер этих типов перед проведением операций, зависящих от этих величин. Например, один компьютер выделяет под целые числа 32 бита, а другой — всего лишь 16 бит. В результате на первой машине программа может хранить в целочисленном представлении числа из большего диапазона. Конечно, аппаратная совместимость создает немало хлопот для программистов на C и C++.
В Java оператор sizeof() не нужен, так как все типы данных имеют одинаковые размеры на всех машинах. Вам не нужно заботиться о переносимости на низком уровне — она встроена в язык.
Сводка операторов
Следующий пример показывает, какие примитивные типы данных используются с теми или иными операторами. Вообще-то это один и тот же пример, повторенный много раз, но для разных типов данных. Файл должен компилироваться без ошибок, поскольку все строки, содержащие неверные операции, предварены символами //! :
//: operators/AllOps.java
// Проверяет все операторы со всеми
// примитивными типами данных, чтобы показать,
// какие операции допускаются компилятором Java
public class AllOps {
// To accept the results of a boolean test:
void f(boolean b) {}
void boolTest(boolean x, boolean y) {
// Arithmetic operators:
//! x = x * y;
//! x = x / y;
//! x = x % y;
//! x = x + y;
//! x = x - y;
//! x++;
//! x--;
//! x = +y;
//! x = -y;
// Relational and logical:
//! f(x > y);
//! f(x >= y);
//! f(x < y);
//! f(x <= y);
f(x == y);
f(x != y);
f(!y);
x = x && y;
x = x || y;
// Bitwise operators:
//! x = ~y;
x = x & y;
x = x | y;
x = x ^ y;
//! x = x << 1;
//! x = x >> 1;
//! x = x >>> 1;
// Compound assignment:
//! x += y;
//! x -= y;
//! x *= y;
//! x /= y;
//! x %= y;
//! x <<= 1;
//! x >>= 1;
//! x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! char c = (char)x;
//! byte b = (byte)x;
//! short s = (short)x;
//! int i = (int)x;
//! long l = (long)x;
//! float f = (float)x;
//! double d = (double)x;
}
void charTest(char x, char y) {
// Arithmetic operators:
x = (char)(x * y);
x = (char)(x / y);
x = (char)(x % y);
x = (char)(x + y);
x = (char)(x - y);
x++;
x--;
x = (char)+y;
x = (char)-y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x= (char)~y;
x = (char)(x & y);
x = (char)(x | y);
x = (char)(x ^ y);
x = (char)(x << 1);
x = (char)(x >> 1);
x = (char)(x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean bl = (boolean)x;
byte b = (byte)x;
short s = (short)x;
int i = (int)x;
long l = (long)x;
float f = (float)x;
double d = (double)x;
}
void byteTest(byte x, byte y) {
// Arithmetic operators:
x = (byte)(x* y);
x = (byte)(x / y);
x = (byte)(x % y);
x = (byte)(x + y);
x = (byte)(x - y);
x++;
x--;
x = (byte)+ y;
x = (byte)- y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x = (byte)~y;
x = (byte)(x & y);
x = (byte)(x | y);
x = (byte)(x ^ y);
x = (byte)(x << 1);
x = (byte)(x >> 1);
x = (byte)(x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean bl = (boolean)x;
char c = (char)x;
short s = (short)x;
int i = (int)x;
long l = (long)x;
float f = (float)x;
double d = (double)x;
}
void shortTest(short x, short y) {
// Arithmetic operators:
x = (short)(x * y);
x = (short)(x / y);
x = (short)(x % y);
x = (short)(x + y);
x = (short)(x - y);
x++;
x--;
x = (short)+y;
x = (short)-y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x = (short)~y;
x = (short)(x & y);
x = (short)(x | y);
x = (short)(x ^ y);
x = (short)(x << 1);
x = (short)(x >> 1);
x = (short)(x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean bl = (boolean)x;
char c = (char)x;
byte b = (byte)x;
int i = (int)x;
long l = (long)x;
float f = (float)x;
double d = (double)x;
}
void intTest(int x, int y) {
// Arithmetic operators:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x = ~y;
x = x & y;
x = x | y;
x = x ^ y;
x = x << 1;
x = x >> 1;
x = x >>> 1;
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean bl = (boolean)x;
char c = (char)x;
byte b = (byte)x;
short s = (short)x;
long l = (long)x;
float f = (float)x;
double d = (double)x;
}
void longTest(long x, long y) {
// Arithmetic operators:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x = ~y;
x = x & y;
x = x | y;
x = x ^ y;
x = x << 1;
x = x >> 1;
x = x >>> 1;
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean bl = (boolean)x;
char c = (char)x;
byte b = (byte)x;
short s = (short)x;
int i = (int)x;
float f = (float)x;
double d = (double)x;
}
void floatTest(float x, float y) {
// Arithmetic operators:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
//! x = ~y;
//! x = x & y;
//! x = x | y;
//! x = x ^ y;
//! x = x << 1;
//! x = x >> 1;
//! x = x >>> 1;
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
//! x <<= 1;
//! x >>= 1;
//! x >>>= 1;
//! x &= y;
//! x ^= y;
//! x |= y;
// Casting:
//! boolean bl = (boolean)x;
char c = (char)x;
byte b = (byte)x;
short s = (short)x;
int i = (int)x;
long l = (long)x;
double d = (double)x;
}
void doubleTest(double x, double y) {
// Arithmetic operators:
x = x * y;
x = x / y;
x = x % y;
x = x + y;
x = x - y;
x++;
x--;
x = +y;
x = -y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
//! x = ~y;
//! x = x & y;
//! x = x | y;
//! x = x ^ y;
//! x = x << 1;
//! x = x >> 1;
//! x = x >>> 1;
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
//! x <<= 1;
//! x >>= 1;
//! x >>>= 1;
//! x &= y;
//! x ^= y;
//! x |= y;
// Casting:
//! boolean bl = (boolean)x;
char c = (char)x;
byte b = (byte)x;
short s = (short)x;
int i = (int)x;
long l = (long)x;
float f = (float)x;
}
}
Заметьте, что действия с типом boolean довольно ограничены. Ему можно присвоить значение true или false, проверить на истинность или ложность, но нельзя добавить логические переменные к другим типам или произвести с ними любые иные операции.
В случае с типами char, byte и short можно заметить эффект повышения при использовании арифметических операторов. Любая арифметическая операция с этими типами дает результат типа int, который затем нужно явно приводить к изначальному типу (сужающее приведение, при котором возможна потеря информации). При использовании значений типа int приведение осуществлять не придется, потому что все значения уже имеют этот тип.
Однако не заблуждайтесь относительно безопасности происходящего. При перемножении двух достаточно больших целых чисел int произойдет переполнение. Следующий пример демонстрирует сказанное:
//: operators/Overflow.java
// Сюрприз! В Java можно получить переполнение.
public class Overflow {
public static void main(String[] args) {
int big = Integer.MAX_VALUE;
System.out.println("большое = " + big);
int bigger = big * 4;
System.out.println("еще больше = " + bigger);
}
}
<spoiler text="Output:">
большое = 2147483647
еще больше = -4
</spoiler>
Компилятор не выдает никаких ошибок или предупреждений, и во время исполнения не возникнет исключений. Язык Java хорош, но хорош не настолько.
Совмещенное присваивание не требует приведения для типов char, byte, short, хотя для них и производится повышение, как и в случае с арифметическими операциями. С другой стороны, отсутствие приведения в таких случаях, несомненно, упрощает программу.
Можно легко заметить, что за исключением типа boolean, любой примитивный тип может быть преобразован к другому примитиву. Как упоминалось ранее, необходимо остерегаться сужающего приведения при преобразованиях к меньшему типу, так как при этом возникает риск потери информации.
Резюме
Читатели с опытом работы на любом языке семейства C могли убедиться, что операторы Java почти нйчем не отличаются от классических. Если же материал этой главы показался трудным, обращайтесь к мультимедийной презентации «Thinking in C» (www.MindView.net).
------------------------
ТРИО теплый пол отзыв
Vkjust отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Я уже пять лет не занимаюсь сайтом, так как работаю по 12 часов. Образование у меня среднее, и по этому нет нормальной работы. Если бы сайт приносил по 100$ в месяц, я бы добавлял по 50 статей каждый месяц. Если отправите пожертвования, я оставлю Ваши имена и фамилии в списке благодетелей !
Bitcoin: 1AP3JCZVFpHzZwcAyNztbrbFFiLdnKbY1j
Litecoin LfHXHz4k6LnDNNvCodd5pj9aW4Qk67KoUD
Dogecoin D9HHvKNFZxRhjtoh6uZYjMjgcZewwiUME9
Есть также другие кошельки.
Чего не видят фанатики обоих лагерей? Смотрите с минуты 25:40
Категория: Книги по Java
Комментарии |