Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Операторы и выражения

Арифметические, логические, битовые операторы

Ресурсы

Основные понятия

Выражение (expression) - это конструкция, которая вычисляется и возвращает результат. Результатом может быть:

  • Переменная (lvalue)
  • Значение (value)
  • Ничего (void - для методов без возвращаемого значения)

При вычислении выражение может завершиться:

  • Нормально (normal completion) - если все шаги выполнены без исключений
  • Резко (abrupt completion) - если возникло исключение

Типы выражений

Выражения классифицируются по синтаксическим формам:

  • Имена выражений
  • Первичные выражения (литералы, this, создание объектов)
  • Унарные операторы
  • Бинарные операторы
  • Тернарный оператор ? :
  • Лямбда-выражения
  • Switch-выражения

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

Java гарантирует строгий порядок вычисления выражений:

Основные правила

  1. Левый операнд вычисляется первым - в бинарных операторах левый операнд всегда вычисляется до правого
  2. Операнды вычисляются до операции - все операнды вычисляются полностью перед выполнением операции
  3. Соблюдение скобок и приоритета - порядок определяется скобками и приоритетом операторов
  4. Аргументы слева направо - аргументы методов вычисляются слева направо
int i = 2;
int j = (i=3) * i;  // j = 9, не 6

Литералы

Литерал - фиксированное неизменяемое значение.

Типы литералов:

  • Целочисленные: 42, 0xFF, 0b1010, 100L
  • Вещественные: 3.14, 2.5f, 1.0e-10
  • Логические: true, false
  • Символьные: 'a', '\n', '\u0041'
  • Строковые: "Hello", текстовые блоки """..."""
  • Null: null

Первичные выражения

this и super

  • this - ссылка на текущий объект
  • super - ссылка на родительский класс
  • Квалифицированный this: ClassName.this

Литералы классов

Class<String> c1 = String.class;
Class<Integer> c2 = int.class;
Class<Void> c3 = void.class;

Создание объектов

new ClassName()
new ClassName(args)
new ClassName() { /* анонимный класс */ }

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

Инкремент и декремент

  • ++x - префиксный инкремент (сначала увеличение, потом использование)
  • x++ - постфиксный инкремент (сначала использование, потом увеличение)
  • --x - префиксный декремент
  • x-- - постфиксный декремент

Знаковые операторы

  • +x - унарный плюс
  • -x - унарный минус (смена знака)

Логические и битовые

  • !x - логическое отрицание (НЕ)
  • ~x - побитовое отрицание (инверсия битов)

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

Мультипликативные

  • * - умножение
  • / - деление (целочисленное для int, обычное для float/double)
  • % - остаток от деления
int a = 7 / 2;      // 3
double b = 7.0 / 2; // 3.5
int c = 7 % 2;      // 1

Важно: Деление на ноль для целых чисел выбрасывает ArithmeticException, для вещественных - возвращает Infinity или NaN.

Аддитивные

  • + - сложение или конкатенация строк
  • - - вычитание
int sum = 5 + 3;              // 8
String s = "Hello" + "World"; // "HelloWorld"
String s2 = "Value: " + 42;   // "Value: 42"

Операторы сдвига

Работают только с целочисленными типами:

  • << - сдвиг влево (умножение на 2^n)
  • >> - арифметический сдвиг вправо (деление на 2^n, сохраняет знак)
  • >>> - логический сдвиг вправо (заполняет нулями слева)
int x = 8;
x << 2;  // 32 (8 * 4)
x >> 2;  // 2  (8 / 4)

int y = -8;
y >> 2;  // -2  (знак сохраняется)
y >>> 2; // 1073741822 (беззнаковый сдвиг)

Операторы сравнения

Числовые операторы сравнения

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

Результат: boolean

instanceof

Проверяет принадлежность объекта к типу:

if (obj instanceof String) {
    String s = (String) obj;
}

// С pattern matching (Java 16+)
if (obj instanceof String s) {
    // s доступна здесь
}

Операторы равенства

Для примитивов

  • == - равенство значений
  • != - неравенство значений

Для ссылок

  • == - проверка идентичности (ссылаются ли на один объект)
  • != - проверка неидентичности
String s1 = new String("Hello");
String s2 = new String("Hello");
s1 == s2;        // false (разные объекты)
s1.equals(s2);   // true (одинаковое содержимое)

Битовые и логические операторы

Целочисленные битовые

  • & - побитовое И (AND)
  • | - побитовое ИЛИ (OR)
  • ^ - побитовое исключающее ИЛИ (XOR)
int a = 0b1100;
int b = 0b1010;
a & b;  // 0b1000 (8)
a | b;  // 0b1110 (14)
a ^ b;  // 0b0110 (6)

Логические для boolean

  • & - логическое И (вычисляет оба операнда)
  • | - логическое ИЛИ (вычисляет оба операнда)
  • ^ - логическое XOR

Условные логические (короткое замыкание)

  • && - условное И (если левый false, правый не вычисляется)
  • || - условное ИЛИ (если левый true, правый не вычисляется)
if (obj != null && obj.isValid()) { // безопасно
    // obj.isValid() не вызовется если obj == null
}

Тернарный оператор

Синтаксис: условие ? значение_если_true : значение_если_false

int max = (a > b) ? a : b;
String status = (age >= 18) ? "Взрослый" : "Ребёнок";

Типы результата:

  • Если оба операнда числовые - выбирается общий числовой тип
  • Если оба boolean - результат boolean
  • Если ссылочные типы - выбирается общий родительский тип

Операторы присваивания

Простое присваивание

  • = - присваивание значения
int x = 5;
String s = "Hello";

Составные операторы присваивания

Комбинируют операцию с присваиванием:

  • += - сложение с присваиванием
  • -= - вычитание с присваиванием
  • *= - умножение с присваиванием
  • /= - деление с присваиванием
  • %= - остаток с присваиванием
  • &= - побитовое И с присваиванием
  • |= - побитовое ИЛИ с присваиванием
  • ^= - побитовое XOR с присваиванием
  • <<= - сдвиг влево с присваиванием
  • >>= - сдвиг вправо с присваиванием
  • >>>= - беззнаковый сдвиг с присваиванием
int x = 10;
x += 5;  // эквивалентно x = x + 5; (x = 15)
x *= 2;  // эквивалентно x = x * 2; (x = 30)

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

byte b = 5;
b += 10;  // ОК, эквивалентно b = (byte)(b + 10)
b = b + 10; // ОШИБКА компиляции! (требуется явное приведение)

Приоритет операторов

От высшего к низшему (сверху вниз):

  1. Постфиксные: expr++, expr--
  2. Унарные: ++expr, --expr, +, -, ~, !
  3. Приведение типов: (type)
  4. Мультипликативные: *, /, %
  5. Аддитивные: +, -
  6. Сдвиг: <<, >>, >>>
  7. Сравнение: <, >, <=, >=, instanceof
  8. Равенство: ==, !=
  9. Побитовое И: &
  10. Побитовое XOR: ^
  11. Побитовое ИЛИ: |
  12. Логическое И: &&
  13. Логическое ИЛИ: ||
  14. Тернарный: ? :
  15. Присваивание: =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=
  16. Лямбда: ->
int result = 2 + 3 * 4;        // 14, не 20 (* выше +)
int result2 = (2 + 3) * 4;     // 20 (скобки меняют порядок)
boolean b = x > 0 && y < 10;   // сначала сравнения, потом &&

Особенности вещественных чисел

Java полностью поддерживает IEEE 754:

  • Специальные значения: Infinity, -Infinity, NaN
  • Операции с NaN всегда возвращают NaN
  • Сравнения с NaN всегда false (кроме !=, которое true)
double inf = 1.0 / 0.0;      // Infinity
double nan = 0.0 / 0.0;      // NaN
nan == nan;                  // false
Double.isNaN(nan);           // true

Политики округления:

  • Round to nearest (округление к ближайшему) - для большинства операций
  • Round toward zero (округление к нулю) - при приведении к целому типу

Приведение типов (Cast)

Синтаксис: (type) expression

double d = 3.14;
int i = (int) d;              // 3 (отбрасывается дробная часть)

Object obj = "Hello";
String s = (String) obj;      // ОК

// Небезопасное приведение вызывает ClassCastException
Integer num = (Integer) obj;  // Runtime error!

Создание массивов

С указанием размера

int[] arr1 = new int[10];
int[][] arr2 = new int[5][10];
int[][] arr3 = new int[5][];  // "рваный" массив

С инициализацией

int[] arr1 = {1, 2, 3, 4, 5};
int[][] arr2 = {{1, 2}, {3, 4}, {5, 6}};
String[] names = new String[] {"Alice", "Bob"};

Доступ к массиву

Синтаксис: array[index]

int[] arr = {10, 20, 30};
int x = arr[0];      // 10
arr[1] = 25;         // изменение элемента

