Логические операторы в Java: &&, ||, !, ^ и короткое замыкание

Логические операторы в Java: &&, ||, !, ^ и короткое замыкание Полезное

Логические операторы в Java — это операторы, которые принимают значения типа boolean и возвращают boolean: ! (НЕ), && и & (И), || и | (ИЛИ), ^ (исключающее ИЛИ). Из них собирают условия для if, while и тернарного оператора. Главная практическая разница — между &&/|| (вычисляют правый операнд только при необходимости) и &/| (вычисляют оба всегда).

Ниже — таблица истинности, короткое замыкание и защита от NullPointerException, приоритет, тернарный оператор и типичные ошибки. Все примеры проверены на OpenJDK 25.0.4 (Java 25 LTS) в сентябре 2026.

Мини-словарь: что с чем не путать

Одни и те же символы в Java означают разное в зависимости от типа операндов, поэтому сначала термины:

Термин Операторы Операнды Что делает
Условные логические (с коротким замыканием) &&, || только boolean правый операнд вычисляется, только если от него зависит результат
Логические без сокращения &, |, ^ boolean всегда вычисляют оба операнда
Побитовые &, |, ^, ~ целые (int, long и др.) работают с каждым битом числа
Логическое отрицание ! boolean меняет true на false и наоборот
Операторы сравнения ==, !=, <, >, <=, >= числа (==, != — еще и ссылки) не логические, но дают boolean, который потом комбинируют логическими

Выражение 5 & 3 — побитовое И двух чисел (результат 1), а true & false — логическое И (результат false). Побитовые операции — отдельная тема, здесь только логические.

Таблица истинности: полный пример

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

public class TruthTable {
    public static void main(String[] args) {
        boolean[] values = {false, true};
        System.out.println("a     b     | a&&b  a||b  a^b   !a");
        for (boolean a : values) {
            for (boolean b : values) {
                System.out.printf("%-5b %-5b | %-5b %-5b %-5b %-5b%n",
                        a, b, a && b, a || b, a ^ b, !a);
            }
        }
    }
}

Запуск без отдельной компиляции (Java 11+): java TruthTable.java. Вывод:

a     b     | a&&b  a||b  a^b   !a
false false | false false false true 
false true  | false true  true  true 
true  false | false true  true  false
true  true  | true  true  false false

Как читать таблицу:

  • И (&&, &) — true, только когда оба операнда true.
  • ИЛИ (||, |) — false, только когда оба операнда false.
  • Исключающее ИЛИ (^) — true, когда операнды разные. Для boolean запись a ^ b дает то же, что a != b.
  • НЕ (!) — унарный оператор, у него один операнд.

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

Короткое замыкание: && и || против & и |

Короткое замыкание (short-circuit evaluation) — правило, по которому && и || не вычисляют правый операнд, если результат уже известен по левому:

  • false && X — всегда false, X не вычисляется;
  • true || X — всегда true, X не вычисляется.

Проверим счетчиком вызовов:

public class ShortCircuit {
    static int calls = 0;

    static boolean check(String name, boolean result) {
        calls++;
        System.out.println("  вызван " + name);
        return result;
    }

    public static void main(String[] args) {
        System.out.println("false && ...:");
        boolean r1 = check("левый", false) && check("правый", true);
        System.out.println("  результат " + r1 + ", вызовов " + calls);

        calls = 0;
        System.out.println("false & ...:");
        boolean r2 = check("левый", false) & check("правый", true);
        System.out.println("  результат " + r2 + ", вызовов " + calls);

        calls = 0;
        System.out.println("true || ...:");
        boolean r3 = check("левый", true) || check("правый", false);
        System.out.println("  результат " + r3 + ", вызовов " + calls);
    }
}
false && ...:
  вызван левый
  результат false, вызовов 1
false & ...:
  вызван левый
  вызван правый
  результат false, вызовов 2
true || ...:
  вызван левый
  результат true, вызовов 1

