Java


28.07.2020https://habr.com/ru/articles/512730/
Keywords

if else switch case while do for break continue return try catch finally throw throws assert class interface enum extends implements abstract final static void synchronized volatile transient native strictfp boolean byte short int long float double char public private protected default import package this super new null true false instanceof goto const record

package

Механизм организации классов и интерфейсов в группы для упрощения управления кодом и предотвращения конфликтов имён.

package com.example.myapp;

public class MyClass {
    public void sayHello() {
        System.out.println("Hello!");
    }
}
Basic Types
byte

Хранит целые числа небольшого диапазона от -128 до 127.

byte a = 10;
short

Хранит целые числа среднего диапазона от -32,768 до 32,767.

short a = 1000;
int

Хранит целые числа от -2³¹ до 2³¹-1. Обеспечивает хороший баланс между диапазоном значений и объемом занимаемой памяти.

int number = 100;
long

Хранит целые числа большого диапазона от -2⁶³ до 2⁶³-1. Можно указать литерал L.

long bigNumber = 10000000000L;
float

Хранит числа с плавающей точкой одинарной точности. Требует суффикс f или F. Точность - примерно 6-7 цифр.

float pi = 3.14f;
double

Хранит числа с плавающей точкой двойной точности. По умолчанию все дробные значения считаются double. Точность - примерно 15-16 цифр. Можно указать литерал D.

double pi = 3.1415926535d;
boolean

Храненит логические значения true или false.

boolean isJavaFun = true;
char

Храненит символы Unicode. Символы указываются в одиночных кавычках.

char letter = 'A';
char unicodeChar = '\u0041'; // 'A' в Unicode
Basic Type Casting
intlong

Не требует явного приведения, так как long (64-бита) шире, чем int (32-бита). Преобразование происходит автоматически (implicit casting).

int intVal = 42;
long longVal = intVal;
intfloat

Не требует явного приведения типов. Преобразование происходит автоматически (implicit cast), так как float может точно представлять все значения int.

int intVal = 100;
float floatVal = intVal;
intdouble

Не требует явного приведения типов. Это автоматическое (неявное) преобразование, так как double может точно представлять все значения int.

int intVal = 42;
double doubleVal = intVal;
longint

Требует явное приведение типов (explicit cast), так как int (32 бита) уже, чем long (64 бита). Возможна потеря данных, если значение не помещается в диапазон int.

long longVal = 123456789L;
int intVal = (int) longVal; 
longfloat

Требует явное приведение типов (explicit cast), так как long (64-битное целое) шире, чем float (32-битное число с плавающей точкой). Возможна потеря точности, особенно для больших значений.

long longVal = 123456789012345L;
float floatVal = (float) longVal;
longdouble

Не требует явного приведения, так как double (64-бита) может точно представлять все значения long (также 64-бита), но с плавающей точкой.

long longVal = 123456789L;
double doubleVal = longVal;
floatint

Требует явное приведение (explicit cast), так как int — целочисленный тип, а float — дробный. Дробная часть будет отброшена без округления.

float floatVal = 5.99f;
int intVal = (int) floatVal;
floatlong

Требует явное приведение типов, так как float — это число с плавающей точкой, а long — целочисленный тип. Дробная часть отбрасывается.

float floatVal = 12345.67f;
long longVal = (long) floatVal;
floatdouble

Не требует явного кастинга, так как double (64-бита) шире, чем float (32-бита). Преобразование происходит автоматически.

float floatVal = 3.14f;
double doubleVal = floatVal;
doubleint

Требует явное приведение типов, так как дробная часть (double) будет отброшена, а значение округляется в сторону нуля.

double doubleVal = 3.99;
int intVal = (int) doubleVal;
doublelong

Требует явное приведение, так как double (64-битное число с плавающей точкой) содержит дробную часть, которая отбрасывается, и может выходить за пределы диапазона long.

double doubleVal = 123456789.99;
long longVal = (long) doubleVal;
doublefloat

