5.03. Операторы и циклы в Java
Операторы и циклы в Java
Виды операторов
Операторы можно разделить на несколько видов:
| Тип | Примеры |
|---|---|
| Арифметические | + — сложение, - — вычитание, * — умножение, / — деление, % — остаток от деления |
| Сравнения | == — равно, != — не равно, > — больше, < — меньше, >= — больше или равно, <= — меньше или равно |
| Логические | ! — отрицание (NOT), & — конъюнкция (AND), | — дизъюнкция (OR), ^ — исключающее ИЛИ (XOR), && — сокращённая конъюнкция, || — сокращённая дизъюнкция, ~ — побитовое дополнение (NOT) |
| Присваивания | =, +=, -=, *=, /= |
| Тернарный | условие ? значение_если_истина : значение_если_ложь |
| Условные операторы | if-else, switch |
| Прочие | ++ — инкремент (увеличение на 1), -- — декремент (уменьшение на 1), // — однострочный комментарий, /* */ — многострочный комментарий |
Сравнение
Для примитивных типов (например, int, double) оператор == сравнивает значения.
Для объектов (например, строк или пользовательских классов) оператор == сравнивает ссылки на объекты, то есть их физическое местоположение в памяти.
Чтобы сравнить содержимое объектов, используется метод .equals().
В Java есть методы, которые выполняют функции, аналогичные операторам:
.equals()— аналог оператора == для сравнения содержимого объектов..compareTo()— используется для сравнения строк или объектов, реализующих интерфейс Comparable;Math.addExact(),Math.subtractExact(),Math.multiplyExact()— аналоги операторов+,-,*, но с проверкой на переполнение. Если результат выходит за пределы допустимых значений, выбрасывается исключение ArithmeticException;Integer.bitCount()— подсчитывает количество единиц в двоичном представлении числа. Аналогично работе с битовыми операциями (&,|,^);Objects.equals()— безопасный способ сравнения двух объектов, который обрабатывает null. Если оба объекта равны null, метод вернёт true;Integer.parseInt(),Double.parseDouble()— аналоги операторов приведения типов (например, (int)).
Метод equals() проверяет логическое равенство содержимого объектов. Для строк сравнение учитывает последовательность символов.
String text1 = new String("привет");
String text2 = new String("привет");
String text3 = "привет";
System.out.println(text1 == text2); // false — разные объекты в памяти
System.out.println(text1.equals(text2)); // true — одинаковое содержимое
System.out.println(text1.equals(text3)); // true — содержимое совпадает
Integer num1 = 100;
Integer num2 = 100;
System.out.println(num1 == num2); // true — кэширование малых целых чисел
System.out.println(num1.equals(num2)); // true
Integer big1 = 200;
Integer big2 = 200;
System.out.println(big1 == big2); // false — разные объекты
System.out.println(big1.equals(big2)); // true — содержимое одинаковое
При создании собственных классов метод equals() следует переопределять вместе с hashCode():
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Person p1 = new Person("Анна", 30);
Person p2 = new Person("Анна", 30);
System.out.println(p1.equals(p2)); // true — содержимое совпадает
Метод compareTo() возвращает целое число: отрицательное, если текущий объект меньше аргумента, ноль при равенстве, положительное при превосходстве.
String a = "яблоко";
String b = "апельсин";
System.out.println(a.compareTo(b)); // положительное число — "яблоко" позже в алфавите
String c = "банан";
System.out.println(b.compareTo(c)); // отрицательное число — "апельсин" раньше "банана"
Integer x = 42;
Integer y = 17;
System.out.println(x.compareTo(y)); // положительное число — 42 больше 17
// Сортировка списка с использованием compareTo
List<String> fruits = Arrays.asList("груша", "яблоко", "апельсин", "банан");
fruits.sort(String::compareTo);
System.out.println(fruits); // [апельсин, банан, груша, яблоко]
Методы Math.addExact(), Math.subtractExact(), Math.multiplyExact() контролируют переполнение целочисленных типов.
int max = Integer.MAX_VALUE; // 2147483647
System.out.println(max + 1); // -2147483648 — тихое переполнение
try {
int result = Math.addExact(max, 1);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("Переполнение при сложении: " + e.getMessage());
}
long big = Long.MAX_VALUE;
try {
long product = Math.multiplyExact(big, 2);
} catch (ArithmeticException e) {
System.out.println("Переполнение при умножении");
}
// Безопасное вычитание
int safeSubtract = Math.subtractExact(100, 50);
System.out.println(safeSubtract); // 50
Метод Integer.bitCount() возвращает количество установленных битов (единиц) в двоичном представлении числа.
int number = 42; // двоичное: 101010
System.out.println(Integer.toBinaryString(number)); // 101010
System.out.println(Integer.bitCount(number)); // 3 — три единицы
int mask = 0b11110000;
System.out.println(Integer.bitCount(mask)); // 4
// Практическое применение: проверка чётности количества единиц
int value = 15; // 1111
boolean evenOnes = (Integer.bitCount(value) % 2 == 0);
System.out.println("Чётное количество единиц: " + evenOnes); // true
// Сравнение с ручным подсчётом через битовые операции
int manualCount = 0;
int temp = number;
while (temp != 0) {
manualCount += temp & 1;
temp >>>= 1;
}
System.out.println("Ручной подсчёт: " + manualCount); // 3
Метод Objects.equals() корректно обрабатывает значения null, избегая NullPointerException.
String s1 = null;
String s2 = null;
String s3 = "текст";
System.out.println(Objects.equals(s1, s2)); // true — оба null
System.out.println(Objects.equals(s1, s3)); // false — null и непустая строка
System.out.println(Objects.equals(s3, s3)); // true — одинаковые строки
// Сравнение без Objects.equals() требует дополнительных проверок
if (s1 != null && s1.equals(s2)) {
// безопасное сравнение без использования Objects.equals()
}
Методы Integer.parseInt() и Double.parseDouble() преобразуют текстовые представления в числовые значения.
String intText = "12345";
int intValue = Integer.parseInt(intText);
System.out.println(intValue); // 12345
String doubleText = "3.14159";
double doubleValue = Double.parseDouble(doubleText);
System.out.println(doubleValue); // 3.14159
// Обработка ошибок преобразования
try {
int invalid = Integer.parseInt("не число");
} catch (NumberFormatException e) {
System.out.println("Невозможно преобразовать строку в число");
}
// Преобразование с указанием системы счисления
int hexValue = Integer.parseInt("FF", 16); // 255 в шестнадцатеричной
int binaryValue = Integer.parseInt("1010", 2); // 10 в двоичной
System.out.println(hexValue); // 255
System.out.println(binaryValue); // 10
Класс Object
Класс Object — базовый для всех классов в Java. Он определяет поведение, доступное каждому объекту.
Основные методы:
equals(Object obj)— сравнение объектов на логическое равенствоhashCode()— возвращает хеш-код объектаtoString()— строковое представление объектаgetClass()— возвращает объект Class, представляющий тип в рантаймеclone()— создает копию объекта (если поддерживает Cloneable)finalize()— вызывается перед сборкой мусора (устарел и не рекомендуется)wait(),notify(),notifyAll()— методы для межпоточной синхронизации.
Метод equals() определяет логическое равенство объектов. Метод hashCode() генерирует целочисленный хеш-код, используемый в хеш-коллекциях.
class Book {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return title.equals(book.title) && author.equals(book.author);
}
@Override
public int hashCode() {
return Objects.hash(title, author);
}
}
Book b1 = new Book("1984", "Оруэлл");
Book b2 = new Book("1984", "Оруэлл");
Book b3 = new Book("Мастер и Маргарита", "Булгаков");
System.out.println(b1.equals(b2)); // true
System.out.println(b1.equals(b3)); // false
System.out.println(b1.hashCode() == b2.hashCode()); // true — равные объекты имеют одинаковый хеш
Метод toString() предоставляет текстовое представление объекта. Переопределение улучшает отладку и логирование.
class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}
}
Point p = new Point(10, 20);
System.out.println(p); // Point[x=10, y=20]
System.out.println(p.toString()); // Point[x=10, y=20]
Метод getClass() возвращает объект класса Class, описывающий тип объекта во время выполнения.
String text = "пример";
Class<?> clazz = text.getClass();
System.out.println(clazz.getName()); // java.lang.String
System.out.println(clazz.getSimpleName()); // String
Integer number = 42;
System.out.println(number.getClass().getName()); // java.lang.Integer
// Проверка типа во время выполнения
Object obj = "текст";
if (obj.getClass() == String.class) {
System.out.println("Это строка");
}
Метод clone() создаёт поверхностную копию объекта. Класс должен реализовывать интерфейс Cloneable.
class Data implements Cloneable {
private int value;
private String[] items;
public Data(int value, String[] items) {
this.value = value;
this.items = items;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Data copy = (Data) super.clone();
copy.items = this.items.clone(); // глубокое копирование массива
return copy;
}
public void setValue(int value) { this.value = value; }
public void setItems(String[] items) { this.items = items; }
public int getValue() { return value; }
public String[] getItems() { return items; }
}
Data original = new Data(100, new String[]{"a", "b", "c"});
try {
Data copy = (Data) original.clone();
copy.setValue(200);
copy.getItems()[0] = "x";
System.out.println(original.getValue()); // 100 — значение не изменилось
System.out.println(original.getItems()[0]); // a — первый элемент массива не изменился благодаря глубокому копированию
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
Методы синхронизации: wait(), notify(), notifyAll() координируют работу потоков при доступе к общим ресурсам.
class SharedBuffer {
private String content;
private boolean empty = true;
public synchronized String read() {
while (empty) {
try {
wait(); // ожидание, пока буфер не станет непустым
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = true;
notifyAll(); // уведомление ожидающих потоков записи
return content;
}
public synchronized void write(String content) {
while (!empty) {
try {
wait(); // ожидание, пока буфер не станет пустым
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
this.content = content;
empty = false;
notifyAll(); // уведомление ожидающих потоков чтения
}
}
Метод finalize() объявлен устаревшим в Java 9 и удалён в последующих версиях. Для освобождения ресурсов следует использовать интерфейс AutoCloseable и конструкцию try-with-resources.
Условные операторы
★ Условия (условные операторы) управляют потоком выполнения программы, позволяют выполнять разный код, в зависимости от выполнения определённого условия.
if-else
Условный оператор if-else (если/иначе) выбирает путь выполнения программы на основе логического выражения.
int temperature = 22;
if (temperature > 30) {
System.out.println("Жарко");
} else if (temperature > 20) {
System.out.println("Тепло"); // выполняется эта ветка
} else if (temperature > 10) {
System.out.println("Прохладно");
} else {
System.out.println("Холодно");
}
Синтаксис:
if (условие) {
// выполняется, если условие истинно
} else {
// выполняется, если условие ложно
}
Пример:
int age = 18;
if (age >= 18) {
System.out.println("Вы совершеннолетний");
} else {
System.out.println("Вы несовершеннолетний");
}
Особенности синтаксиса:
- Фигурные скобки можно опустить для одиночного оператора, но такой стиль не рекомендуется
- Условие всегда приводится к булевому типу
- Ветки
else ifпроверяются последовательно сверху вниз - Выполняется только первая удовлетворяющая условию ветка
Примеры с логическими операторами:
int age = 25;
boolean hasLicense = true;
if (age >= 18 && hasLicense) {
System.out.println("Можно водить автомобиль");
}
String role = "admin";
if (role.equals("admin") || role.equals("moderator")) {
System.out.println("Доступ к административным функциям");
}
boolean isWeekend = true;
if (!isWeekend) {
System.out.println("Рабочий день");
} else {
System.out.println("Выходной"); // выполняется эта ветка
}
Вложенные условия:
int hour = 14;
boolean isHoliday = false;
if (!isHoliday) {
if (hour >= 9 && hour < 18) {
System.out.println("Рабочее время");
} else {
System.out.println("Нерабочее время");
}
} else {
System.out.println("Праздничный день");
}
switch
Оператор switch выбирает одну из нескольких веток выполнения на основе значения выражения.
Синтаксис:
switch (выражение) {
case значение1:
// код
break;
case значение2:
// код
break;
default:
// код по умолчанию
}
Пример:
int day = 3;
switch (day) {
case 1 -> System.out.println("Понедельник");
case 2 -> System.out.println("Вторник");
case 3 -> System.out.println("Среда");
default -> System.out.println("Неизвестный день");
}
Традиционный синтаксис с оператором break:
int month = 3;
switch (month) {
case 1:
System.out.println("Январь");
break;
case 2:
System.out.println("Февраль");
break;
case 3:
System.out.println("Март"); // выполняется эта ветка
break;
default:
System.out.println("Неизвестный месяц");
}
Современный синтаксис с оператором -> (доступен с Java 14):
String day = "вторник";
switch (day) {
case "понедельник" -> System.out.println("Начало недели");
case "вторник", "среда", "четверг" -> System.out.println("Середина недели"); // выполняется эта ветка
case "пятница" -> System.out.println("Конец рабочей недели");
case "суббота", "воскресенье" -> System.out.println("Выходные");
default -> System.out.println("Неизвестный день");
}
Оператор switch как выражение (возвращает значение):
int statusCode = 404;
String statusMessage = switch (statusCode) {
case 200 -> "OK";
case 404 -> "Not Found"; // возвращается это значение
case 500 -> "Internal Server Error";
default -> "Unknown Status";
};
System.out.println(statusMessage); // Not Found
Правила использования:
- Выражение в
switchможет иметь типbyte,short,char,int,enum,Stringили их обёртки - Каждая метка
caseдолжна содержать константное выражение - Оператор
breakпредотвращает переход к следующей ветке (проваливание) - Ветка
defaultвыполняется, если ни одна меткаcaseне совпала - В синтаксисе с
->операторbreakне требуется — выполняется только одна ветка
Циклы
★ Циклы позволяют выполнять блок кода несколько раз.
for
for (для) используется, когда известно количество повторений.
Синтаксис:
for (инициализация; условие; обновление) {
// тело цикла
}
Пример:
for (int i = 0; i < 5; i++) {
System.out.println("i = " + i);
}
Структура содержит три компонента: инициализацию, условие и обновление.
for (int i = 0; i < 5; i++) {
System.out.println("Итерация " + i);
}
// Вывод: Итерация 0, Итерация 1, Итерация 2, Итерация 3, Итерация 4
Инициализация выполняется один раз перед началом цикла. Обычно здесь объявляется и присваивается начальное значение счётчику.
for (int counter = 10; counter > 0; counter--) {
System.out.println(counter);
}
// Обратный отсчёт от 10 до 1
Можно объявить несколько переменных одного типа:
for (int i = 0, j = 10; i < 5; i++, j--) {
System.out.println("i=" + i + ", j=" + j);
}
Условие проверяется перед каждой итерацией. Цикл продолжается, пока условие истинно.
int limit = 3;
for (int i = 0; i < limit; i++) {
System.out.println(i);
}
// Вывод: 0, 1, 2
Бесконечный цикл создаётся опущением условия или указанием константы true:
for (;;) {
// бесконечный цикл
break; // выход по условию внутри тела
}
Обновление выполняется после каждой итерации. Обычно здесь изменяется значение счётчика.
for (int i = 1; i <= 100; i *= 2) {
System.out.println(i);
}
// Вывод: 1, 2, 4, 8, 16, 32, 64
Инкремент i++ увеличивает значение на единицу. Декремент i-- уменьшает на единицу. Можно использовать составные операторы: i += 5, i *= 2.
Каждое выполнение тела цикла называется итерацией. Шаг — изменение счётчика за одну итерацию.
// Шаг равен 3
for (int i = 0; i < 15; i += 3) {
System.out.println(i);
}
// Вывод: 0, 3, 6, 9, 12
// Отрицательный шаг
for (int i = 10; i >= 0; i -= 2) {
System.out.println(i);
}
// Вывод: 10, 8, 6, 4, 2, 0
Цикл for подходит для перебора массивов и коллекций:
int[] numbers = {5, 10, 15, 20};
for (int i = 0; i < numbers.length; i++) {
System.out.println("Элемент " + i + ": " + numbers[i]);
}
String[] words = {"яблоко", "банан", "апельсин"};
for (int i = 0; i < words.length; i++) {
System.out.println((i + 1) + ". " + words[i]);
}
Улучшенный цикл for (for-each) упрощает перебор элементов:
int[] values = {1, 2, 3, 4, 5};
for (int value : values) {
System.out.println(value * value);
}
// Вывод: 1, 4, 9, 16, 25
while
Цикл while повторяет выполнение блока кода, пока логическое условие остаётся истинным. Проверка условия происходит перед каждой итерацией.
Синтаксис:
while (условие) {
// тело цикла
}
Пример:
int i = 0;
while (i < 5) {
System.out.println("i = " + i);
i++;
}
int count = 3;
while (count > 0) {
System.out.println("Осталось: " + count);
count--;
}
// Вывод: Осталось: 3, Осталось: 2, Осталось: 1
Особенности:
- Если условие изначально ложно, тело цикла не выполнится ни разу
- Счётчик или переменная условия должны изменяться внутри тела цикла
- Бесконечный цикл возникает при отсутствии изменения условия
Примеры использования:
// Чтение данных до достижения конца
boolean hasNext = true;
int dataIndex = 0;
while (hasNext) {
// обработка данных[dataIndex]
dataIndex++;
if (dataIndex >= 10) {
hasNext = false; // завершение цикла
}
}
// Поиск элемента в массиве
int[] items = {4, 8, 15, 16, 23, 42};
int target = 16;
int position = 0;
boolean found = false;
while (position < items.length && !found) {
if (items[position] == target) {
found = true;
} else {
position++;
}
}
if (found) {
System.out.println("Элемент найден на позиции " + position);
}
Цикл while удобен, когда количество итераций заранее неизвестно и определяется в процессе выполнения.
do-while
do-while (делать, пока), выполняется хотя бы один раз, даже если условие ложно. Проверка условия происходит после каждой итерации.
Синтаксис:
do {
// тело цикла
} while (условие);
Пример:
int j = 0;
do {
System.out.println("j = " + j);
j++;
} while (j < 5);
int attempts = 0;
do {
System.out.println("Попытка " + (attempts + 1));
attempts++;
} while (attempts < 3);
// Вывод: Попытка 1, Попытка 2, Попытка 3
Сравнение с while:
int value = 10;
// Цикл while не выполнится
while (value < 5) {
System.out.println("while: " + value);
value++;
}
// Цикл do-while выполнится один раз
do {
System.out.println("do-while: " + value);
value++;
} while (value < 5);
// Вывод: do-while: 10
Практическое применение — интерактивные меню:
Scanner scanner = new Scanner(System.in);
int choice;
do {
System.out.println("Меню:");
System.out.println("1. Показать баланс");
System.out.println("2. Пополнить счёт");
System.out.println("3. Выйти");
System.out.print("Выберите действие: ");
choice = scanner.nextInt();
switch (choice) {
case 1 -> System.out.println("Баланс: 1000 руб.");
case 2 -> System.out.println("Счёт пополнен");
case 3 -> System.out.println("Выход");
default -> System.out.println("Неверный выбор");
}
} while (choice != 3);
scanner.close();
Цикл do-while применяется, когда требуется как минимум одна итерация независимо от начального состояния условия.