Начало работы
Hello World
Каждая программа Java начинается с main(). Имя файла должно совпадать с именем публичного кла сса (Main.java → Main.class). javac компилирует в байткод (.class), java запускает его на JVM. System.out.println печатает в stdout; printf поддерживает спецификаторы формата (%s, %d, %f, %n для новой строки).
// Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
System.out.printf("Name: %s, Age: %d%n", "Alice", 30);
}
}
// Compile: javac Main.java -> Main.class
// Run: java Main
// Package: java -cp . com.example.Main
// Every Java program needs:
// 1. A class (public class, file name must match)
// 2. A main method: public static void main(String[] args)Переменные и примитивные типы
Java имеет 8 примитивных типов (int, long, double, float, boolean, char, byte, short) и ссылочные типы (String, массивы, объекты). Используйте 'final' для констант. 'var' (Java 10+) выводит тип при компиляции — используйте для локальных переменных с очевидными типами. Подчёркивания в числах (100_000) улучшают читаемость.
// Primitive types (8 total)
int age = 30; // 32-bit integer
long bigNum = 100_000L; // 64-bit integer
double price = 19.99; // 64-bit float (default for decimals)
float pi = 3.14f; // 32-bit float
boolean active = true; // true/false
char grade = 'A'; // 16-bit Unicode character
byte b = 127; // 8-bit signed
short s = 32767; // 16-bit signed
// Reference types
String name = "Alice"; // Object (not primitive)
int[] nums = {1, 2, 3}; // Array object
// Constants
final double PI = 3.14159; // can't be reassigned
// var (Java 10+, local type inference)
var count = 42; // inferred as int
var list = new ArrayList<String>(); // inferred as ArrayList<String>Wrapper-классы и boxing
Wrapper-классы (Integer, Double, Boolean и т.д.) — объектные версии примитивов. Autoboxing/unboxing конвертирует автоматически. Integer кэширует значения от -128 до 127, поэтому == работает для маленьких чисел, но не для больших — всегда используйте .equals(). Wrappers нужны для Collections (которые не могут хранить примитивы).
// Wrapper classes (Object versions of primitives)
Integer wrapped = Integer.valueOf(42); // explicit
Integer auto = 42; // autoboxing
int unboxed = auto; // unboxing
// Useful methods
int max = Integer.MAX_VALUE; // 2147483647
String bin = Integer.toBinaryString(42);
int parsed = Integer.parseInt("42");
String s = String.valueOf(42);
// Other wrappers: Double, Boolean, Character, Long, Float
Double d = 3.14;
Boolean b = Boolean.TRUE;
Character c = 'A';
// Be careful with == on wrappers
Integer a = 127, b2 = 127; // a == b2: true (cached)
Integer x = 128, y = 128; // x == y: false (not cached)
// Always use .equals() for Integer comparisonПакеты и импорты
Пакеты организуют классы и предотвращают конфликты имён. Конвенция: обратное доменное имя (com.example.app). Импортируйте конкретные классы или используйте wildcards (*). Static imports приносят константы и методы (Math.PI, Math.sqrt). Полные квалифицированные имена работают без импортов, но многословны. Пакет java.lang авто-импортируется.
// Package declaration (must be first line)
package com.example.app;
// Import specific class
import java.util.List;
import java.util.ArrayList;
// Import all classes in a package
import java.util.*;
// Static import (for static members)
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
// Usage
double area = PI * 5 * 5;
double root = sqrt(16);
// Fully qualified name (no import needed)
java.time.LocalDate today = java.time.LocalDate.now();
// Package naming convention: reverse domain
// com.google.gson, org.apache.commons, io.netty.channelВвод и вывод
System.out (stdout), System.err (stderr), System.in (stdin). Scanner — простейший способ читать консольный ввод — он парсит токены (nextInt, nextDouble, nextLine). Всегда закрывайте Scanner для освобождения ресурсов. Аргументы командной строки в args[] (args[0] — первый аргумент, а не имя программы, как в C).
import java.util.Scanner;
// Console output
System.out.println("Hello"); // with newline
System.out.print("No newline"); // without newline
System.out.printf("Pi: %.2f%n", 3.14159); // formatted
// Console input with Scanner
Scanner scanner = new Scanner(System.in);
System.out.print("Enter name: ");
String name = scanner.nextLine();
System.out.print("Enter age: ");
int age = scanner.nextInt();
System.out.printf("Hi %s, age %d%n", name, age);
scanner.close(); // always close
// Command-line arguments
// java Main arg1 arg2
// args[0] = "arg1", args[1] = "arg2"Строки и форматирование
Методы String
Строки неизменяемы — методы возвращают новые строки. Всегда используйте .equals() для сравнения содержимого (== сравнивает ссылки). compareTo() возвращает отрицательное/ноль/положительное для упорядочивания (полезно для сортировки). split() возвращает String[]. Для изменяемых строк используйте StringBuilder.
String s = "Hello, World";
// Length and access
int len = s.length(); // 12
char c = s.charAt(0); // 'H'
// Comparison
s.equals("Hello, World"); // true (content comparison)
s.equalsIgnoreCase("hello, world"); // true
s.compareTo("Apple"); // positive (s > "Apple")
"abc".compareTo("abd"); // negative
// Search
s.indexOf("World"); // 7 (-1 if not found)
s.lastIndexOf("l"); // 10
s.contains("World"); // true
s.startsWith("Hello"); // true
s.endsWith("World"); // true
// Extract
s.substring(7); // "World"
s.substring(0, 5); // "Hello"
// Transform
s.toUpperCase(); // "HELLO, WORLD"
s.toLowerCase(); // "hello, world"
s.replace("o", "0"); // "Hell0, W0rld"
s.trim(); // remove whitespace
s.split(", "); // ["Hello", "World"]StringBuilder и конкатенация
Конкатенация строк с + создаёт новую String каждый раз (неэффективно в циклах). StringBuilder изменяем и эффективен для инкрементального построения строк. StringBuffer — потокобезопасная версия (редко нужна). String.join() объединяет с разделителем. Java 11+ добавляет repeat() для умножения строк.
// String concatenation (creates new String each time)
String s = "Hello" + ", " + "World";
String formatted = String.format("%s is %d", "Alice", 30);
// StringBuilder (mutable, efficient for many concatenations)
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(", ");
sb.append("World");
sb.insert(5, " there");
sb.delete(5, 11);
sb.reverse();
String result = sb.toString();
// StringBuffer (thread-safe, slower than StringBuilder)
StringBuffer sbf = new StringBuffer("thread-safe");
// Join strings
String joined = String.join(", ", "a", "b", "c"); // "a, b, c"
// Repeat (Java 11+)
String repeated = "ab".repeat(3); // "ababab"Форматирование строк
printf/format использует C-стиль спецификаторов формата: %d (int), %f (float), %s (string), %c (char), %b (boolean), %x (hex). Ширина (%5d), левое выравнивание (%-5d), zero-pad (%05d), точность (%.2f). %n — платформенный перевод строки. Текстовые блоки (Java 15+) с тройными кавычками обеспечивают многострочные строки без экранирования.
// printf / format specifiers
System.out.printf("Int: %d%n", 42);
System.out.printf("Float: %.2f%n", 3.14159); // 3.14
System.out.printf("String: %s%n", "hello");
System.out.printf("Char: %c%n", 'A');
System.out.printf("Bool: %b%n", true);
System.out.printf("Hex: %x%n", 255); // ff
System.out.printf("Octal: %o%n", 8); // 10
// Width and padding
System.out.printf("[%5d]%n", 42); // [ 42]
System.out.printf("[%-5d]%n", 42); // [42 ]
System.out.printf("[%05d]%n", 42); // [00042]
System.out.printf("[%8.2f]%n", 3.14); // [ 3.14]
// String.format returns a String
String s = String.format("Name: %s, Age: %d", "Alice", 30);
// Text blocks (Java 15+)
String json = """
{
"name": "Alice",
"age": 30
}
""";Регулярные выражения
Java regex использует Pattern (скомпилированный) и Matcher (применённый к входу). Методы String (matches, split, replaceAll) — удобные ярлыки. Обратные слеши должны удваиваться в строковых литералах Java (\\d для \d). Группы захватываются скобками и ссылаются как $1, $2 в заменах. Всегда компилируйте паттерны один раз, если используете многократно.
import java.util.regex.*;
// String methods
"hello123".matches("[a-z]+\d+"); // true
"a,b,c".split(","); // ["a", "b", "c"]
"hello".replaceAll("l", "L"); // "heLLo"
// Pattern and Matcher
Pattern p = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher m = p.matcher("Date: 2024-01-15");
if (m.find()) {
System.out.println(m.group()); // "2024-01-15"
}
// Find all matches
while (m.find()) {
System.out.println(m.group());
}
// Replace with regex
String result = "2024-01-15".replaceAll(
"(\\d{4})-(\\d{2})-(\\d{2})",
"$3/$2/$1"); // "15/01/2024"
// Common patterns
String email = "^[\\w.]+@[\\w.]+\\.\\w+$";
String phone = "^\\d{3}-\\d{4}$";Числа и Math
Класс Math предоставляет статические математические функции. Math.random() возвращает 0.0-1.0. Для большего контроля используйте java.util.Random (с seed) или java.security.SecureRandom (криптографический). Integer/Double имеют статические utility-методы. Будьте осторожны с точностью floating-point — используйте BigDecimal для финансовых вычислений.
// Math class
double sqrt = Math.sqrt(16); // 4.0
double pow = Math.pow(2, 10); // 1024.0
int abs = Math.abs(-5); // 5
int max = Math.max(3, 7); // 7
int min = Math.min(3, 7); // 3
double rounded = Math.round(3.7); // 4
double ceil = Math.ceil(3.1); // 4.0
double floor = Math.floor(3.9); // 3.0
double random = Math.random(); // 0.0 to 1.0
// Constants
double pi = Math.PI; // 3.14159...
double e = Math.E; // 2.71828...
// Integer/Long methods
int sum = Integer.sum(3, 4); // 7
int max2 = Integer.max(3, 7); // 7
// Rounding modes
double r = Math.round(3.5); // 4 (round half up)
double r2 = Math.floor(3.5 + 0.5); // alternative
// Random (java.util.Random)
import java.util.Random;
Random rand = new Random();
int n = rand.nextInt(100); // 0-99
double d = rand.nextDouble(); // 0.0-1.0
boolean b = rand.nextBoolean();Управление потоком
If / Else
Java if/else работает как C/C++. Условия должны быть boolean — нет truthy/falsy как в JavaScript (0 и непустые строки НЕ truthy). Тернарный оператор (cond ? a : b) — выражение, а не инструкция. Используйте фигурные скобки даже для однострочных тел (лучшая практика стиля кода).
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else if (score >= 70) {
System.out.println("C");
} else {
System.out.println("F");
}
// Ternary operator
String grade = score >= 60 ? "Pass" : "Fail";
// Nested if
if (score >= 60) {
if (score >= 90) {
System.out.println("Excellent");
}
}
// Note: conditions must be boolean (no truthy/falsy)
// if (score) { } // Error: int is not booleanSwitch и выражения
Традиционный switch имеет fall-through (используйте break). Switch-выражения Java 14+ (->) не проваливаются и могут возвращать значения. Используйте запятые для нескольких case-меток (case 1, 2, 3). 'yield' возвращает значение из сложного блока. Switch-выражения исчерпывающие — нужен default или все случаи для enum'ов.
// Traditional switch (fall-through)
int day = 3;
switch (day) {
case 1:
System.out.println("Mon");
break;
case 2:
case 3:
case 4:
System.out.println("Midweek");
break;
case 6:
case 7:
System.out.println("Weekend");
break;
default:
System.out.println("Invalid");
}
// Switch expression (Java 14+, no fall-through)
String type = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> "Weekend";
default -> "Invalid";
};
// Switch with yield (for complex blocks)
int result = switch (day) {
case 1, 2, 3, 4, 5 -> {
int hours = 8;
yield hours * 5;
}
case 6, 7 -> 0;
default -> -1;
};Циклы
Java имеет циклы for, while и do-while. Расширенный for (for-each) работает с массивами и любым Iterable. break выходит из цикла; continue переходит к следующей итерации. Для коллекций предпочитайте for-each или streams вместо индексных циклов. do-while выполняется минимум один раз (редко используется).
// For loop
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
// Enhanced for (for-each)
int[] nums = {1, 2, 3, 4, 5};
for (int n : nums) {
System.out.println(n);
}
List<String> names = List.of("Alice", "Bob");
for (String name : names) {
System.out.println(name);
}
// While loop
int count = 0;
while (count < 3) {
System.out.println(count);
count++;
}
// Do-while (runs at least once)
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 3);
// Break and continue
for (int j = 0; j < 10; j++) {
if (j == 5) break; // exit loop
if (j % 2 == 0) continue; // skip iteration
System.out.println(j);
}Меченые break и continue
Метки (outer:) позволяют прерывать/продолжать внешние циклы из вложенных. Это редко нужно — извлечение в метод с return обычно чище. Метки ставятся перед циклом, после двоеточия. break label выходит из меченого цикла; continue label переходит к его следующей итерации.
// Labels for breaking out of nested loops
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outer; // exits both loops
}
System.out.println(i + "," + j);
}
}
// Labeled continue
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) {
continue outer; // skip to next i
}
System.out.println(i + "," + j);
}
}
// Alternative: extract to method and use return
void findPair(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == target) return; // exit method
}
}
}Массивы
Массивы имеют фиксированную длину (используйте ArrayList для динамических). Arrays.sort() сортирует на месте. Arrays.toString() даёт читаемое представление. Arrays.copyOf() создаёт копию с новой длиной. Для многомерных массивов каждая строка может иметь разную длину (jagged arrays). Используйте Arrays для utility-методов над массивами.
// Declare and initialize
int[] nums = {1, 2, 3, 4, 5};
int[] empty = new int[5]; // [0, 0, 0, 0, 0]
String[] names = new String[3]; // [null, null, null]
// Access and modify
nums[0] = 10;
int first = nums[0]; // 10
int length = nums.length; // 5
// Multidimensional
int[][] matrix = {{1, 2}, {3, 4}};
int val = matrix[0][1]; // 2
// Arrays utility class
import java.util.Arrays;
int[] sorted = {3, 1, 2};
Arrays.sort(sorted); // [1, 2, 3]
int[] copy = Arrays.copyOf(nums, 3);
String str = Arrays.toString(nums); // "[10, 2, 3, 4, 5]"
boolean eq = Arrays.equals(nums, copy);
// Fill
int[] filled = new int[5];
Arrays.fill(filled, 42); // [42, 42, 42, 42, 42]
// Binary search (sorted array only)
int idx = Arrays.binarySearch(sorted, 2); // index of 2Методы и функции
Определение метода
Методы Java всегда внутри класса. 'static' означает, что метод принадлежит классу (вызов без экземпляра). Возвращаемый тип (int, String, void) объявляется перед именем. Параметры типизированы. В Java нет значений параметров по умолчанию — используйте перегрузку методов вместо этого.
public class Calculator {
// Method with return type
public static int add(int a, int b) {
return a + b;
}
// Void method (no return)
public static void printResult(int result) {
System.out.println("Result: " + result);
}
// Method with default (no overloading needed)
public static String greet(String name, String greeting) {
return greeting + ", " + name + "!";
}
public static void main(String[] args) {
int sum = add(3, 4);
printResult(sum);
String msg = greet("Alice", "Hello");
System.out.println(msg);
}
}Перегрузка методов
Перегрузка методов позволяет несколько методов с одним именем, но разными списками параметров (тип, количество или порядок). Java разрешает перегрузки при компиляции на основе типов аргументов. Перегрузк а распространена для конструкторов и utility-методов. Это отличается от переопределения (которое включает наследование и runtime-диспетчеризацию).
public class MathUtils {
// Overloaded methods (same name, different params)
public static int add(int a, int b) {
return a + b;
}
public static double add(double a, double b) {
return a + b;
}
public static int add(int a, int b, int c) {
return a + b + c;
}
public static String add(String a, String b) {
return a + b;
}
}
// Java picks the most specific match
MathUtils.add(1, 2); // int version -> 3
MathUtils.add(1.5, 2.5); // double version -> 4.0
MathUtils.add(1, 2, 3); // 3-param version -> 6
MathUtils.add("Hello", "!"); // String version -> "Hello!"Varargs и pass-by-value
Varargs (Type... name) позволяют переменные аргументы, получаемые как массив. Java всегда pass-by-value: примитивы копируются, ссылки на объекты копируются (но указывают на тот же объект). Поэтому модификация параметра внутри метода не влияет на переменную вызывающего, но модификация объекта, на который он указывает, влияет.
// Varargs: variable number of arguments
public static int sum(int... nums) {
int total = 0;
for (int n : nums) {
total += n;
}
return total;
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
sum(); // 0 (empty array)
int[] arr = {1, 2, 3};
sum(arr); // 6 (pass array to varargs)
// Java is ALWAYS pass-by-value
public static void modify(int x) {
x = 100; // doesn't affect the caller's variable
}
int n = 5;
modify(n);
System.out.println(n); // still 5
// For objects, the reference is passed by value
public static void addItem(List<String> list) {
list.add("new"); // modifies the same list object
}Рекурсия
Рекурсия — когда метод вызывает сам себя. Всегда имейте базовый случай для остановки. Java не оптимизирует хвостовую рекурсию (в отличие от некоторых языков), поэтому глубокая рекурсия может вызвать StackOverflowError. Для performance-critical или глубокой рекурсии преобразуйте в итерацию. Мемоизация (кэширование результатов) может ускорить рекурсивные решения вроде Fibonacci.
// Factorial
public static int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
// factorial(5) = 5 * 4 * 3 * 2 * 1 = 120
// Fibonacci
public static int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
// Tail recursion (Java doesn't optimize this)
public static int factorialTail(int n, int acc) {
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc);
}
// Call: factorialTail(5, 1)
// Be careful: deep recursion causes StackOverflowError
// For deep recursion, use iteration or a loop insteadLambda-выражения
Lambda (Java 8+) — анонимные функции. Тип — функциональный интерфейс (один абстрактный метод). Распространённые: Function<T,R> (вход→выход), Predicate<T> (boolean-проверка), Consumer<T> (потребление, без возврата), Supplier<T> (производство, без входа). Ссылки на методы (String::length) — сокращение для lambda, вызывающих один метод.
import java.util.function.*;
// Lambda syntax: (params) -> expression
Function<Integer, Integer> square = x -> x * x;
Function<String, Integer> length = s -> s.length();
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
// With type annotations
BinaryOperator<Integer> multiply = (Integer a, Integer b) -> a * b;
// Multi-line lambda
Function<String, String> process = s -> {
String upper = s.toUpperCase();
return upper.substring(0, 3);
};
// Predicate (boolean test)
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> isEmpty = String::isEmpty; // method reference
// Consumer (no return)
Consumer<String> printer = s -> System.out.println(s);
Consumer<String> printer2 = System.out::println; // method reference
// Supplier (no input, produces value)
Supplier<Double> random = () -> Math.random();
// Usage
int result = square.apply(5); // 25
boolean even = isEven.test(4); // true
printer.accept("Hello"); // prints "Hello"Классы и ООП
Класс и конструктор
Классы — шаблоны для объектов. Поля хранят состояние, методы определяют поведение. Конструкторы инициализируют новые объекты (используйте 'this' для различения полей и параметров). @Override указывает, что метод переопределяет метод суперкласса (toString из Object). Инкапсуляция: приватные поля, публичные геттеры/сеттеры.
public class Person {
// Fields (instance variables)
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name; // 'this' refers to the current instance
this.age = age;
}
// Methods
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age >= 0) this.age = age;
}
public String greet() {
return "Hi, I'm " + name;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
// Usage
Person p = new Person("Alice", 30);
System.out.println(p.getName()); // "Alice"
System.out.println(p); // uses toString()Модификаторы доступа и static
Модификаторы доступа: public (везде), private (только класс), protected (класс + подклассы + пакет), default/package-private (тот же пакет). Static-члены принадлежат классу, не экземплярам — разделяются всеми объектами. Static-инициализаторы запускаются один раз при загрузке класса. Используйте static для констант (static final), utility-методов и счётчиков.
public class BankAccount {
// Access modifiers:
public String owner; // accessible everywhere
private double balance; // class only
protected String type; // class + subclasses + same package
String id; // package-private (default)
// Static field (shared by all instances)
private static int accountCount = 0;
// Static constant
public static final double MIN_BALANCE = 100.0;
// Static method (call without instance)
public static int getAccountCount() {
return accountCount;
}
// Static initializer (runs once when class loads)
static {
System.out.println("BankAccount class loaded");
}
public BankAccount(String owner) {
this.owner = owner;
this.balance = MIN_BALANCE;
accountCount++; // increment shared counter
}
}
int count = BankAccount.getAccountCount(); // static method callНаследование и super
Java использует 'extends' для наследования классов (только одиночное). super() вызывает конструктор родителя (должен быть первой строкой). @Override указывает переопределение метода (runtime-полиморфизм). Dog IS-A Animal. Используйте наследование для отношений 'is-a'; композицию (has-a) — для переиспользования кода. Java 17+ поддерживает sealed-классы для ограничения наследования.
// Parent class
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
System.out.println("Animal constructor");
}
public void eat() {
System.out.println(name + " is eating");
}
}
// Child class (extends)
class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // must be first line — call parent constructor
this.breed = breed;
}
// Override parent method
@Override
public void eat() {
super.eat(); // call parent's eat()
System.out.println(name + " the " + breed + " eats dog food");
}
public void bark() {
System.out.println("Woof!");
}
}
Dog dog = new Dog("Rex", "Labrador");
dog.eat(); // calls Dog's eat()
dog.bark(); // Dog-specific methodАбстрактные классы и интерфейсы
Абстрактные классы нельзя создать и они могут иметь как абстрактные (без тела), так и конкретные методы. Интерфейсы определяют контракты — все методы public abstract по умолчанию. Java 8+ разрешает default-методы (с телом) и static-методы в интерфейсах. Класс расширяет один абстрактный класс, но может реализовать несколько интерфейсов. Используйте абстрактные классы для общего кода, интерфейсы — для контрактов.
// Abstract class (can't be instantiated)
abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// Abstract method (must be implemented by subclasses)
public abstract double area();
// Concrete method (inherited)
public String describe() {
return color + " " + this.getClass().getSimpleName();
}
}
// Interface (pure contract, Java 8+ can have default methods)
interface Drawable {
void draw(); // abstract by default
// Default method (Java 8+)
default void drawTwice() {
draw();
draw();
}
// Static method in interface
static Drawable empty() {
return () -> System.out.println("nothing");
}
}
// A class can extend one class and implement multiple interfaces
class Circle extends Shape implements Drawable {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("Drawing " + describe());
}
}Полиморфизм и приведение
Полиморфизм: ссылка на родителя может содержать объект-наследник. Вызовы методов диспетчеризуются к реализации фактического объекта (runtime-полиморфизм). Используйте instanceof перед downcast'ом, чтобы избежать ClassCastException. Java 16+ pattern matching (instanceof Circle c) объединяет проверку и приведение. Переопределяйте equals() и hashCode() вместе для корректного поведения в коллекциях.
// Polymorphism: one interface, many forms
Shape s1 = new Circle("red", 5);
Shape s2 = new Square("blue", 3);
// Calls the overridden method (runtime dispatch)
System.out.println(s1.area()); // Circle's area
System.out.println(s2.area()); // Square's area
// instanceof check
if (s1 instanceof Circle) {
Circle c = (Circle) s1; // downcast
System.out.println("Radius: " + c.radius);
}
// Pattern matching (Java 16+)
if (s1 instanceof Circle c) {
System.out.println("Radius: " + c.radius); // c is already cast
}
// Upcasting (automatic)
Circle circle = new Circle("green", 2);
Shape shape = circle; // upcast (no explicit cast needed)
// Object class methods (all classes inherit from Object)
circle.equals(circle); // reference equality by default
circle.hashCode(); // hash code
circle.getClass(); // Class<Circle>
circle.toString(); // string representationRecords и Enums
Records (Java 16+) — неизменяемые классы данных — компилятор генерирует конструктор, геттеры, equals, hashCode и toString. Используйте для DTO и value-объектов. Enums — типобезопасные константы, которые могут иметь поля, методы и конструкторы. Enums реализуют Comparable и имеют методы values() и valueOf(). Оба существенны для современного Java.
// Record (Java 16+): concise data class
public record Point(int x, int y) {}
// Equivalent to a class with:
// - final fields x, y
// - constructor
// - getters x(), y()
// - equals, hashCode, toString
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p.y()); // 4
System.out.println(p); // Point[x=3, y=4]
// Compact constructor (validation)
public record Age(int value) {
public Age {
if (value < 0 || value > 150) {
throw new IllegalArgumentException("Invalid age");
}
}
}
// Enum (named constants)
public enum Direction {
UP, DOWN, LEFT, RIGHT;
public Direction opposite() {
return switch (this) {
case UP -> DOWN;
case DOWN -> UP;
case LEFT -> RIGHT;
case RIGHT -> LEFT;
};
}
}
Direction d = Direction.UP;
Direction opp = d.opposite(); // DOWNКоллекции и дженерики
List (ArrayList и LinkedList)
ArrayList поддерживается массивом (быстрый get/set, медленные insert/delete в середине). LinkedList поддерживается двусвязным списком (быстрый insert/delete на концах, медленный случайный доступ). List.of() создаёт неизменяемые списки. Используйте ArrayList для большинства случаев; LinkedList только для частых операций на концах. Оба реализуют интерфейс List.
import java.util.*;
// ArrayList (fast random access, slow insert/delete in middle)
List<String> list = new ArrayList<>();
list.add("Alice");
list.add("Bob");
list.add(0, "Carol"); // insert at index
list.set(1, "Dave"); // replace at index
String name = list.get(0); // "Carol"
list.remove(0); // remove by index
list.remove("Dave"); // remove by value
int size = list.size(); // 1
boolean has = list.contains("Bob");
// LinkedList (fast insert/delete at ends)
LinkedList<Integer> linked = new LinkedList<>();
linked.addFirst(1);
linked.addLast(2);
linked.removeFirst();
linked.peek(); // see first element
// Immutable list (Java 9+)
List<String> immutable = List.of("a", "b", "c");
// immutable.add("d"); // UnsupportedOperationException
// Iterate
for (String s : list) {
System.out.println(s);
}
list.forEach(System.out::println); // method referenceSet (HashSet и TreeSet)
Set хранит уникальные элементы. HashSet самый быстрый, но неупорядоченный. TreeSet хранит элементы отсортированными (естественный порядок или Comparator). LinkedHashSet поддерживает порядок вставки. Операции над множествами: addAll (union), retainAll (intersection), removeAll (difference). Для пользовательских объектов в HashSet переопределяйте equals() и hashCode().
import java.util.*;
// HashSet (fast, unordered)
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // duplicate ignored
System.out.println(set.size()); // 2
System.out.println(set.contains("apple")); // true
set.remove("banana");
// TreeSet (sorted, slower)
Set<Integer> sorted = new TreeSet<>();
sorted.add(3);
sorted.add(1);
sorted.add(2);
System.out.println(sorted); // [1, 2, 3]
// LinkedHashSet (maintains insertion order)
Set<String> ordered = new LinkedHashSet<>();
ordered.add("c");
ordered.add("a");
ordered.add("b");
System.out.println(ordered); // [c, a, b]
// Set operations
Set<Integer> a = new HashSet<>(Set.of(1, 2, 3));
Set<Integer> b = new HashSet<>(Set.of(2, 3, 4));
a.addAll(b); // union: [1, 2, 3, 4]
a.retainAll(b); // intersection: [2, 3]
a.removeAll(b); // difference: [1]
// Immutable set
Set<String> immutable = Set.of("x", "y", "z");Map (HashMap и TreeMap)
Map хранит пары ключ-значение. HashMap самый быстрый (неупорядоченный). TreeMap сортирует по ключам. LinkedHashMap поддерживает порядок вставки. getOrDefault избегает проверок на null. compute/merge мощны для обновления значений. Для пользовательских ключей переопределяйте equals() и hashCode(). Map.of() создаёт неизменяемые map (Java 9+).
import java.util.*;
// HashMap (fast, unordered)
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
ages.put("Alice", 31); // overwrite
// Access
int age = ages.get("Alice"); // 31
int defaultAge = ages.getOrDefault("Eve", 0); // 0
// Check
boolean has = ages.containsKey("Alice");
boolean hasVal = ages.containsValue(25);
// Remove
ages.remove("Bob");
// Iterate
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
ages.forEach((key, val) -> System.out.println(key + "=" + val));
// Compute (Java 8+)
ages.compute("Alice", (k, v) -> v + 1); // increment
ages.putIfAbsent("Carol", 28);
ages.merge("Alice", 1, Integer::sum); // add 1
// TreeMap (sorted by keys)
Map<String, Integer> sorted = new TreeMap<>();
// LinkedHashMap (maintains insertion order)
Map<String, Integer> ordered = new LinkedHashMap<>();Queue и Deque
Queue — FIFO (first-in-first-out). Deque — двусторонняя (можно добавлять/удалять с обоих концов). PriorityQueue упорядочивает элементы по естественному порядку или Comparator (min-heap по умолчанию). Для стека используйте ArrayDeque (push/pop) вместо устаревшего класса Stack. ArrayDeque быстрее LinkedList для операций queue/deque.
import java.util.*;
// Queue (FIFO)
Queue<String> queue = new LinkedList<>();
queue.add("first"); // throws if full (capacity-restricted)
queue.offer("second"); // returns false if full
String head = queue.peek(); // see head (null if empty)
String removed = queue.poll(); // remove and return head
// Deque (double-ended)
Deque<Integer> deque = new ArrayDeque<>();
deque.addFirst(1);
deque.addLast(2);
deque.peekFirst(); // 1
deque.peekLast(); // 2
deque.pollFirst(); // 1
deque.pollLast(); // 2
// PriorityQueue (min-heap by default)
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(3);
pq.add(1);
pq.add(2);
System.out.println(pq.poll()); // 1 (smallest first)
// Max-heap (reverse order)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
maxHeap.add(1);
maxHeap.add(3);
System.out.println(maxHeap.poll()); // 3 (largest first)
// Stack (legacy, prefer Deque)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // add to front
stack.push(2);
stack.pop(); // 2 (remove from front)Дженерики
Дженерики обеспечивают типобезопасные коллекции и классы. <T> — параметр типа. Ограниченные типы (<T extends Comparable<T>>) ограничивают типами с определённым поведением. Wildcards: ? (любой), ? extends T (ковариантный, только чтение), ? super T (контравариантный, только запись). Дженерики используют type erasure — типы проверяются при компиляции, стираются в runtime.
// Generic class
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> stringBox = new Box<>();
stringBox.set("hello");
String s = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(42);
// Generic method
public static <T> T firstOf(List<T> list) {
return list.get(0);
}
String first = firstOf(List.of("a", "b"));
// Bounded type parameter
public static <T extends Comparable<T>> T max(List<T> list) {
T result = list.get(0);
for (T item : list) {
if (item.compareTo(result) > 0) {
result = item;
}
}
return result;
}
// Wildcards
void process(List<?> list) { } // any type
void processNums(List<? extends Number> list) { } // Number or subclass
void addNums(List<? super Integer> list) { } // Integer or superclassИтераторы и Comparable
Iterator позволяет безопасное удаление во время итерации (it.remove()). ListIterator добавляет двунаправленный обход и set/add. Comparable определяет естественный порядок (compareTo). Comparator определяет пользовательский порядок (comparing, comparingInt, reversed, thenComparing). Используйте Comparator.comparing() для fluent-сортировки. Collections.sort() использует естественный порядок.
import java.util.*;
// Iterator
List<String> list = List.of("a", "b", "c");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
// it.remove(); // safe removal during iteration
}
// ListIterator (bidirectional)
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
lit.set(lit.next().toUpperCase()); // replace
}
// Comparable (natural ordering)
public class Person implements Comparable<Person> {
String name;
int age;
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
}
// Comparator (custom ordering)
Comparator<Person> byName = Comparator.comparing(p -> p.name);
Comparator<Person> byAgeDesc = Comparator.comparingInt((Person p) -> p.age).reversed();
List<Person> people = new ArrayList<>();
people.sort(byName);
people.sort(byAgeDesc);
Collections.sort(people); // uses ComparableStreams и функциональное
Основы Stream
Streams (Java 8+) обеспечивают декларативную обработку данных. Создание через .stream() (коллекции) или Stream.of(). Промежуточные операции (filter, map, sorted) ленивые — они выполняются только при вызове терминальной операции (collect, reduce, count, forEach). toList() (Java 16+) — лаконичная альтернатива collect(Collectors.toList()).
import java.util.*;
import java.util.stream.*;
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
// Filter and collect
List<Integer> evens = nums.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // [2, 4, 6]
// Map (transform)
List<String> doubled = nums.stream()
.map(n -> "num" + n)
.collect(Collectors.toList());
// Reduce
int sum = nums.stream().reduce(0, Integer::sum); // 21
int product = nums.stream().reduce(1, (a, b) -> a * b);
// Count
long count = nums.stream().filter(n -> n > 3).count(); // 3
// Find
Optional<Integer> first = nums.stream().filter(n -> n > 3).findFirst();
boolean anyMatch = nums.stream().anyMatch(n -> n > 5);
boolean allMatch = nums.stream().allMatch(n -> n > 0);
// ForEach
nums.stream().forEach(System.out::println);
// toList() shortcut (Java 16+)
List<Integer> result = nums.stream().filter(n -> n > 3).toList();Операции Stream
sorted() упорядочивает элементы (естественный или с Comparator). distinct() удаляет дубликаты. limit(n)/skip(n) пагинируют. flatMap выравнивает вложенные streams — существенен для one-to-many преобразований. peek() — для отладки (побочные эффекты). groupingBy создаёт map, группируя элементы по классификатору. Streams ленивы — операции цепляются эффективно.
List<String> names = List.of("Alice", "Bob", "Charlie", "David");
// Sorted
List<String> sorted = names.stream()
.sorted()
.toList();
// Sorted by length
List<String> byLength = names.stream()
.sorted(Comparator.comparing(String::length))
.toList();
// Distinct
List<Integer> distinct = List.of(1, 2, 2, 3, 3, 3).stream()
.distinct()
.toList(); // [1, 2, 3]
// Limit and Skip
List<Integer> limited = nums.stream()
.skip(2) // skip first 2
.limit(3) // take next 3
.toList();
// FlatMap (flatten nested structures)
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.toList(); // [1, 2, 3, 4]
// Peek (debug, side-effect)
nums.stream()
.peek(n -> System.out.println("before: " + n))
.filter(n -> n > 2)
.peek(n -> System.out.println("after: " + n))
.toList();
// Grouping
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));Collectors и редукция
Collectors предоставляют богатые операции редукции: joining (конкатенация строк), groupingBy (группировка по ключу), partitioningBy (разделение по boolean), toMap (создание map), summarizingInt (статистика: count, sum, min, max, average). Collectors компонуются (groupingBy с downstream collector). Они заменяют многословные циклы декларативными one-liner'ами.
import java.util.stream.*;
List<Person> people = List.of(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
// Join strings
String joined = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", ")); // "Alice, Bob, Charlie"
// Group by
Map<Integer, List<Person>> byAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
// Partition (boolean)
Map<Boolean, List<Person>> partition = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() > 28));
// Count by group
Map<Integer, Long> countByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
// Summarizing
IntSummaryStatistics stats = people.stream()
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println(stats.getAverage()); // 30.0
System.out.println(stats.getMax()); // 35
// To map
Map<String, Integer> nameToAge = people.stream()
.collect(Collectors.toMap(Person::getName, Person::getAge));
// Reducing
int totalAge = people.stream()
.collect(Collectors.reducing(0, Person::getAge, Integer::sum));Optional
Optional<T> — контейнер, который может или не может содержать значение. Он форсирует явную обработку отсутствия — больше никакого NullPointerException. Используйте of() для non-null значений, ofNullable() для возможно-null. Цепочечный через map/flatMap/filter. Никогда не используйте get() без isPresent() — предпочитайте orElse/orElseThrow. Optional разработан для возвращаемых типов, не полей.
import java.util.Optional;
// Creating Optional
Optional<String> present = Optional.of("hello");
Optional<String> empty = Optional.empty();
Optional<String> nullable = Optional.ofNullable(null); // empty if null
// Checking
present.isPresent(); // true
empty.isEmpty(); // true (Java 11+)
// Getting values
String val = present.get(); // throws if empty (avoid!)
String safe = present.orElse("default");
String computed = present.orElseGet(() -> computeDefault());
String orThrow = present.orElseThrow(() -> new RuntimeException("missing"));
// Transform (map/flatMap)
Optional<Integer> length = present.map(String::length); // Optional[5]
Optional<String> upper = present.map(s -> s.toUpperCase());
// Filter
Optional<String> filtered = present.filter(s -> s.length() > 3);
// ifPresent
present.ifPresent(s -> System.out.println(s));
present.ifPresentOrElse(
s -> System.out.println("Got: " + s),
() -> System.out.println("Empty")
);
// Chaining (avoid null checks)
String result = getUser(1)
.map(User::getProfile)
.map(Profile::getEmail)
.orElse("no email");Функциональные интерфейсы
Функциональные интерфейсы имеют ровно один абстрактный метод (могут иметь несколько default-методов). @FunctionalInterface опционален, но документирует намерение. Java предоставляет множество в java.util.function: Function, Predicate, Consumer, Supplier, плюс Bi- и примитивные варианты. Используйте их вместо создания пользовательских интерфейсов, когда возможно. Они обеспечивают lambda-выражения и ссылки на методы.
import java.util.function.*;
// Built-in functional interfaces
Function<String, Integer> strToInt = Integer::parseInt;
BiFunction<String, String, String> concat = String::concat;
Predicate<String> isEmpty = String::isEmpty;
BiPredicate<String, String> contains = String::contains;
Consumer<String> printer = System.out::println;
BiConsumer<String, Integer> printPair = (s, i) -> System.out.println(s + ":" + i);
Supplier<List<String>> listFactory = ArrayList::new;
// Primitive specializations
IntFunction<String> intToStr = String::valueOf;
ToIntFunction<String> length = String::length;
IntPredicate isPositive = n -> n > 0;
IntConsumer intPrinter = System.out::println;
IntSupplier randomInt = () -> (int)(Math.random() * 100);
// Binary operators
BinaryOperator<Integer> max = Integer::max;
IntBinaryOperator sum = Integer::sum;
// Unary operators
UnaryOperator<String> trim = String::trim;
IntUnaryOperator negate = n -> -n;
// Custom functional interface
@FunctionalInterface
interface StringProcessor {
String process(String input);
// Can have default methods
default StringProcessor andThen(StringProcessor after) {
return input -> after.process(process(input));
}
}Исключения и I/O
Try / Catch / Finally
try/catch/finally обрабатывает исключения. finally всегда выполняется (используйте для очистки). Multi-catch (catch A | B) обрабатывает несколько исключений вместе. Try-with-resources авто-закрывает любой AutoCloseable (файлы, соединения, потоки) — предпочтительнее ручной очистки в finally. Ресурсы закрываются в обратном порядке объявления.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Math error: " + e.getMessage());
} catch (Exception e) {
System.out.println("General error: " + e);
} finally {
// Always runs (even if return/throw in try/catch)
System.out.println("Cleanup");
}
// Multi-catch (Java 7+)
try {
riskyOperation();
} catch (IOException | SQLException e) {
// Handle both exceptions the same way
log.error(e);
}
// Try-with-resources (auto-close, Java 7+)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"));
PrintWriter pw = new PrintWriter("output.txt")) {
String line = br.readLine();
pw.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// br and pw are auto-closed (in reverse order)Checked vs Unchecked
Checked-исключения (extends Exception) должны быть объявлены через 'throws' или пойманы — компилятор это требует. Используйте для восстановимых условий (файл не найден, сетевая ошибка). Unchecked-исключения (extends RuntimeException) не требуют объявления — используйте для ошибок программирования (null pointer, неверный аргумент). Дебаты: checked-исключения форсируют обработку, но могут загромождать код; многие фреймворки предпочитают unchecked.
// Checked exceptions (must be declared or caught)
public void readFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
// IOException is checked — compiler enforces handling
}
// Unchecked exceptions (RuntimeException, no need to declare)
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Divisor cannot be zero");
// RuntimeException — no 'throws' needed
}
return a / b;
}
// Common checked exceptions:
// IOException, SQLException, ClassNotFoundException
// Common unchecked exceptions:
// NullPointerException, IllegalArgumentException,
// IndexOutOfBoundsException, ArithmeticException,
// ClassCastException, IllegalStateException
// Custom checked exception
class DataException extends Exception {
public DataException(String msg) { super(msg); }
}
// Custom unchecked exception
class ValidationException extends RuntimeException {
public ValidationException(String msg) { super(msg); }
}Файловый I/O (NIO.2)
NIO.2 (java.nio.file) — современный файловый API. Files.readString/writeString (Java 11+) удобны для текста. Files.lines() возвращает ленивый Stream — эффективен для больших файлов (нужно закрыть через try-with-resources). Path.of() заменяет старый класс File. Files.createDirectories() создаёт полный путь. Всегда обрабатывайте IOException.
import java.nio.file.*;
import java.io.*;
// Read entire file (small files)
List<String> lines = Files.readAllLines(Path.of("input.txt"));
String content = Files.readString(Path.of("config.json")); // Java 11+
byte[] bytes = Files.readAllBytes(Path.of("image.png"));
// Write file
Files.writeString(Path.of("output.txt"), "Hello, World!");
Files.write(Path.of("data.bin"), bytes);
// Append
Files.writeString(Path.of("log.txt"), "entry\n",
StandardOpenOption.APPEND, StandardOpenOption.CREATE);
// Stream lines (large files, lazy)
try (Stream<String> lineStream = Files.lines(Path.of("large.txt"))) {
lineStream.filter(l -> l.contains("ERROR"))
.forEach(System.out::println);
}
// Copy, move, delete
Files.copy(Path.of("src.txt"), Path.of("dest.txt"));
Files.move(Path.of("old.txt"), Path.of("new.txt"));
Files.delete(Path.of("temp.txt"));
// Check existence
boolean exists = Files.exists(Path.of("file.txt"));
// Create directories
Files.createDirectories(Path.of("a/b/c"));Reader и Writer (текст)
BufferedReader/Writer эффективны для текстового I/O (буферизация уменьшает системные вызовы). PrintWriter предлагает printf-стиль форматирования. Scanner парсит ввод (nextInt, nextDouble, nextLine). InputStreamReader мостит байтовые потоки к символьным (укажите charset для non-UTF-8). Всегда используйте try-with-resources для гарантии закрытия потоков.
import java.io.*;
import java.nio.file.*;
// BufferedReader (efficient text reading)
try (BufferedReader br = Files.newBufferedReader(Path.of("input.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// BufferedWriter (efficient text writing)
try (BufferedWriter bw = Files.newBufferedWriter(Path.of("output.txt"))) {
bw.write("First line");
bw.newLine();
bw.write("Second line");
}
// PrintWriter (convenient formatting)
try (PrintWriter pw = new PrintWriter("output.txt")) {
pw.println("Hello");
pw.printf("Name: %s, Age: %d%n", "Alice", 30);
}
// Scanner (parsing input)
try (Scanner sc = new Scanner(Path.of("data.txt"))) {
while (sc.hasNextLine()) {
String line = sc.nextLine();
// Parse tokens
Scanner lineSc = new Scanner(line);
if (lineSc.hasNextInt()) {
int n = lineSc.nextInt();
}
}
}
// InputStreamReader (bytes to chars, e.g., from InputStream)
Reader reader = new InputStreamReader(System.in);Дата и время (java.time)
java.time (Java 8+) — современный API даты/времени, заменяющий старые Date/Calendar. LocalDate (только дата), LocalTime (только время), LocalDateTime (оба), ZonedDateTime (с часовым поясом). Все неизменяемые и потокобезопасные. Используйте Period для разницы дат, Duration для разницы времени. DateTimeFormatter для парсинга/форматирования. Instant для машинных временных меток (UTC).
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
// Current date/time
LocalDate today = LocalDate.now(); // 2024-01-15
LocalTime now = LocalTime.now(); // 14:30:45.123
LocalDateTime dt = LocalDateTime.now(); // both
ZonedDateTime zdt = ZonedDateTime.now(); // with timezone
// Create specific
LocalDate date = LocalDate.of(2024, 1, 15);
LocalTime time = LocalTime.of(14, 30, 0);
LocalDateTime specific = LocalDateTime.of(2024, 1, 15, 14, 30);
// Parsing and formatting
LocalDate parsed = LocalDate.parse("2024-01-15");
String formatted = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
// "15/01/2024"
// Manipulation (immutable, returns new)
LocalDate tomorrow = today.plusDays(1);
LocalDate lastMonth = today.minusMonths(1);
LocalDate nextYear = today.plusYears(1);
// Period (date-based)
Period age = Period.between(LocalDate.of(1990, 1, 1), today);
System.out.println(age.getYears()); // 34
// Duration (time-based)
Duration dur = Duration.between(time, LocalTime.now());
System.out.println(dur.toMinutes());
// Instant (machine time, UTC)
Instant instant = Instant.now();
Instant epoch = Instant.ofEpochSecond(0);Основы конкурентности
Конкурентность Java: Thread (низкоуровневый), ExecutorService (пулы потоков — предпочтительно), CompletableFuture (async-композиция, как Promises). parallelStream() использует ForkJoinPool для параллельной обработки. synchronized-блоки защищают разделяемое состояние. Atomic-переменные (AtomicInteger и т.д.) обеспечивают lock-free потокобезопасные операции. Для сложной конкурентности используйте коллекции java.util.concurrent (ConcurrentHashMap, BlockingQueue).
import java.util.concurrent.*;
// Create a thread
Thread thread = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
thread.start();
thread.join(); // wait for completion
// ExecutorService (thread pool)
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
Integer result = future.get(); // blocks until done
executor.shutdown();
// CompletableFuture (async, Java 8+)
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
String asyncResult = cf.join(); // "HELLO WORLD"
// Parallel stream
List<Integer> nums = List.of(1, 2, 3, 4, 5);
int sum = nums.parallelStream().mapToInt(Integer::intValue).sum();
// Synchronized
synchronized (this) {
// only one thread at a time
}
// Atomic variables
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.compareAndSet(0, 1);Lambda-выражения
Основы синтаксиса Lambda
Lambda (Java 8+) — лаконичные реализации функциональных интерфейсов. Синтаксис: (params) -> выражение или (params) -> { инструкции; }. Компилятор выводит типы параметров из целевого типа. Lambda с одним параметром может опустить скобки; с нулём параметров нужны пустые скобки. Lambda обеспечивает функциональное программирование и является основой Streams API.
// Anonymous class (verbose, pre-Java 8)
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Old way");
}
};
// Lambda expression (Java 8+)
Runnable r2 = () -> System.out.println("Lambda");
// With parameters and body
Comparator<Integer> cmp = (a, b) -> {
int diff = a - b;
return diff;
};
// Type inference (omit types)
Comparator<Integer> cmp2 = (a, b) -> a - b;
// Single param, no parentheses needed
Consumer<String> printer = s -> System.out.println(s);
// Zero params need empty parens
Runnable noop = () -> {};Функциональные интерфейсы
Функциональный интерфейс имеет ровно один абстрактный метод (SAM type). Аннотация @FunctionalInterface заставляет компилятор это требовать. Lambda могут нацеливаться только на функциональные интерфейсы. Default- и static-методы разрешены и не нарушают правило одного метода. Это основа, делающая lambda работающими в системе типов Java.
// Functional interface: exactly one abstract method
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
// Implement with lambda
MathOperation add = (a, b) -> a + b;
MathOperation mul = (a, b) -> a * b;
MathOperation max = (a, b) -> Math.max(a, b);
int result = add.operate(3, 4); // 7
int m = mul.operate(3, 4); // 12
// @FunctionalInterface is optional but recommended
// It prevents accidentally adding a second abstract method
// Default and static methods don't count toward the limit
interface StringProcessor {
String process(String s);
default StringProcessor andThen(StringProcessor next) {
return s -> next.process(this.process(s));
}
}Встроенные функциональные интерфейсы
java.util.function предоставляет ~40 готовых функциональных интерфейсов, поэтому редко нужно писать свои. Основная четвёрка: Function (преобразование), Predicate (проверка), Consumer (потребление), Supplier (производство). Bi- варианты принимают два аргумента. Примитивные варианты (IntFunction, ToIntFunction и т.д.) избегают накладных расходов autoboxing. Используйте их вместо создания пользовательских интерфейсов.