Java 8 операции

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 выражения, инструкции и блоки».
Предыдущая статья — «Переменные в Java 8».

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

Содержание

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

Преобразование примитивных типов

Расширяющее преобразование примитивов

Сужающее преобразование примитивов

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

Унарные операции

Отличие постфиксного и префиксного инкремента и декремента

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

Логические И и ИЛИ

Операция instanceof

Тернарная операция

Битовые операции

Присвоение с выполнением другой операции

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

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

Операция «=» позволяет присвоить значение переменной:

КОНСТАНТНЫЕ значения до int  можно присвоить без приведения типа к переменным меньшего размера (например short  в byte), если значение помещается в эту переменную.

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

Примеры:

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

 

Преобразование примитивных типов

Есть два типа преобразования примитивных типов:

Расширяющее преобразование примитивов

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

  • byte  -> short, int, long, float, double
  • short  -> int, long, float, double
  • char  -> int, long, float, double
  • int  -> long, float, double
  • long  -> float, double
  • float  -> double

Расширяющее преобразование не приводит к потере информации в следующих случаях:

  • из целого типа в другой целый тип
  • из byte, shortchar  в тип с плавающей точкой
  • из int  в double
  • из float  в double  в выражении с strictfp  (это такой особый режим вычислений с плавающей точкой, возможно, распишу позже).

Расширяющее преобразование из float  в double  в обычном режиме (без strictfp ) может привести к потере точности.

Расширяющее преобразование  int  во float  или из long  во float, или из long  в double  может привести к потере точности, то есть результат может потерять несколько наименее значимых бит информации, что приведёт к получению округлённого значения.

Примеры расширяющего преобразования примитивов:

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

Сужающее преобразование примитивов

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

  • short  -> byte, char
  • char  -> byte , short
  • int  -> byte , short , char
  • long  -> byte , short , char , int
  • float  -> byte , short , char , int , long
  • double  -> byte , short , char , int , long , float

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

Преобразование double  во float  может привести к потере точности и получению значения -0.0f или +0.0f вместо очень маленького значения double, а также к -Infinity  и +Infinity  вместо очень большого значения double. NaN  из double  преобразуется в NaN  из float. Infinity  в double  преобразуется в Infinity  в float  того же знака.

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

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

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

 

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

Арифметические операции позволяют выполнять сложение (операция «+»), вычитание (операция «-»), умножение (операция «*»), деление (операция «/») и взятие остатка (операция «%»).  Эти операции имеют такие же приоритеты, что и в обычной математике, которую изучают в школе, то есть умножение и деление выполняется перед сложением и вычитанием.

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

При выполнении арифметической операции над операндами разных типов результат операции будет иметь наибольший тип, Что можно описать следующими правилами:

  1. Если один из операндов имеет тип double, то результат выражения имеет тип double, иначе смотри пункт 2.
  2. Если один из операндов имеет тип float, то результат выражения имеет тип float, иначе смотри пункт 3.
  3. Если один из операндов имеет тип long, то результат выражения имеет тип long, иначе результат выражения имеет тип int.

(например, при сложении int  и long  результат будет иметь тип long, а при сложении long  и float  результат будет иметь тип float, а при сложении float  и double  результат будет иметь тип double).

Если результат операции с целочисленными данными выходит за диапазон, то старшие биты отбрасываются, и результирующее значение будет совершенно неверным. При попытке деления на 0 возникает исключение java.lang.ArithmeticException / zero.

При выполнении операций с плавающей точкой при выходе за верхнюю или нижнюю границу диапазона получается +Infinity  ( Double.POSITIVE_INFINITY  и Float.POSITIVE_INFINITY) и -Infinity  ( Double.NEGATIVE_INFINITY  и Float.NEGATIVE_INFINITY ) соответственно, а при получении слишком маленького числа, которое не может быть нормально сохранено в этом типе данных получается -0.0 или +0.0.

При выполнении операций с плавающей точкой результат NaN  ( Double.NaN  и Float.NaN) получается в следующих случаях:

  • Когда один из операндов NaN
  • В неопределённых результатах:
    • Деления 0/0, ∞/∞, ∞/−∞, −∞/∞,  −∞/−∞
    • Умножения 0×∞ and 0×−∞
    • Степень 1
    • сложения ∞ + (−∞), (−∞) + ∞ и эквивалентные вычитания.
  • Операции с комплексными результатами:
    • Квадратный корень из отрицательного числа
    • Логарифм отрицательного числа
    • Тангенс 90 градусов и ему подобных (или π/2 радиан)
    • Обратный синус и косинус от числа меньше −1 и больше +1.