Требуется явное приведение типов (explicit cast), так как float (32 бита) менее точный, чем double (64 бита). Возможна потеря точности.

double doubleVal = 3.141592653589793;
float floatVal = (float) doubleVal; 
Reference Types
Byte

Целое число от -128 до 127.

Byte b = 127;
Short

Целое число от -32,768 до 32,767.

Short s = 32000;
Integer

Целое число от -2³¹ до 2³¹-1.

Integer i = 100000;
Long

Целое число от -2⁶³ до 2⁶³-1.

Long l = 10000000000L;
Float

Дробное число с одинарной точностью.

Float f = 3.14f;
Double

Дробное число с двойной точностью.

Double d = 3.14159265359;
Boolean

Значение true или false.

Boolean bool = true;
Character

Символ в Unicode.

Character c = 'A';
Boxing

Процесс преобразования примитивного типа данных в объект его соответствующего типа-обертки.

int primitiveInt = 42;             // Примитивный тип
Integer wrappedInt = primitiveInt; // Боксинг, превращает примитив в объект
Unboxing

Процесс преобразования объекта типа-обертки в соответствующий примитивный тип.

Integer wrappedInt = 42;       // Объект типа Integer
int primitiveInt = wrappedInt; // Анбоксинг, превращает объект в примитив
сlass

В Java класс — это основная конструкция для определения объектов и инкапсуляции данных и поведения. Классы могут содержать поля (переменные экземпляра), методы и конструкторы.

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void introduce() {
        System.out.println("Hi, I'm " + name + " and I'm " + age + " years old.");
    }
}

Person person = new Person("Alice", 30);
person.introduce(); // Вывод: Hi, I'm Alice and I'm 30 years old.
new

Используется для создания нового экземпляра класса. Выделяет память под объект и вызывает конструктор класса.

Person person = new Person("Alice", 30); // Создание нового объекта Person
extends

Используется для наследования классов. Позволяет одному классу (подклассу) наследовать свойства и методы другого класса (суперкласса).

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

class Dog extends Animal { // Dog наследует Animal
    void sound() {
        System.out.println("Bark");
    }
}
implements

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

interface Drawable {
    void draw(); // Метод без тела
}

class Circle implements Drawable { // Circle реализует интерфейс Drawable
    public void draw() {
        System.out.println("Drawing a circle");
    }
}
instanceof

Проверяет, является ли объект экземпляром определенного класса или его подкласса.

• Можно использовать для проверки интерфейсов.

String text = "Hello, world!";

if (text instanceof String) {
    System.out.println("Это строка!");
}
record

Класс для неизменяемых объектов. Он автоматически создаёт конструктор, геттеры, equals, hashCode и toString.

public record Point(int x, int y) {}
Статическая загрузка классов

Происходит во время компиляции. Компилятор знает о классах и их зависимостях заранее. Статическая загрузка используется, когда класс загружается до его первого использования. Например, при инициализации статических переменных или вызове статических методов.

// В этом примере статический блок будет выполнен до вызова метода main.

public class StaticLoading {
    static {
        System.out.println("Static block executed");
    }

    public static void main(String[] args) {
        System.out.println("Main method executed");
    }
}
Динамическая загрузка классов

Происходит во время выполнения программы. Классы загружаются по мере необходимости, что позволяет уменьшить время начальной загрузки и память. Динамическая загрузка используется с помощью механизма рефлексии или в контексте загрузки классов в ответ на определенные действия.

// В этом примере класс SomeClass загружается динамически во время выполнения программы.

public class DynamicLoading {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("SomeClass"); // Динамическая загрузка класса
            Object obj = clazz.getDeclaredConstructor().newInstance(); // Создание экземпляра
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
interface

Задаёт контракт с методами, которые класс должен реализовать.

interface Animal {
    void sound(); // абстрактный метод

    default void eat() { // метод с реализацией
        System.out.println("This animal eats food.");
    }
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Woof");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound(); // Woof
        dog.eat();   // This animal eats food.
    }
}
default

