Programming

Java

Java — статически типизированный язык, компилируемый в байт-код JVM, основной инструмент корпоративных бэкендов и Android-разработки.

Что такое Java

Java — объектно-ориентированный язык со статической строгой типизацией, выпущенный Sun Microsystems в 1995 году. Код компилируется не в машинные инструкции, а в байт-код, который исполняет виртуальная машина JVM. Отсюда принцип «write once, run anywhere»: один и тот же .jar работает везде, где есть JVM.

Три аббревиатуры, которые постоянно путают:

  • JVM (Java Virtual Machine) — виртуальная машина, исполняющая байт-код. Только запуск.
  • JRE (Java Runtime Environment) — JVM плюс стандартные библиотеки. Достаточно, чтобы запускать.
  • JDK (Java Development Kit) — JRE плюс компилятор javac и инструменты. Нужен, чтобы собирать.

Где живёт Java: банковские и корпоративные бэкенды (Spring Boot), Android, Big Data — Hadoop, Kafka и Elasticsearch написаны на JVM-языках.

@Override — что это

@Override — аннотация, помечающая метод как переопределяющий метод родительского класса или реализующий метод интерфейса.

На поведение программы она не влияет вообще. Её единственная задача — заставить компилятор проверить, что метод действительно что-то переопределяет:

class Animal {
    void makeSound() { System.out.println("..."); }
}

class Dog extends Animal {
    @Override
    void makeSound() { System.out.println("Гав"); }   // ок

    @Override
    void makeSoud() { }        // опечатка → ошибка компиляции
}

Без @Override вторая строка молча создала бы новый метод makeSoud(), а makeSound() продолжил бы печатать точки. Баг ушёл бы в прод. Поэтому аннотацию ставят всегда — это бесплатная страховка от опечаток и от изменений в родительском классе.

equals и hashCode

equals() отвечает на вопрос «эти два объекта равны по смыслу?». По умолчанию (из Object) он сравнивает ссылки — то есть «это буквально один объект в памяти», почти никогда не то, что нужно.

hashCode() возвращает хеш объекта. Его используют HashMap и HashSet, чтобы быстро найти корзину с объектом, не сравнивая его со всеми подряд.

Контракт между ними — то, о чём и спрашивают на собеседованиях:

  1. Если a.equals(b), то a.hashCode() == b.hashCode() — обязательно.
  2. Обратное неверно: одинаковый хеш при разных объектах допустим (коллизия).
  3. Пока объект в коллекции, его хеш меняться не должен.

Нарушение первого пункта ломает коллекции. Переопределили equals, забыли hashCode — и объект «пропадает» из HashSet:

Set<User> set = new HashSet<>();
set.add(new User(1, "Пётр"));
set.contains(new User(1, "Пётр"));   // false, если hashCode не переопределён

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

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof User user)) return false;
    return id == user.id && Objects.equals(name, user.name);
}

@Override
public int hashCode() {
    return Objects.hash(id, name);
}

На практике оба метода генерирует IDE или закрывает Lombok (@EqualsAndHashCode); в современной Java для таких классов чаще берут record — там equals и hashCode есть из коробки.

Артефакт

Артефакт (artifact) в Java — результат сборки, файл, который можно положить в репозиторий и развернуть. JAR (Java ARchive) — библиотека или приложение, по сути zip с классами и манифестом. WAR (Web ARchive) — веб-приложение под сервлет-контейнер вроде Tomcat; сейчас чаще собирают fat JAR со встроенным сервером.

В Maven артефакт однозначно определяется координатами groupId:artifactId:version:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.0</version>
</dependency>

Здесь artifactId — имя самого модуля. Собирают проект через Maven или Gradle, а готовые артефакты хранят в репозитории (Maven Central, Nexus, Artifactory) — это JVM-аналог model registry или Docker Registry для образов.

mvn clean package        # собрать jar в target/
java -jar app.jar        # запустить

Java на сервере: память

Главная эксплуатационная особенность — JVM управляет памятью сама, и ограничивать её нужно явно. Куча (heap) растёт до -Xmx, а сверх неё JVM берёт метапространство, стеки потоков и буферы: реальное потребление процесса всегда больше -Xmx.

В контейнере это классические грабли — JVM видит память хоста, а не лимит контейнера, раздувает кучу, и процесс убивает OOM killer. Современные JDK учитывают cgroup-лимиты, но полагаться на умолчания не стоит:

java -XX:MaxRAMPercentage=75 -jar app.jar    # 75% от лимита контейнера

Отсюда же правило: лимит памяти в Kubernetes ставьте с запасом к -Xmx, а не равным ему.

Связанные термины

Готовы запустить GPU-задачу?

Запустить GPU-сервер