Унарные операции

Унарными называются операции, которые имеют только один операнд. Унарные операции бывают префиксные и постфиксные.

Постфиксные унарные операции ставятся после операнда:

  • Инкремент (увеличение на 1) ++
  • Декремент (уменьшение на 1) --

Примеры:

 

 

Префиксные унарные операции ставятся перед операндом:

  • Унарный плюс (обозначает положительные числа, хотя числа положительными будут и без него) +
  • Унарный минус (обозначает отрицательные числа) -
  • Логическое НЕ (инвертирует значение логического типа, превращая true  в false и наоборот) !
  • Префиксный инкремент (увеличивает значение на 1) ++
  • Префиксный декремент (уменьшает значение на 1) --

Примеры:

Отличие постфиксного и префиксного инкремента и декремента

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

Пример:

Не помню, описывал ли я это, но две косые черты //  означают комментарий. Компилятор игнорирует любой текст, находящийся правее //, что позволяет записать какое-нибудь пояснение для будущего читателя программы. Строки System.out.println  выводят текст в консоль.

Этот пример выводит в консоль следующее:

Как видно из примера y1  и y2  стали равны  значениям x1  и x2, которые получились после осуществления операций инкремента и декремента соответственно, а z1  и z2  стали равны значениям x1  и x2, которые были до операций инкремента и декремента.

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

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

Вот список операций сравнения в Java:

  • ==  равенство (обратите внимание, что нужно использовать два символа равно для сравнения, а не один)
  • !=  неравенство
  • >  больше
  • >=  больше или равно
  • <  меньше
  • <=  меньше или равно

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

Пример:

При сравнении используются следующие правила:

  • Если один из операндов NaN, то результат false.
  • -Infinity  меньше +Infinity
  • -0.0 с плавающей точкой равен +0.0 с плавающей точкой
  • При сравнении примитивов разных типов значение меньшего типа преобразуется в больший тип.

Логические И и ИЛИ

Логическое И &&  и Логическое ИЛИ || ведут себя вполне ожидаемо для логического И или логического ИЛИ:

Логическое И &&  вычисляет свой правый операнд только в том случае, если левый равен true. Если левый операнд равен false, то сразу возвращается false. Логическое ИЛИ ||  вычисляет правый операнд только в том случае, если левый равен false. Если левый операнд равен true, то сразу возвращается true. Эти два правила сокращения вычислений позволяют сразу откинуть последующие вычисления, если результат всего выражения уже известен. Это можно использовать для проверки на null  перед проверкой результата какого-либо метода объекта (будет описано в дальнейшем):

Операция instanceof

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

Возвращается true, если obj1  не null  и является экземпляром класса A  или экземпляром дочернего класса A  или экземпляром класса, реализующего интерфейс A.

Если левый операнд равен null, то результатом будет false. Код ниже выведет “NO”:

Тернарная операция

Операция «?:» называется тернарной, потому что он принимает три операнда.

Тернарная операция вычисляет <выражение_boolean>, если оно равно true, то вычисляет и возвращает <выражение1>, а если false, то <выражение2> .

Битовые операции

Битовые операции в Java используются редко, но знать их нужно. Работают они так же, как и в Javascript.

Битовые операции в Java:

  • Битовый сдвиг влево <<
  • Битовый знаковый сдвиг вправо >>
  • Беззнаковый битовый сдвиг вправо >>>. Он отличается от >>  тем, что ставит 0 в самую левую позицию, а >>  ставит то, что было в знаковом бите.
  • Инвертация бит ~  меняет 0 на 1 и 1 на 0 во всех битах.
  • Битовый  &  применяет побитовую операцию И
  • Битовый |  применяет побитовую операцию ИЛИ
  • Битовый ^  применяет XOR (исключающее или)

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

Присвоение с выполнением другой операции