// Проверки времени выполнения
arr[5];              // ArrayIndexOutOfBoundsException
int[] nullArr = null;
nullArr[0];          // NullPointerException

Вызов методов

object.method()
object.method(arg1, arg2)
ClassName.staticMethod()

Перегрузка разрешается во время компиляции на основе типов аргументов.

Ссылки на методы (Method References)

Синтаксис для функционального программирования (Java 8+):

// Ссылка на статический метод
Function<String, Integer> parser = Integer::parseInt;

// Ссылка на метод экземпляра
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(System.out::println);

// Ссылка на конструктор
Supplier<List<String>> listSupplier = ArrayList::new;

Лямбда-выражения

Синтаксис: (parameters) -> expression или (parameters) -> { statements }

// Без параметров
Runnable r = () -> System.out.println("Hello");

// С одним параметром (скобки необязательны)
Consumer<String> printer = s -> System.out.println(s);
Consumer<String> printer2 = (String s) -> System.out.println(s);

// С несколькими параметрами
Comparator<Integer> comp = (a, b) -> a.compareTo(b);

// С телом блока
BiFunction<Integer, Integer, Integer> sum = (a, b) -> {
    int result = a + b;
    return result;
};

Switch-выражения (Java 14+)

// Традиционный switch-statement
switch (day) {
    case MONDAY:
    case FRIDAY:
        System.out.println("Work");
        break;
    case SATURDAY:
    case SUNDAY:
        System.out.println("Rest");
        break;
}

// Switch-выражение (возвращает значение)
String activity = switch (day) {
    case MONDAY, FRIDAY -> "Work";
    case SATURDAY, SUNDAY -> "Rest";
    default -> "Other";
};

// С блоком и yield
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> {
        System.out.println("Checking...");
        yield 9;
    }
};

Константные выражения

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

Константными могут быть:

  • Литералы примитивных типов и String
  • Приведение типов к примитивам или String
  • Унарные операторы: +, -, ~, !
  • Мультипликативные: *, /, %
  • Аддитивные: +, -
  • Сдвиг: <<, >>, >>>
  • Сравнение: <, <=, >, >=
  • Равенство: ==, !=
  • Побитовые и логические: &, ^, |
  • Условные: &&, ||
  • Тернарный: ? :
  • Скобки: ( )
  • Простые имена final переменных, инициализированных константными выражениями
final int MAX = 100;
final int MIN = 0;
final int RANGE = MAX - MIN;  // константное выражение

// Используется в switch
switch (value) {
    case MIN:     // OK
    case MAX:     // OK
    case RANGE:   // OK
}

Исключения времени выполнения

Операторы могут вызывать исключения:

  • NullPointerException - операция с null
  • ArithmeticException - деление на ноль (целые числа)
  • ArrayIndexOutOfBoundsException - выход за границы массива
  • NegativeArraySizeException - отрицательный размер массива
  • ClassCastException - недопустимое приведение типов
  • ArrayStoreException - несовместимый тип при записи в массив
  • OutOfMemoryError - нехватка памяти

Автоупаковка и автораспаковка

Автоматическое преобразование между примитивами и обёртками:

// Автоупаковка (boxing)
Integer obj = 42;  // эквивалентно Integer.valueOf(42)

// Автораспаковка (unboxing)
int x = obj;       // эквивалентно obj.intValue()

// В выражениях
Integer a = 10;
Integer b = 20;
Integer sum = a + b;  // автораспаковка, затем автоупаковка

// Осторожно с null!
Integer nullObj = null;
int y = nullObj;      // NullPointerException!

Важные особенности и best practices

  1. Используйте скобки для ясности - не полагайтесь только на приоритет операторов
  2. Избегайте сложных выражений - разбивайте на несколько строк для читаемости
  3. Осторожно с автоупаковкой - может вызвать NullPointerException
  4. Используйте equals() для объектов - не == (если не проверяете идентичность)
  5. Проверяйте null перед && - используйте короткое замыкание
  6. Избегайте деления целых на ноль - проверяйте делитель
  7. Осторожно с NaN - проверяйте через Double.isNaN()
  8. Используйте составные операторы - они короче и автоматически приводят тип
// Хорошо
if (obj != null && obj.isValid()) { }

// Плохо
if (obj.isValid() && obj != null) { } // может быть NPE

// Хорошо
String.valueOf(obj).equals("value")

// Плохо  
obj.toString().equals("value")  // может быть NPE