Результат у && и & одинаковый, но с & правый метод все равно выполнился. Если в правом операнде есть побочный эффект (запись в базу, i++, сетевой вызов), от выбора оператора зависит поведение программы, а не только скорость. У ^ сокращенной формы нет: чтобы узнать, различаются ли операнды, нужны оба.

Защита от NullPointerException

Самое частое применение короткого замыкания — проверка на null перед обращением к объекту. Сначала неверный вариант с &:

public class NullGuard {
    public static void main(String[] args) {
        String name = null;

        if (name != null && !name.isBlank()) {
            System.out.println("Привет, " + name);
        } else {
            System.out.println("Имя не задано");
        }

        if (name != null & !name.isBlank()) {
            System.out.println("Привет, " + name);
        }
    }
}

Результат: первый if отработал, второй упал.

Имя не задано
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.isBlank()" because "<local1>" is null
    at NullGuard.main(NullGuard.java:11)

С && вызов name.isBlank() не выполняется, потому что name != null уже дал false. С & Java вычисляет оба операнда и вызывает метод у null. Исправление — &&, и проверка на null обязательно слева: !name.isBlank() && name != null упадет так же. Вместо имени переменной в тексте исключения стоит "<local1>": так бывает при запуске java NullGuard.java и при javac без отладочной информации. Если скомпилировать с javac -g, сообщение назовет переменную: because "name" is null.

Когда & и | для boolean все же нужны: если оба операнда обязаны выполниться (например, провалидировать все поля формы и собрать все ошибки, а не остановиться на первой). Такой код лучше дополнить комментарием, иначе коллега примет & за опечатку.

Приоритет логических операторов

Приоритет определяет, как выражение без скобок группируется (сверху — связывает сильнее). Сами операнды Java вычисляет слева направо, а приоритет лишь решает, к какому оператору какой операнд относится.

Приоритет Операторы Пример
1 ! (унарный) !a
2 арифметика * / %, затем + - x + 1
3 сравнение < > <= >= age >= 18
4 равенство == != a == b
5 & a & b
6 ^ a ^ b
7 | a | b
8 && a && b
9 || a || b
10 тернарный ?: c ? x : y
11 присваивание = &= |= ^= ok &= f

Таблица сокращенная: в нее не вошли сдвиги << >>, instanceof, инкременты и лямбды. Главное из нее: && связывает сильнее, чем ||, так же как умножение сильнее сложения.

public class Precedence {
    public static void main(String[] args) {
        boolean a = true, b = false, c = false;

        System.out.println(a || b && c);    // a || (b && c)
        System.out.println((a || b) && c);
        System.out.println(!a == b);        // (!a) == b
        System.out.println(a | b & c);      // a | (b & c)
        System.out.println(a ^ b | c);      // (a ^ b) | c

        int age = 25;
        boolean hasTicket = false, isStaff = true;
        System.out.println(age >= 18 && hasTicket || isStaff);   // (age >= 18 && hasTicket) || isStaff
        System.out.println(age >= 18 && (hasTicket || isStaff));
    }
}

Вывод: true, false, true, true, true, true, true. Первые две строки показывают, что скобки меняют результат. Последние две совпали на этих данных случайно: при age = 15 первое выражение даст true (сотрудник пройдет в любом возрасте), второе — false. Если условие смешивает && и ||, скобки лучше ставить явно, даже когда приоритет и так дает нужный порядок.

Как упростить отрицание сложного условия

Законы де Моргана позволяют внести ! внутрь скобок: !(a && b) равно !a || !b, а !(a || b) равно !a && !b. Пример: «магазин закрыт» — это !(hour >= 9 && hour < 21), или без отрицания hour < 9 || hour >= 21. Второй вариант читается легче, и его проще покрыть тестом на границы (8, 9, 20, 21).

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

Тернарный оператор условие ? значение1 : значение2 возвращает первое значение, если условие true, иначе второе. Он тоже вычисляет только одну ветку.