Добавляет метод с реализацией в интерфейс. Классы, которые его реализуют, могут не переопределять этот метод.

interface Example {
    default void show() {
        System.out.println("Default method in interface");
    }
}
Functions

void

Когда метод объявлен с использованием void, это означает, что он не возвращает никаких значений. Внутри метода можно выполнять действия, такие как вывод на экран, изменение значений полей объекта и так далее, но не может быть оператора return с каким-либо значением.

void increment() {
    count++;
}

• Функция, возвращающая значение.

int add(int a, int b) {
    return a + b; // Возвращает сумму a и b
}

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

void printNumbers(int... numbers) {
    for (int number: numbers) {
        System.out.print(number + " ");
    }
    System.out.println();
}

// Вызов метода с переменным количеством аргументов
printNumbers(1, 2, 3, 4, 5);
Modifiers
public

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

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

public class Example {
    public int number;

    public void display() {
        System.out.println("This is a public method.");
    }
}
protected

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

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

class Dog extends Animal {
    public void bark() {
        makeSound(); // доступ через наследование
        System.out.println("Woof");
    }
}
private

Закрывает доступ к методу или переменной для всех, кроме самого класса. Такие данные недоступны для подклассов и других классов даже в одном пакете.

class Account {
    private double balance;

    private void updateBalance(double amount) {
        balance += amount;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            updateBalance(amount); // доступ только внутри класса
        }
    }
}
final

Запрещает изменение. Переменная становится неизменяемой после инициализации, метод нельзя переопределить в подклассе, а класс — наследовать.

final class Constants {
    final int MAX_USERS = 100;

    final void displayMax() {
        System.out.println("Max users: " + MAX_USERS);
    }
}

// Наследование и изменение метода запрещены
// class ExtendedConstants extends Constants {} // Ошибка
static

Делает метод, переменную или вложенный класс привязанными к классу, а не к его экземпляру. Доступ возможен без создания объекта.

class Outer {
    static class Inner {
        static void sayHello() {
            System.out.println("Hello from static class!");
        }
    }
}

Outer.Inner.sayHello(); // вызов без создания объектов
IO
InputStream

Используется для чтения данных из источника.

InputStream inputStream = new FileInputStream("file.txt");
int data;
while ((data = inputStream.read()) != -1) {
    System.out.print((char) data);
}
inputStream.close();
BufferedInputStream

Ускоряет чтение данных из потока, используя внутренний буфер. Вместо обращения к ресурсу каждый раз, он загружает данные большими порциями, что экономит время.

public class BufferedInputStreamExample {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file.txt"))) {
            int byteData;
            while ((byteData = bis.read()) != -1) {
                System.out.print((char) byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
OutputStream

Используется для записи данных в целевой объект.

OutputStream outputStream = new FileOutputStream("file.txt");
outputStream.write(65); // Запишет символ 'A'
outputStream.close();
BufferedOutputStream

Ускоряет запись данных, копя их в буфере и отправляя сразу крупными порциями. Это снижает количество операций и делает запись эффективнее.

public class Example {
    public static void main(String[] args) {
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
            bos.write("Пример использования BufferedOutputStream.".getBytes());
            bos.flush(); // Сбрасываем буфер
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Reader

Абстрактный класс для чтения символов. Предназначен для обработки текстовых данных и поддерживает работу с различными кодировками.

Reader reader = new FileReader("file.txt");
int data;
while ((data = reader.read()) != -1) {
    System.out.print((char) data);
}
reader.close();
BufferedReader

Ускоряет чтение текстовых данных, загружая их в буфер большими порциями. Он умеет читать строки целиком, что удобно для работы с текстовыми файлами.

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Writer

Абстрактный класс для записи символов. Он позволяет записывать текстовые данные в различные выходные источники.

Writer writer = new FileWriter("file.txt");
writer.write('A');
writer.close();
BufferedWriter

Ускоряет запись текста, используя буфер. Вы записываете данные частями, а он отправляет их в файл, когда буфер заполняется или вручную вызывается flush().

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
            bw.write("Пример использования BufferedWriter.");
            bw.newLine(); // Добавляем перенос строки
            bw.write("Записываем текст быстрее.");
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Exceptions
Checked Exceptions (проверяемые исключения)

Исключения, которые обязательно нужно обработать либо с помощью try-catch, либо указав в сигнатуре метода throws. Если вы этого не сделаете, код не скомпилируется.

Примеры

IOException — ошибки ввода-вывода.

SQLException — ошибки при работе с базой данных.

ClassNotFoundException — класс не найден.

InterruptedException — поток был прерван.

Unchecked Exceptions (непроверяемые исключения)

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

Примеры

NullPointerException — обращение к null.

ArithmeticException — деление на ноль.

IndexOutOfBoundsException — выход за границы массива или списка.

IllegalArgumentException — недопустимый аргумент в методе.

Throwable

Базовый класс для всех исключений, которые могут возникнуть в программе. От него наследуются Error и Exception.

Throwable
├── Error
│   ├── StackOverflowError
│   └── OutOfMemoryError
│
└── Exception
    ├── RuntimeException
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   └── IndexOutOfBoundsException
    ├── IOException
    └── SQLException
Error

Критические ошибки (например, переполнение памяти), которые обычно нельзя обработать.

Exception

Проблемы, которые можно и нужно обрабатывать (например, деление на ноль или отсутствие файла).

try

Cодержит код, который может вызвать исключение. Это обязательная часть конструкции.

try {
    int result = 10 / 0;
    System.out.println("Результат: " + result);
} catch (Exception e) {}
catch

Перехватывает исключение, если оно возникло в try.

try {
    int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("Деление на ноль недопустимо: " + e.getMessage());
}
finally

Выполняется всегда, независимо от того, было исключение или нет.

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Ошибка: " + e.getMessage());
} finally {
    System.out.println("Этот блок выполнится в любом случае.");
}
throw

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

if (age < 18) {
    throw new IllegalArgumentException("Age must be 18 or older");
}
throws

Указывает, какие исключения метод может выбросить.

public void readFile(String path) throws IOException {
    // Чтение файла
}
enum

Перечисление, специальный тип для фиксированного набора значений (констант).

enum Day {
    MONDAY, TUESDAY, WEDNESDAY;
}

Day today = Day.MONDAY;
name

Возвращает имя константы enum как строку.

enum Day {
    MONDAY, TUESDAY, WEDNESDAY
}

public static void main(String[] args) {
    System.out.println(Day.MONDAY.name()); // "MONDAY"
}
ordinal

Возвращает порядковый номер (индекс) константы.

enum Day {
    MONDAY, TUESDAY, WEDNESDAY
}

public static void main(String[] args) {
    System.out.println(Day.MONDAY.ordinal()); // 0
}
Generics

Механизм, позволяющий создавать классы, интерфейсы и методы с параметризованными типами.

• Примитивные типы (например, int, char) не могут использоваться в качестве параметров типа. Вместо этого используются их обертки (Integer, Character).

• Статические поля с параметризованными типами не допускаются.

• Невозможно создать массив дженериков.

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

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

// Использование
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String item = stringBox.getItem(); // Возвращает строку

• Общие методы. Методы также могут быть параметризованы типами.

public class Utils {
    public static <T> void printArray(T[] array) {
        for (T element: array) {
            System.out.println(element);
        }
    }
}

// Использование
Integer[] numbers = {1, 2, 3};
Utils.printArray(numbers); // Печатает числа

• Существующие классы. Дженерики широко используются в стандартной библиотеке Java, например, в коллекциях:

List<String> list = new ArrayList<>();
list.add("One");
String first = list.get(0); // Безопасно, возвращает строку

• Ограничения дженериков. Дженерики могут иметь ограничения, позволяющие использовать только определенные типы:

public class ComparableBox<T extends Comparable<T>> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public boolean isGreater(T other) {
        return item.compareTo(other) > 0;
    }
}

• Стирание типов. В Java дженерики реализованы с помощью стирания типов (type erasure). Это означает, что информация о типах дженериков не сохраняется во время выполнения.

List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
// На этапе выполнения они обе являются List
Lambdas

Лямбда-выражения в Java — способ писать компактный код для реализации функциональных интерфейсов. Они позволяют передавать поведение в виде параметра и упрощают работу с анонимными классами.

• Лямбда-выражения работают с функциональными интерфейсами — интерфейсами с одним методом.

• Лямбды появились в Java 8.

// Обычный способ через анонимный класс
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, world!");
    }
};

// Лямбда-выражение
Runnable lambdaRunnable = () -> System.out.println("Hello, world!");
lambdaRunnable.run();
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
     .filter(name -> name.startsWith("A"))
     .forEach(name -> System.out.println(name));
::

Оператор Method Reference используется для ссылки на методы или конструкторы, упрощая синтаксис лямбда-выражений.

• Ссылка на статический метод.

class Utils {
    public static void print(String s) {
        System.out.println(s);
    }
}

public static void main(String[] args) {
    List<String> list = List.of("Hello", "World");
    list.forEach(Utils::print); // Ссылка на метод
}

• Ссылка на метод экземпляра объекта.

public static void main(String[] args) {
    List<String> list = List.of("apple", "banana");
    list.forEach(System.out::println); // Ссылка на метод println объекта System.out
}

• Ссылка на метод экземпляра конкретного объекта.

class StringUtils {
    public void printUpperCase(String s) {
        System.out.println(s.toUpperCase());
    }
}

public static void main(String[] args) {
    StringUtils utils = new StringUtils();
    List<String> list = List.of("java", "kotlin");
    list.forEach(utils::printUpperCase);
}

• Ссылка на конструктор.

class User {
    String name;