Операции  += (сложение с присвоением), -= (вычитание с присвоением),  *= (умножение с присвоением), /= (деление с присвоением), %= (взятие остатка с присвоением), &= (битовый И с присвоением), ^=  (битовое исключающее ИЛИ с присвоением), |= (битовое ИЛИ с присвоением), <<= (сдвиг влево с присвоением), >>= (знаковый сдвиг вправо с присвоением), >>>=  (беззнаковый сдвиг вправо с присвоением) позволяют сразу выполнить операции и присвоить результат другой переменной.

Они работают так:

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

, где T  — это тип переменной E1.

То есть int x1 += x2 эквивалентно int x1 = (int) x1 + x2.

 

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

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

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

Пример 1:

Последовательность вычисляения такая:

  1. 3+4 = 7
  2. 200 * 7 = 1 400
  3. z = 1 400

Пример 2:

Последовательность вычисления такая:

  1. 10 000 + 20 000 = 30 000 (Присвоение вычисляется справа налево, поэтому сначала смотрится y = 10000 + 20000 >> 1 + 3 * 2 , и вычисляется правая часть. В правой части ( 10000 + 20000 >> 1 + 3 * 2 ) вычисление идёт слева направо, и берётся 10000 + 20000 (выбирается среди 10000 + 20000 , 20000 >> 1 , 1 + 3  и 3 * 2 ), которое вычисляется перед сдвигом, так как у сложения приоритет выше.)
  2. 3 * 2 = 6 (В выражении 30000 >> 1 + 3 * 2 вычисление идёт слева направо, и выбирается умножение(среди 30000 >> 1 , 1 + 3  и 3 * 2 ), так как у него приоритет выше, что означает, что сложение будет выполнено раньше.)
  3. 1 + 6 = 7 (В выражении 30000 >> 1 + 6 вычисление идёт слева направо и сложение вычисляется раньше сдвига, так как приоритет у сложения выше.)
  4. 30 000 >> 7 = 234   (0b00…111010100110000 сдвигаем на 7 бит вправо и получаем 0b00…0011101010)
  5. y = 234
  6. x = 234
  7. z = 234

Таблица приоритетов операций

Группа операций Приоритет
Группировка ( ... )
Доступ к члену ... . ...
постфиксные expr++ expr--
унарные ++expr --expr +expr -expr ~ !
мультипликативные * / %
аддитивные + -
сдвиги << >> >>>
сравнения < > <= >= instanceof
равенства == !=
бинарный И &
бинарный исключающее ИЛИ ^
бинарный ИЛИ |
логический И &&
логический ИЛИ ||
тернарный ? :
лямбда ->
присваивания = += -= *= /= %= &= ^= |= <<= >>= >>>=

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 выражения, инструкции и блоки».
Предыдущая статья — «Переменные в Java 8».

Java 8 операции: 9 комментариев

  1. Приоритет операции «*» выше приоритета операции «+». Следовательно в выражении «int z = x = y = 10000 + 20000 >> 1 + 3 * 2;» сначала вычисляется часть «3*2», а уже потом «10000 + 20000». А в статье написано наоборот.

    1. Вычисления выполняются слева направо. Часть выражения 10000 + 20000 — это самая первая полная операция, которая находится при проходе слева направо.

  2. Вероятно, в описание instanceof можно добавить его поведение с null, а то встречал проверки на null перед его использованием.

  3. То есть int x1 += x3 эквивалентно int x1 = (int) x1 + x2.
    Ошибка. Должно быть так:
    То есть int x1 += x3 эквивалентно int x1 = (int) x1 + x3.

  4. Здравствуйте!
    А почему в Java преобразования «short -> char» и «char -> short» являются сужающими?
    Оба примитива занимают по 16 бит. Оба целочисленные. Где тут можно потерять точность?
    Число преобразованное по цепочке «short -> char -> short» останется тем же числом.
    Символ, преобразованный по цепочке «char -> short -> char» останется тем же символом.
    При всем при этом требуется обязательно явно указывать приведение. Не пойму логику, которой руководствовались регламентируя данное правило.

    1. Думаю, что в случае short -> char и char -> short преобразование сужающее, потому что short может принимать диапазон от -32 768 до +32 767 (включительно), а char может хранить только положительные значения от 0 до 65 535 (включительно). При конвертации отрицательных значений из short или слишком больших положительных из char получим совсем не те данные, что хотели бы. Поэтому оно сужающее. Пример:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *