Java с нуля: просто, понятно и с примерами

Курс для новичков, который шаг за шагом объясняет основы Java простым языком. Вы изучите синтаксис, ООП, коллекции, обработку ошибок и работу с файлами, закрепляя всё на практических примерах и мини‑проекте.

1. Старт: установка JDK, IDE и первая программа Hello World

Старт: установка JDK, IDE и первая программа Hello World

В этой статье вы подготовите компьютер к изучению Java и запустите свою первую программу. Мы сделаем всё максимально простыми шагами:

  • Выберем и установим JDK
  • Проверим, что Java работает в терминале
  • Установим IDE (среду разработки)
  • Напишем и запустим Hello World
  • Что такое JDK, JVM и IDE (простыми словами)

  • JDK (Java Development Kit) — набор инструментов для разработки на Java: компилятор javac, команда запуска java и другие утилиты.
  • JVM (Java Virtual Machine) — “виртуальная машина”, которая запускает Java-программы.
  • IDE — программа, в которой удобно писать код: подсказки, кнопка запуска, поиск ошибок, проекты.
  • Важно: для обучения вам нужен именно JDK, а не “что-то одно для запуска”.

    !Как Java-код превращается в запускаемую программу

    Какую версию Java выбрать

    Для старта лучше брать LTS-версию (долго поддерживается, меньше сюрпризов):

  • Java 21 (LTS) — актуальный выбор
  • Java 17 (LTS) — тоже часто встречается
  • Если не знаете, что выбрать — ставьте 21.

    Установка JDK

    Ниже — простой и бесплатный вариант: Eclipse Temurin (сборка OpenJDK).

    Ссылки:

  • Eclipse Temurin (скачать JDK)
  • Инструкция по установке Temurin
  • Также существует JDK от Oracle:

  • Oracle JDK (скачать)
  • Windows

  • Перейдите на Eclipse Temurin (скачать JDK).
  • Выберите версию 21, вашу ОС Windows и архитектуру x64.
  • Скачайте установщик (.msi), запустите.
  • В установщике полезно включить опцию добавления в PATH (если она есть).
  • macOS

  • Перейдите на Eclipse Temurin (скачать JDK).
  • Выберите macOS и подходящую архитектуру:
  • - aarch64 для Apple Silicon (M1/M2/M3) - x64 для Intel
  • Скачайте .pkg и установите.
  • Linux

    Самый простой путь зависит от дистрибутива. Официальная страница даёт подходящие варианты:

  • Откройте Инструкция по установке Temurin.
  • Выберите ваш дистрибутив (Ubuntu/Debian/Fedora и т.д.).
  • Установите Temurin 21 по инструкции.
  • Проверка установки (самый важный шаг)

    Откройте терминал:

  • Windows: PowerShell или cmd
  • macOS/Linux: Terminal
  • Введите команды:

    Вы должны увидеть версию (например, 21.x). Если команда не найдена, чаще всего проблема в PATH.

    Что такое PATH и зачем он нужен

    PATH — это список папок, где система ищет программы, когда вы набираете команду вроде java.

    Если Java установлена, но java -version не работает, значит:

  • либо JDK не добавился в PATH автоматически
  • либо терминал был открыт до установки (закройте и откройте заново)
  • !Почему команда java может не находиться

    Установка IDE

    Для новичка лучший вариант — IntelliJ IDEA Community Edition (бесплатная).

  • IntelliJ IDEA (скачать)
  • Альтернативы:

  • Visual Studio Code (скачать)
  • Extension Pack for Java (для VS Code)
  • Eclipse IDE (скачать)
  • Дальше в примерах будет IntelliJ IDEA, но код одинаковый в любой среде.

    Первая программа Hello World

    Вариант А: запуск через терминал (полезно, чтобы понять основу)

  • Создайте папку, например java-start.
  • Внутри создайте файл HelloWorld.java.
  • Вставьте код:
  • Разберём код:

  • public class HelloWorld — объявляем класс с именем HelloWorld.
  • mainточка входа: отсюда начинается выполнение программы.
  • System.out.println(...) — печать строки в консоль.
  • Важно:

  • Имя файла HelloWorld.java должно совпадать с именем класса HelloWorld.
  • В конце строки с печатью стоит ; — в Java он обязателен.
  • Теперь скомпилируйте и запустите.

    macOS/Linux (обычно так):

    Windows PowerShell (обычно так же):

    Если всё правильно, увидите:

    Вариант Б: запуск в IntelliJ IDEA

  • Установите IntelliJ IDEA из IntelliJ IDEA (скачать).
  • Откройте IntelliJ IDEA и выберите создание нового проекта:
  • 1. New Project 2. Language: Java 3. JDK: выберите установленный JDK (например, 21)
  • Создайте класс HelloWorld.
  • Вставьте тот же код.
  • Нажмите кнопку запуска рядом с main (обычно зелёный треугольник).
  • Типичные ошибки новичка и как их исправить

  • java или javac не найден
  • 1. Перезапустите терминал. 2. Проверьте установку JDK. 3. Проверьте PATH (или переустановите JDK с добавлением в PATH).
  • Ошибка про класс, который не найден (Could not find or load main class)
  • 1. Вы запускаете команду java не из той папки. 2. Вы написали java HelloWorld.java вместо java HelloWorld.
  • Несовпадение имени файла и класса
  • 1. Файл должен называться HelloWorld.java, а класс — HelloWorld.
  • Опечатки в коде
  • 1. Проверьте кавычки: нужны обычные ". 2. Проверьте ; в конце строки.

    Итог

    Теперь у вас:

  • установлен JDK (Java 21 или 17)
  • работает java -version и javac -version
  • установлена IDE
  • запущена первая программа Hello World
  • В следующей статье обычно переходят к базовому синтаксису: переменные, типы данных и простые операции — это будет ваш первый реальный “инструментарий” для написания программ.

    2. Основы синтаксиса: переменные, типы данных, операторы и ввод-вывод

    Основы синтаксиса: переменные, типы данных, операторы и ввод-вывод

    В прошлой статье вы установили JDK и IDE и запустили Hello World. Теперь пора перейти к тому, из чего состоит почти любая программа: данные (переменные и типы) и действия над ними (операторы), а также общение программы с пользователем (ввод-вывод).

    Как читать Java-код

    Java-программа состоит из инструкций (команд). Чаще всего одна инструкция заканчивается символом ;.

    Пример:

  • int age = 20; — создали переменную и положили в неё число.
  • System.out.println(age); — вывели значение в консоль.
  • Переменные

    Переменная — это именованное место для хранения значения.

    Чтобы создать переменную, в Java нужно указать:

  • Тип данных
  • Имя переменной
  • (Опционально) начальное значение
  • Примеры:

    Правила (упрощённо):

  • Имя переменной обычно пишут в стиле camelCase: userName, totalSum.
  • Нельзя начинать имя с цифры: 2day — нельзя, day2 — можно.
  • Нельзя использовать ключевые слова Java (например, class, public, int).
  • !Переменная как именованное хранилище значения

    Типы данных

    В Java есть две большие группы типов:

  • Примитивные типы — простые значения (числа, символ, true/false).
  • Ссылочные типы — более сложные объекты (например, String).
  • Примитивные типы (основные)

    | Тип | Для чего | Пример | |---|---|---| | int | целые числа (чаще всего) | int count = 10; | | long | большие целые числа | long distance = 10_000_000_000L; | | double | числа с дробной частью (по умолчанию) | double x = 3.14; | | float | дробные числа, но обычно реже | float f = 1.5f; | | boolean | логическое значение | boolean ok = true; | | char | один символ | char ch = 'A'; | | byte | маленькие целые числа | byte b = 120; | | short | целые числа поменьше int | short s = 32000; |

    Полезная деталь:

  • В числе можно использовать _ для читаемости: 1_000_000.
  • Суффиксы: L для long, f для float.
  • Официальная справка (можно открыть, когда захотите деталей): Primitive Data Types (Oracle Java Tutorial)

    String — строки

    String — это строка текста. Это не примитив, а ссылочный тип.

    Примеры:

    Полезно помнить:

  • Строки в Java пишутся в двойных кавычках: "Привет".
  • Символ (char) пишется в одинарных кавычках: 'A'.
  • Автоматическое преобразование и приведение типов

    Иногда Java может преобразовать тип сама, если это безопасно:

    Но если вы пытаетесь положить дробное число в целое, нужно явное приведение:

    Важно: (int) здесь не округляет, а именно отбрасывает дробную часть.

    Операторы

    Присваивание

    Оператор = кладёт значение в переменную:

    Арифметика

    Ключевой момент: если делите int на int, результат тоже int.

    Чтобы получить дробный результат, пусть хотя бы одно число будет double:

    Сравнение

    Результат сравнения — всегда boolean.

    Важно: == — это сравнение, а = — присваивание.

    Логические операторы

    Они работают с boolean:

  • && — И (оба условия должны быть true)
  • || — ИЛИ (достаточно одного true)
  • ! — НЕ (инверсия)
  • Пример:

    Инкремент и сокращённые присваивания

    На старте вам достаточно помнить x++ и x += ....

    Ввод-вывод

    Вывод в консоль

    Самый частый способ — System.out.println(...):

    Если нужно вывести без перехода на новую строку:

    Часто используют склейку строк оператором +:

    Форматированный вывод (когда нужен порядок)

    System.out.printf(...) позволяет аккуратно форматировать текст.

    Пример:

    Что здесь значит:

  • %s — место для строки
  • %.2f — число с дробной частью, 2 знака после запятой
  • %n — перенос строки (часто удобнее, чем \n)
  • Ввод с клавиатуры через Scanner

    Для чтения ввода в консоли обычно используют Scanner.

    Полный пример:

    Пояснения простыми словами:

  • import java.util.Scanner; — подключили класс Scanner.
  • new Scanner(System.in) — создаём сканер, который читает из клавиатуры.
  • nextLine() — читает строку целиком.
  • nextInt() — читает целое число.
  • Документация (если захотите посмотреть все методы): Scanner (Java SE Documentation)

    Частая проблема: nextInt() и nextLine()

    Если вы читаете число через nextInt(), а потом хотите прочитать строку через nextLine(), можно неожиданно получить пустую строку. Причина: после числа в потоке остаётся символ перевода строки.

    Пример, который часто ломается:

    Простой фикс: после nextInt() прочитать остаток строки:

    Мини-программа: калькулятор суммы и среднего

    Эта программа тренирует переменные, типы, операторы и ввод-вывод.

    Итог

    Вы освоили основу, без которой невозможно двигаться дальше:

  • как объявлять переменные и выбирать тип
  • чем отличаются int, double, boolean, char и String
  • главные операторы: арифметика, сравнение, логика, присваивание
  • как выводить в консоль (println, printf) и как читать ввод (Scanner)
  • Дальше обычно переходят к управлению логикой программы: ветвлениям (if/else) и циклам (for, while).

    3. Управляющие конструкции: условия, циклы и логика программы

    Управляющие конструкции: условия, циклы и логика программы

    В прошлой статье вы научились работать с переменными, типами данных, операторами и вводом-выводом через Scanner. Теперь добавим то, что превращает набор команд в логику программы: условия и циклы.

    Эти конструкции позволяют программе:

  • принимать решения: что делать, если условие true, и что делать иначе
  • повторять действия: пока условие выполняется или заданное число раз
  • !Наглядно показывает, как программа выбирает ветку и как работает повторение

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

    Любое условие в Java должно быть типа boolean, то есть иметь значение true или false.

    Примеры выражений, которые дают boolean:

    Часто условия составляют из нескольких частей с помощью логических операторов:

  • &&И: обе части должны быть true
  • ||ИЛИ: достаточно одной части true
  • !НЕ: инверсия
  • Короткое вычисление (short-circuit)

    Операторы && и || работают так, что Java может не проверять вторую часть, если результат уже понятен.

  • Для &&: если первая часть false, дальше можно не проверять
  • Для ||: если первая часть true, дальше можно не проверять
  • Это важно, когда во второй части есть действия, которые нельзя выполнять всегда.

    Условия if, else if, else

    if

    if выполняет блок кода, если условие true.

    if и else

    else выполняется, если условие в if оказалось false.

    else if для нескольких вариантов

    Полезно помнить:

  • проверка идёт сверху вниз
  • как только найдено подходящее условие, остальные ветки пропускаются
  • Частая ошибка: = вместо ==

  • = — присваивание
  • == — сравнение
  • В условиях почти всегда нужны операторы сравнения (==, !=, >, <, >=, <=).

    switch для выбора из вариантов

    switch удобен, когда есть одна переменная и несколько фиксированных вариантов.

    Пример с числом:

    Зачем нужен break:

  • без break выполнение продолжится в следующем case (это называется проваливание), и часто это ошибка
  • switch можно делать и по строкам:

    Если захотите увидеть полный список возможностей switch, можно открыть документацию Java: Java Language Specification: switch Statements

    Циклы: while, do-while, for

    Циклы нужны, когда действие нужно повторять много раз.

    while

    Цикл while проверяет условие до выполнения тела.

    Что важно:

  • внутри цикла нужно менять значения так, чтобы условие когда-то стало false
  • иначе получится бесконечный цикл
  • do-while

    do-while выполняется хотя бы один раз, а проверка идёт после тела.

    for

    for удобен, когда известно, сколько раз повторять.

    У for три части:

  • int i = 1 — старт
  • i <= 5 — условие продолжения
  • i++ — шаг после каждой итерации
  • break и continue

    break

    break полностью завершает цикл.

    continue

    continue пропускает текущую итерацию и переходит к следующей.

    Вложенные конструкции

    Можно вкладывать условия в циклы и циклы друг в друга. Главное — не запутаться и следить за фигурными скобками.

    Пример: таблица умножения (частично).

    Практический пример: проверка ввода и меню

    Ниже мини-программа, которая показывает типичный сценарий:

  • читаем ввод
  • используем switch
  • повторяем через while
  • Что здесь происходит:

  • running управляет циклом: пока true, меню показывается снова
  • switch выбирает действие по choice
  • default ловит неверные варианты
  • Если хотите подробнее изучить if, switch и циклы в официальном учебнике: Oracle Java Tutorial: Control Flow Statements

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

  • Забыли поменять счётчик в while и получили бесконечный цикл
  • Поставили ; сразу после if (условие); и блок стал работать не так, как ожидалось
  • Сравнивают строки через == вместо equals
  • Про строки важно:

    == для ссылочных типов чаще сравнивает не текст, а то, один и тот же это объект или нет.

    Итог

    Теперь вы умеете строить логику программы:

  • принимать решения через if/else и switch
  • повторять действия через while, do-while, for
  • управлять выполнением циклов через break и continue
  • составлять условия с &&, ||, ! и понимать короткое вычисление
  • Дальше обычно переходят к более крупным строительным блокам: методам (функциям), массивам и работе с несколькими значениями сразу.

    4. Методы: параметры, возвращаемые значения, перегрузка и область видимости

    Методы: параметры, возвращаемые значения, перегрузка и область видимости

    До этого вы писали код в основном внутри main, использовали условия if/switch и циклы for/while. Это отлично для первых шагов, но довольно быстро main превращается в длинный и неудобный текст.

    Методы помогают разбивать программу на маленькие понятные части: прочитать число, посчитать сумму, распечатать результат, проверить ввод. Так код проще читать, тестировать и изменять.

    Что такое метод

    Метод — это именованный блок кода, который можно вызывать много раз.

    Плюсы методов:

  • Код становится короче и понятнее
  • Повторяющиеся действия не копируются
  • Логику проще менять: исправили в одном месте
  • Простой пример:

    Здесь:

  • sum — метод
  • int a, int b — параметры (входные данные)
  • return a + b; — возвращаемое значение (результат)
  • Из чего состоит метод

    Обычно метод выглядит так:

    Основные части:

  • Тип возвращаемого значения: например int, double, String или void
  • Имя метода: например sum, printMenu
  • Параметры: то, что метод получает на вход
  • Тело метода: что именно делаем
  • return: чем заканчиваем метод и что отдаём наружу (если тип не void)
  • В этом курсе на старте вы чаще всего будете писать методы со словом static. Так их можно вызывать прямо из main, не изучая пока объекты и классы глубже.

    Параметры: как передавать данные в метод

    Параметры — это переменные в круглых скобках при объявлении метода.

    Пример: метод считает стоимость со скидкой.

    Здесь:

  • price и percent — параметры
  • при вызове applyDiscount(1000.0, 15) числа 1000.0 и 15 называются аргументами
  • Важное правило: порядок и типы параметров

    При вызове метода:

  • количество аргументов должно совпадать с количеством параметров
  • типы должны подходить
  • порядок важен
  • Например, если метод объявлен как applyDiscount(double, int), то applyDiscount(15, 1000.0) не подходит.

    Как Java передаёт значения в метод (простое объяснение)

    Java всегда передаёт в метод копию значения.

  • Для примитивов (int, double, boolean) это означает: метод не может изменить переменную снаружи
  • Для ссылочных типов (например, String) передаётся копия ссылки на объект
  • Пример с int:

    x не изменился, потому что метод работал со своей копией.

    Возвращаемые значения: return и void

    Метод с результатом

    Если метод должен вернуть результат, указываем тип и используем return.

    Правила:

  • return завершает выполнение метода
  • в методе с типом int нужно вернуть int во всех возможных ветках
  • Метод без результата: void

    Если метод просто делает действие (например, печатает), можно использовать void.

    В void-методе можно написать return;, чтобы выйти раньше.

    Перегрузка методов: одно имя, разные параметры

    Перегрузка — это когда в одном классе есть несколько методов с одним именем, но разными параметрами.

    Например, метод sum для двух чисел и для трёх чисел:

    Как Java выбирает нужный метод:

  • смотрит на количество параметров
  • смотрит на типы параметров
  • Важно:

  • перегрузка не зависит только от возвращаемого типа
  • нельзя сделать так: два метода с одинаковыми параметрами, но разными возвращаемыми типами
  • Неправильно:

    Область видимости: где переменная существует и доступна

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

    Самое простое правило:

  • переменная видна только внутри фигурных скобок { ... }, в которых она объявлена
  • !Схема показывает, что параметры и локальные переменные живут только внутри своего метода и блока

    Локальные переменные

    Локальная переменная объявляется внутри метода.

    После завершения demo() переменная x больше не существует.

    Параметры тоже имеют область видимости

    Параметр виден только внутри своего метода.

    Снаружи метода text и times недоступны.

    Переменные в блоках if и циклах

    Переменная, объявленная внутри if, не видна снаружи.

    То же самое относится к переменной цикла:

    Частая проблема: переменная с тем же именем (затенение)

    Если внутри блока объявить переменную с тем же именем, внешняя переменная станет временно недоступной.

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

    Практический пример: мини-калькулятор, но аккуратно через методы

    Ниже программа, где логика разделена на методы:

  • readDouble читает число и не даёт программе упасть на неверном вводе
  • sum и avg считают
  • printResult печатает
  • Обратите внимание:

  • методы делают по одной понятной задаче
  • readDouble показывает типичную связку циклов и методов: пока ввод неверный, повторяем
  • Типичные ошибки новичка

  • Забыли return в одной из веток метода, который должен вернуть значение
  • Пытаются изменить переменную снаружи, меняя параметр внутри метода (для примитивов это не сработает)
  • Путаются в области видимости и пытаются использовать переменную вне блока { ... }
  • Считают, что перегрузка зависит от возвращаемого типа (это не так)
  • Итог

    Теперь вы умеете организовывать код с помощью методов:

  • передавать данные через параметры
  • получать результат через возвращаемое значение и return
  • писать методы без результата с void
  • использовать перегрузку методов, когда нужна одна операция в разных вариантах
  • понимать область видимости переменных в методах, блоках if и циклах
  • Если захотите посмотреть официальное объяснение методов и параметров, можно открыть материалы Oracle:

  • Java Tutorials: Defining Methods
  • Java Tutorials: Variables
  • 5. ООП в Java: классы, объекты, инкапсуляция, наследование, полиморфизм

    ООП в Java: классы, объекты, инкапсуляция, наследование, полиморфизм

    До этого момента вы писали программы, где почти вся логика живёт в main и нескольких static-методах. Это хороший старт: вы научились переменным, условиям, циклам и методам. Но дальше программы становятся больше, и нужен способ хранить данные вместе с поведением и управлять сложностью.

    ООП (объектно-ориентированное программирование) — это подход, где программа собирается из объектов: у каждого объекта есть состояние (данные) и поведение (методы).

    Классы и объекты

    Класс — это “чертёж”

    Класс описывает, какие данные и какие действия есть у сущности.

    Например, сущность “Банковский счёт”:

  • данные: баланс, владелец
  • действия: пополнить, снять, узнать баланс
  • Объект — это “экземпляр по чертежу”

    Объект — конкретная созданная сущность в памяти, сделанная по описанию класса.

    !Схема различий между классом и объектами

    Первый класс: BankAccount

    И использование в main:

    Что важно заметить:

  • BankAccount acc = new BankAccount(); создаёт объект.
  • acc.deposit(500) — вызов метода у объекта.
  • owner и balance — поля (данные объекта).
  • Поля, методы, static и “обычные” методы

    Раньше вы часто писали static-методы, чтобы вызывать их из main.

  • static относится к классу (одно на весь класс).
  • без static относится к объекту (у каждого объекта своё).
  • Пример (покажем разницу):

    Если создать 3 объекта, totalCreated станет 3, а вот value у каждого будет свой.

    Конструктор: как правильно создавать объект

    Конструктор — специальный метод, который запускается при new.

    Добавим конструктор в BankAccount, чтобы нельзя было забыть задать владельца:

    Использование:

    Ключевое слово this означает “текущий объект”. Здесь this.owner — поле конкретного аккаунта.

    Инкапсуляция: прячем внутренности и защищаем данные

    В примере выше можно сделать так:

    С точки зрения “реального банковского счёта” это ошибка. Инкапсуляция — это принцип: скрывать внутренние детали и давать доступ через понятные и безопасные методы.

    Модификаторы доступа

    Самые важные на старте:

    | Модификатор | Где доступно | Для чего обычно используют | |---|---|---| | public | отовсюду | публичный API класса | | private | только внутри этого класса | скрыть поля и внутреннюю логику | | protected | в пакете и в наследниках | доступ для наследования | | без модификатора | в пределах пакета | внутренние классы внутри одного пакета |

    Пример инкапсуляции: private поля + методы

    Использование:

    Идея проста:

  • поля закрыли (private)
  • правила сосредоточили в методах (deposit, withdraw)
  • снаружи объект теперь сложно “сломать” случайным присваиванием
  • > В ООП принято “говорить объекту, что сделать”, а не “залезать внутрь и менять его поля напрямую”.

    Наследование: расширяем существующий класс

    Наследование позволяет создать новый класс на основе существующего.

  • базовый класс (родитель) содержит общую часть
  • дочерний класс (наследник) добавляет или изменяет поведение
  • Пример: есть обычный аккаунт и аккаунт с кредитным лимитом.

    Наследник:

    Что здесь происходит:

  • extends BankAccount означает “наследуемся от BankAccount”.
  • super(owner) вызывает конструктор родителя.
  • @Override показывает, что мы переопределяем метод родителя.
  • поле balance сделано protected, чтобы наследник мог аккуратно работать с ним.
  • Важно: наследование — мощный инструмент, но им легко злоупотребить. На старте используйте его там, где реально есть отношение “является”: кредитный аккаунт является аккаунтом.

    !Диаграмма наследования классов

    Полиморфизм: один интерфейс — разные реализации

    Полиморфизм простыми словами: переменная типа родителя может хранить объект типа наследника, и будет вызываться правильная версия метода.

    Пример:

    Здесь:

  • и a, и b имеют тип BankAccount
  • но b на самом деле хранит объект CreditAccount
  • при b.withdraw(...) вызывается переопределённый метод CreditAccount
  • Это полезно, когда вы хотите писать код “в общих терминах”:

  • хранить список аккаунтов как BankAccount
  • вызывать одинаковые методы
  • получать разное поведение в зависимости от конкретного типа
  • Интерфейсы: общий контракт без привязки к классу

    Иногда общее поведение не укладывается в одно семейство наследования. Тогда используют интерфейс — набор методов, которые класс обязуется реализовать.

    Пример: всё, что умеет печатать информацию о себе.

    Реализация:

    Теперь любой класс может быть Printable, даже если он не “является аккаунтом”.

    Практический пример: отчёт по разным аккаунтам через полиморфизм

    Соберём вместе инкапсуляцию, наследование и полиморфизм.

    Идея: вы пишете код один раз (через тип BankAccount), а объекты ведут себя по-разному.

    Типичные ошибки новичка в ООП

  • Путают класс и объект: класс — это описание, объект — конкретная сущность.
  • Делают все поля public и ломают правила состояния объекта.
  • Считают, что наследование нужно “всегда”: на практике часто лучше сначала сделать один хороший класс и только потом выделять наследников.
  • Переопределяют метод, но забывают @Override и не замечают опечатку в сигнатуре.
  • Итог

    Теперь у вас есть базовые кирпичики ООП в Java:

  • класс описывает данные и методы, объект хранит конкретные значения
  • конструктор помогает создать объект в корректном состоянии
  • инкапсуляция защищает данные через private и методы доступа
  • наследование позволяет расширять и переиспользовать код (extends, super)
  • полиморфизм даёт единый способ работы с разными объектами через общий тип
  • интерфейсы задают контракт поведения без жёсткой привязки к одному классу-родителю
  • Если захотите свериться с официальными материалами Oracle:

  • Defining Classes (Oracle Java Tutorials)
  • Inheritance (Oracle Java Tutorials)
  • Interfaces and Inheritance (Oracle Java Tutorials)
  • 6. Коллекции и дженерики: List, Set, Map, итераторы и сортировка

    Коллекции и дженерики: List, Set, Map, итераторы и сортировка

    В прошлой статье вы познакомились с ООП: классами, объектами, инкапсуляцией, наследованием и полиморфизмом. Но как только в программе появляется много объектов (пользователи, счета, товары, сообщения), сразу возникает практическая задача: где и как их хранить, перебирать, искать и сортировать.

    Для этого в Java есть коллекции (Collections Framework) и дженерики (generics), которые делают работу с коллекциями безопасной и удобной.

    !Наглядно показывает разницу между List, Set и Map

    Что такое коллекции и зачем они нужны

    Если нужно хранить несколько значений, первое, что приходит в голову, это массив.

    Но у массивов есть ограничения:

  • размер нужно знать заранее (или создавать новый массив и копировать)
  • неудобно вставлять и удалять элементы
  • нет готовых удобных операций вроде поиска, проверки уникальности, хранения пар ключ-значение
  • Коллекции решают эти проблемы: они умеют динамически расти, предоставляют готовые структуры данных и методы.

    Официальная справка: Collections Framework Overview

    Дженерики: почему List<String> лучше, чем просто List

    Дженерики позволяют указать, какой тип элементов хранится в коллекции.

    Пример:

    Что дают дженерики:

  • безопасность типов: нельзя случайно положить в List<String> число
  • меньше ошибок во время выполнения: больше проверок происходит при компиляции
  • не нужно делать приведения типа (не нужен (String))
  • Плохой (устаревший) стиль, который лучше не использовать:

    С дженериками Java не даст так написать.

    Документация: Generics (Java Tutorials)

    List: упорядоченная коллекция с доступом по индексу

    List хранит элементы:

  • в определенном порядке (как вы добавляли)
  • допускает повторы
  • позволяет получать элемент по индексу: get(0), get(1)
  • Основные реализации:

  • ArrayList: самый популярный вариант для начинающих
  • LinkedList: используется реже, в специфических задачах
  • Документация: List

    Основные операции с List

    Полезные методы:

  • add(value)
  • get(index)
  • set(index, value)
  • remove(index) или remove(value)
  • size()
  • contains(value)
  • Перебор List: цикл for-each

    Самый простой способ пройтись по элементам:

    Это читается как: для каждого элемента t в списке tasks.

    List с объектами (связь с ООП)

    Вы можете хранить в списке не только строки, но и свои объекты.

    Set: коллекция без повторов

    Set хранит элементы так, что одинаковых значений быть не может.

  • порядок может быть не таким, как при добавлении (зависит от реализации)
  • повторы не допускаются
  • Частые реализации:

  • HashSet: самый популярный, порядок не гарантируется
  • LinkedHashSet: сохраняет порядок добавления
  • TreeSet: хранит в отсортированном виде
  • Документация: Set

    Пример HashSet

    Важная идея: как Set понимает, что элементы одинаковые

    Для строк (String) все работает ожидаемо, потому что String уже умеет сравнивать значения.

    Для ваших классов важно, чтобы Java могла понять, что два объекта равны по смыслу. Для этого используют методы equals() и hashCode().

    На старте запомните простое правило:

  • если вы кладете свои объекты в HashSet (или используете их как ключи в HashMap), то вам почти всегда нужно корректно переопределить equals() и hashCode()
  • Документация: Object.equals) и Object.hashCode)

    Map: хранение пар ключ-значение

    Map хранит пары:

  • ключ (key)
  • значение (value)
  • Ключи уникальны. Значения могут повторяться.

    Пример: по email быстро находить пользователя.

    Документация: Map

    Пример HashMap

    Полезные методы:

  • put(key, value)
  • get(key)
  • containsKey(key)
  • remove(key)
  • keySet() (все ключи)
  • values() (все значения)
  • entrySet() (все пары ключ-значение)
  • Перебор Map через entrySet()

    Это самый удобный способ, когда нужны и ключ, и значение.

    Итераторы: как коллекции перебираются внутри

    Вы уже использовали for-each. Под капотом он часто работает через итератор.

    Итератор это объект, который умеет идти по коллекции шаг за шагом.

    Документация: Iterator

    Зачем итератор нужен новичку

    Самая частая практическая причина: нужно удалять элементы во время перебора.

    Неправильный вариант (часто приводит к ошибке во время выполнения):

    Правильный вариант: использовать Iterator и его remove().

    Здесь:

  • hasNext() проверяет, есть ли следующий элемент
  • next() возвращает следующий элемент
  • remove() безопасно удаляет текущий элемент
  • Сортировка: Comparable, Comparator и сортировка списков

    Сортировка чаще всего применяется к List.

    Есть два основных подхода:

  • объект сам знает, как его сортировать: Comparable
  • вы задаете правило сортировки снаружи: Comparator
  • Документация: Comparable и Comparator

    Сортировка простых типов

    Строки и числа сортируются сразу.

    Документация: Collections

    Comparator: сортировка объектов по правилу

    Допустим, у нас есть счета, и мы хотим сортировать их по балансу.

    Здесь:

  • accounts.sort(...) сортирует список на месте
  • Comparator.comparingInt(...) задает правило: сравнивать по целому числу
  • Сортировка по убыванию

    Когда нужен Comparable

    Если у вашего класса есть естественный порядок (например, деньги, даты, номера), можно реализовать Comparable.

    Идея: класс сам определяет, как сравнивать два объекта.

    Пример (упрощенный): сортировка аккаунтов по владельцу.

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

    Как выбрать структуру: короткая памятка

    | Что нужно | Подходит | Почему | |---|---|---| | Хранить элементы в порядке добавления, разрешить повторы, брать по индексу | List (ArrayList) | Упорядоченно, есть индекс | | Хранить только уникальные значения | Set (HashSet) | Дубликаты не добавляются | | Быстро находить значение по ключу | Map (HashMap) | Доступ через ключ |

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

  • Используют коллекции без дженериков (List list = ...) и получают ошибки приведения типа
  • Пытаются удалять элементы из List внутри for-each вместо Iterator.remove()
  • Ожидают, что HashSet или HashMap будут хранить элементы в том же порядке, что и добавление
  • Сортируют объекты без Comparator или Comparable и удивляются, что Java не знает, как сравнивать
  • Итог

    Теперь у вас есть базовый набор для работы с множеством данных:

  • дженерики для безопасных коллекций: List<BankAccount>, Map<String, Integer>
  • List для упорядоченных данных и доступа по индексу
  • Set для уникальных значений
  • Map для связки ключ-значение
  • итераторы для корректного перебора и безопасного удаления
  • сортировка через Comparator и Comparable
  • С этими инструментами вы можете писать программы, которые хранят и обрабатывают реальные наборы объектов: пользователей, заказы, товары, счета и многое другое.

    7. Исключения и файлы: try-catch, свои ошибки, чтение/запись и мини-проект

    Исключения и файлы: try-catch, свои ошибки, чтение/запись и мини-проект

    Раньше вы писали программы, которые работают в памяти: читают ввод через Scanner, используют условия, методы, ООП и коллекции (List, Map) и печатают результат в консоль. В реальных программах почти всегда появляются две практические темы:

  • ошибки во время выполнения
  • сохранение данных между запусками (файлы)
  • В Java это решается через исключения (exceptions) и работу с файлами (обычно через Path и Files).

    !Как работает try-catch-finally

    Что такое исключение

    Исключение — это объект, который описывает ошибку и может прервать обычный ход выполнения программы.

    Примеры ситуаций, где часто возникают исключения:

  • деление на ноль
  • попытка открыть несуществующий файл
  • неверный формат числа при разборе строки
  • В Java исключения представлены классами, которые наследуются от Throwable. В практике новичка важнее всего понимать два семейства:

  • Exception — ошибки, которые можно и нужно обрабатывать
  • RuntimeException — ошибки программирования или неверных данных, которые часто не требуют обязательной обработки
  • Документация:

  • Исключения (Oracle Java Tutorials)
  • Класс Exception (Java SE)
  • Класс RuntimeException (Java SE)
  • try-catch: как перехватывать ошибки

    try-catch позволяет попробовать выполнить код и обработать ошибку, если она произошла.

    Пример: безопасный разбор числа.

    Что здесь важно:

  • код в try выполняется обычным образом
  • если возникает исключение указанного типа, управление переходит в catch
  • после catch программа продолжает работу (если вы сами не завершили её)
  • Несколько catch-блоков и порядок

    Можно обрабатывать разные ошибки по-разному.

    Правило:

  • более конкретные исключения пишутся выше
  • более общие (например, Exception) пишутся ниже
  • finally: код, который выполняется в любом случае

    finally выполняется:

  • если ошибки не было
  • если ошибка была и её поймали
  • если ошибка была и не поймали (обычно перед завершением программы)
  • Типичный смысл finally — освобождение ресурсов.

    try-with-resources: правильное закрытие файлов

    Работа с файлами связана с ресурсами, которые нужно закрывать: потоки, читатели, писатели.

    try-with-resources автоматически закрывает ресурс, даже если внутри произошла ошибка.

    Документация:

  • The try-with-resources Statement (Oracle Java Tutorials)
  • Checked и unchecked исключения: что это значит на практике

    Упрощённо в Java есть две категории исключений.

  • Checked (проверяемые) — компилятор требует обработать или объявить; пример: IOException.
  • Unchecked (непроверяемые) — компилятор не требует обработки; пример: NumberFormatException.
  • Пример: методы работы с файлами часто бросают IOException, поэтому Java заставляет вас явно решить, что делать.

    Есть два основных варианта:

  • обработать через try-catch
  • пробросить дальше через throws (обычно выше, где вы сможете нормально решить проблему)
  • Пример с throws:

    Идея:

  • throws IOException говорит: "если ошибка чтения случится, пусть она уйдёт выше"
  • в небольших учебных программах это удобно, но в реальных приложениях чаще обрабатывают ошибку и показывают понятное сообщение
  • Как создавать свои ошибки: throw и пользовательские исключения

    Иногда ошибка не техническая (не файл, не сеть), а логическая: вы не хотите допускать неверное состояние объекта или данных.

    В таких случаях удобно:

  • проверять условия
  • при нарушении условий выбрасывать исключение через throw
  • Пример: банковский счёт из прошлой статьи по ООП.

    Почему здесь RuntimeException:

  • это логическая ошибка использования метода withdraw
  • мы не хотим заставлять каждое место вызова писать обязательный try-catch
  • Если вы хотите, чтобы обработка была обязательной, можно наследоваться от Exception, но тогда придётся добавлять throws и try-catch при каждом вызове.

    Файлы: Path и Files (современный и удобный способ)

    Для новичка самый практичный путь — пакет java.nio.file:

  • Path описывает путь к файлу
  • Files содержит готовые методы чтения и записи
  • Документация:

  • Класс Path (Java SE)
  • Класс Files (Java SE)
  • Чтение файла целиком в строку

    Удобно для небольших файлов.

    Чтение файла построчно в List

    Это особенно хорошо сочетается с коллекциями из прошлой темы.

    Запись строки в файл

    По умолчанию Files.writeString перезаписывает файл.

    !Как данные переходят между памятью и файлом

    Мини-проект: консольный список задач с сохранением в файл

    Сделаем маленькую программу, которая:

  • загружает задачи из файла при старте
  • позволяет добавлять и удалять задачи
  • сохраняет задачи в файл
  • Формат хранения

    Будем хранить каждую задачу отдельной строкой в tasks.txt.

    Пример содержимого файла:

    Код мини-проекта

    Что здесь тренируется сразу из нескольких прошлых тем:

  • коллекции: List<String> для задач
  • циклы и switch: меню
  • методы: loadTasks, saveTasks, removeTask
  • исключения: обработка NumberFormatException и своё TaskNotFoundException
  • файлы: Files.readAllLines, Files.write
  • Частые ошибки новичка и хорошие привычки

  • Не ловите Exception просто чтобы "потушить" ошибку; лучше ловить конкретные типы, которые вы ожидаете.
  • Не игнорируйте сообщение об ошибке; e.getMessage() помогает понять причину.
  • Для файлов почти всегда используйте try-with-resources или методы Files, которые сами управляют ресурсами.
  • Если ошибка означает неверный входной параметр, часто подходит IllegalArgumentException или своё исключение на базе RuntimeException.
  • Итог

    Теперь вы умеете:

  • понимать, что такое исключения и зачем они нужны
  • обрабатывать ошибки через try-catch и использовать finally
  • автоматически закрывать ресурсы через try-with-resources
  • различать checked и unchecked исключения на базовом уровне
  • создавать свои исключения и выбрасывать их через throw
  • читать и записывать файлы через Path и Files
  • После этой темы ваши программы могут не только работать с данными в памяти, но и сохранять результат на диск и корректно переживать ошибки ввода и окружения.