    User(String name) {
        this.name = name;
    }
}

public static void main(String[] args) {
    Function<String, User> userFactory = User::new; // Ссылка на конструктор
    User user = userFactory.apply("Alice");
    System.out.println(user.name);
}
@FunctionalInterface

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

• Интерфейс с @FunctionalInterface должен содержать только один абстрактный метод.

• Могут быть дополнительные методы по умолчанию (default) или статические методы.

• Если в интерфейсе больше одного абстрактного метода, компилятор выдаст ошибку.

@FunctionalInterface
interface MyFunction {
    void sayHello(String name);
}

public class Example {
    public static void main(String[] args) {
        MyFunction greet = (name) -> System.out.println("Hello, " + name);
        greet.sayHello("World");
    }
}
native

Используется для объявления методов, реализованных на нативных языках, таких как C или C++. Это позволяет вызывать функции операционной системы или использовать библиотеки, написанные не на Java.

• Нативные методы требуют подключения JNI (Java Native Interface).

public class NativeExample {
    // Объявление нативного метода
    public native void printMessage();

    static {
        // Загрузка нативной библиотеки
        System.loadLibrary("NativeLib");
    }

    public static void main(String[] args) {
        new NativeExample().printMessage();
    }
}
Serializable

Указывает, что объект может быть сериализован, то есть преобразован в поток байтов.

class User implements Serializable {
    String name;
    int age;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
transient

Модификатор, который указывает, что поле не должно быть сериализовано. При сериализации значения таких полей будут пропущены и заменены на значение по умолчанию (например, null для объектов, 0 для чисел).

class User implements Serializable {
    String name;
    transient String password; // Поле не будет сериализовано

    User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}
Ручная сериализация

В режиме ручного управления в Serializable можно самостоятельно контролировать процесс сериализации и десериализации с помощью методов writeObject() и readObject(). Это полезно, когда требуется особая логика или нужно исключить чувствительные данные.

class User implements Serializable {
    String name;
    transient String password; // Поле не будет сериализовано автоматически

    User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    // Метод для кастомной сериализации
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject(); // Сериализуем обычные поля
        oos.writeObject(encrypt(password)); // Сериализуем пароль в зашифрованном виде
    }