public class Ternary {
    public static void main(String[] args) {
        int books = 14;
        String status = books >= 12 ? "план выполнен" : "еще читать";
        System.out.println(status);

        books = 11;
        String level = books >= 12 ? "план выполнен"
                     : books > 10  ? "почти"
                     : "еще читать";
        System.out.println(level);

        boolean ok = true;
        ok &= books > 20;
        System.out.println(ok);
        ok |= books > 10;
        System.out.println(ok);
    }
}

Вывод: план выполнен, почти, false, true. Тернарный оператор уместен, когда нужно выбрать значение для переменной или return; если в ветках действия, а не значения, или вложенность больше двух уровней — понятнее if/else. Составные &=, |= и ^= для boolean не сокращаются: правая часть вычисляется всегда. Операторов &&= и ||= в Java нет.

Типичные ошибки

1. == вместо equals для строк. Оператор == сравнивает ссылки, а не содержимое.

import java.util.Objects;

public class Mistakes {
    public static void main(String[] args) {
        String typed = new String("java");
        System.out.println(typed == "java");
        System.out.println(typed.equals("java"));
        System.out.println("java".equals(null));
        System.out.println(Objects.equals(null, null));

        boolean isAdmin = false;
        if (isAdmin = true) {
            System.out.println("Доступ открыт");
        }
        System.out.println(isAdmin);

        System.out.println(5 & 3);
        System.out.println(true & false);
    }
}

Вывод: false, true, false, true, Доступ открыт, true, 1, false. Строки с одинаковым текстом дали false через ==. Исправление — equals, а если значение может быть null, то "java".equals(x) (литерал слева) или Objects.equals(a, b).

2. = вместо == в условии. Для boolean это компилируется: isAdmin = true присваивает значение и возвращает его, поэтому ветка выполнилась, а флаг испорчен. Для int такая опечатка не скомпилируется. Исправление — писать просто if (isAdmin), без сравнения с true.

3. & вместо &&. Для boolean результат тот же, но нет короткого замыкания: отсюда NullPointerException из раздела выше и лишние вызовы методов. Для целых чисел & — уже побитовая операция (5 & 3 дает 1).

4. Логический оператор с числом. В Java нет неявного перевода int в boolean, как в C. Код if (count && true) при int count = 3 не компилируется:

BadOperand.java:4: error: bad operand types for binary operator '&&'
        if (count && true) {
                  ^
  first type:  int
  second type: boolean
1 error

Исправление — явное сравнение: if (count > 0 && flag).

Выводы

  • Логические операторы Java работают только с boolean: !, &&, ||, &, |, ^; для целых чисел &, |, ^ становятся побитовыми.
  • && и || вычисляют правый операнд только при необходимости; на этом строится проверка x != null && x.method().
  • & и | на boolean вычисляют оба операнда — используйте их, только когда нужны побочные эффекты обеих частей.
  • && приоритетнее ||; в смешанных условиях ставьте скобки явно.
  • Строки сравнивают через equals или Objects.equals, а не ==.

Где применяется / связь с практикой

Логические выражения — основа любой бизнес-логики: проверка прав доступа, валидация форм, фильтры в Stream API (filter(u -> u.isActive() && u.getAge() >= 18)), условия циклов и защита от null. Ошибки в них — частая причина багов, которые не ловит компилятор: код работает, но пропускает не тех пользователей.

Освойте тему на практике

Если хочется системно пройти синтаксис, ООП и коллекции с проверкой домашних заданий, подойдет курс Java разработчик. Базовый уровень. Разобрать отдельные темы с преподавателями можно на бесплатных открытых уроках.

FAQ

Быстрее ли &&, чем &?
Иногда да, потому что правый операнд может не вычисляться, но выбирать оператор стоит по смыслу, а не ради скорости. Разница заметна, только если правая часть дорогая (вызов метода, запрос).

Есть ли в Java логический XOR с коротким замыканием?
Нет: для XOR нужны оба операнда. Для boolean вместо a ^ b можно писать a != b — результат одинаковый.

Можно ли применить ! к Boolean, который равен null?
Нет. При распаковке Boolean в boolean значение null дает NullPointerException; безопасный вариант — Boolean.TRUE.equals(flag).

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)