    // Метод для кастомной десериализации
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject(); // Десериализуем обычные поля
        password = decrypt((String) ois.readObject()); // Расшифровываем пароль
    }

    private String encrypt(String data) {
        return new StringBuilder(data).reverse().toString(); // Пример шифрования (реверс строки)
    }

    private String decrypt(String data) {
        return new StringBuilder(data).reverse().toString(); // Расшифровка
    }
}
Dynamic Proxy

Механизм, позволяющий создавать объекты-прокси во время выполнения, которые могут перехватывать вызовы методов и выполнять кастомную логику. Используется для создания гибких и динамических реализаций интерфейсов без написания дополнительных классов.

• Работает только с интерфейсами.

Логику обработки вызова метода определяет интерфейс InvocationHandler, реализующий метод invoke().

interface HelloService {
    void sayHello(String name);
}
public class DynamicProxyExample {
    public static void main(String[] args) {
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
            HelloService.class.getClassLoader(),
            new Class[]{HelloService.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Method " + method.getName() + " is called with args: " + args[0]);
                    return null;
                }
            }
        );

        proxy.sayHello("World"); // Вызов метода прокси
    }
}
Java Version Match
Java RuntimeJava
49Java 5
50Java 6
51Java 7
52Java 8
53Java 9
54Java 10
55Java 11
56Java 12
57Java 13
58Java 14
59Java 15
60Java 16
61Java 17
62Java 18
63Java 19
64Java 20
65Java 21
Вопросы на собесе (11)
  • Generics (2)
    1. Какие есть проблемы у дженериков в Java?

      Стирание типов: информация о типах удаляется на этапе компиляции, что не позволяет проверять их в рантайме.

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

      Ограниченный полиморфизм: несовместимость типов (например, List<Object> и List<String>) требует wildcard (? extends T/? super T) для гибкости.

    1. Что такое стирание типов у дженериков?

      Это процесс, при котором информация о типах стирается во время компиляции, чтобы сохранить совместимость с ранними версиями Java. В результате, в байт-коде дженерики представлены в виде базовых типов Object или других ограничений, заданных через extends.

  • Exceptions (2)
    1. Различие проверенных и непроверенных исключений в Java?

      Проверенные исключения (checked) должны быть либо обработаны в коде, либо объявлены с помощью throws, и проверяются на этапе компиляции.

      Непроверенные исключения (unchecked) включают наследников RuntimeException и не требуют обязательной обработки или объявления.

    1. Как в Java работает ключевое слово throws?

      Указывает, что метод может выбросить одно или несколько исключений, которые должны быть обработаны вызывающим кодом либо с помощью try-catch, либо с добавлением throws в сигнатуру метода.

  • Другие (7)
    1. Разница между примитивными и ссылочными типами в Java?

      Примитивные типы в Java хранят сами значения, занимают меньше памяти и расположены в стеке, тогда как ссылочные типы хранят ссылку на объект в куче, что позволяет работать с более сложными структурами и поддерживать значение null.

    1. Различие final finally и finalize?

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

      finally — блок кода, который выполняется после try-catch, независимо от того, было ли исключение.

      finalize — метод, вызываемый сборщиком мусора перед удалением объекта из памяти.

    1. В чем разница между обычным и статическим классом в Java?

      Обычный класс связан с экземпляром, а статический класс (вложенный) может существовать без создания экземпляра внешнего класса.

    1. Разница между статической и динамической загрузкой классов?

      Статическая загрузка классов происходит при компиляции, а динамическая — во время выполнения программы с использованием рефлексии.

    1. Какое максимальное значение у 32-битного Int?

      2^{31} - 1

    1. Для чего в Java используется ключевое слово static?

      static обозначает члены класса, которые принадлежат самому классу, а не его экземплярам. Это позволяет обращаться к статическим переменным и методам без создания объекта, что полезно для хранения констант и вспомогательных методов.

    1. Есть две переменные: Integer a = 100, Integer b = 100. Результатом сравнения a == b будет?

      true

      false