class ClassName
{
field1
field2
. . .
constructor1
constructor2
. . .
method1
method2
. . .
}@inponomarev
Иван Пономарёв, КУРС/МФТИ

Любой код — метод некоторого класса
Любые данные хранятся в полях некоторого класса
Любые типы данных (исключая примитивные, но включая массивы) — наследники класса Object
Как мы помним, классы находятся в пакетах.
class ClassName
{
field1
field2
. . .
constructor1
constructor2
. . .
method1
method2
. . .
}package org.megacompany.staff;
class Employee {
// instance fields
private String name;
private int salary;
private LocalDate hireDay;
// constructor
public Employee(String n, int s, int year, int month, int day) {
name = n; salary = s;
hireDay = LocalDate.of(year, month, day);
}
// a method
public String getName() {
return name; }
// more methods
. . .
}//If necessary, we can import a class from another package
import org.megacompany.staff.Employee;
//somewhere in the body of the method
. . .
Employee hacker = new Employee("Harry Hacker", 50000, 1989, 10, 1);
Employee tester = new Employee("Tommy Tester", 40000, 1990, 3, 15);
hacker.getName(); //returns "Harry Hacker"Unlike local variables, fields can miss explicit initialization.
In this case, primitive types are set to a default value (0, false), and fields with references are set to null.
Проинициализировать поле по месту его определения не возбраняется:
int a = 42 или даже int a = getValue().
this field{ ...
int value;
setValue(int value) {
// the field is hidden by an argument
this.value = value;
}
registerMe(Registrator r) {
//нужна ссылка на себя
r.register(this);
}
}@AllArgsConstructor
class Employee { int id; String name;}
new Employee(42, "Bob")
Employee hacker = new Employee(42, "Bob");
Employee junior = hacker;
hacker = null;
junior = new Employee(43, "Charlie");

| ![]() |
NB: "аллокция на стеке" — это умозрительная картина. Оптимизатор волен делать что угодно, если семантика позволяет, как для обычных, так и для | ![]() |
Scope | Visibility |
| class only |
package-private | package only (default) |
| class, package, and descendant classes |
| everywhere |
In a .java file, there can be only one public class, named the same as the .java file ('public class Foo' in the 'Foo.java' file).
There can be as many package-private classes as you like, but this is rather a bad practice.

public class Manager extends Employee {
private int bonus;
. . .
public void setBonus(int bonus) {
this.bonus = bonus;
}
}// construct a Manager object
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);
Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());

Executive ex = new Executive (...);
// members declared in Manager, Employee, and Executive are available for ex
Manager m = ex;
// members declared in Employee and Manager are available for m
Employee e = m;
// members declared in Employee only are available for mclass Employee {
private int salary;
public int getSalary() {
return salary;
}
public int getTotalPayout(){
return getSalary();
}
}
class Manager extends Employee {
private int bonus;
@Override //this is not mandatory, but highly desirable thing
public int getTotalPayout() {
return getSalary() + bonus;
}
}superclass Manager extends Employee {
private int bonus;
@Override
public int getTotalPayout() {
return super.getTotalPayout() + bonus;
}
}Unlike this, super does not point to any object (and cannot be passed as an argument). It just tells the compiler to call the superclass method.

The return type can be of the same type or subtype
Argument types must match
final classes and methodsКлючевое слово final:
на уровне класса запрещает наследование класса
на уровне метода запрещает наследование метода
Зачем это нужно?
Паттерн "Шаблонный метод"
J. Bloch: 'Design and document for inheritance, or else prohibit it'
sealed types (Java 15+)A sealed class be subclassed, but only by those who explicitly allowed to:
public sealed class Pet
permits
//никакие другие не могут наследоваться от него
Cat, Dog, Fish {
}
public final Cat {...}
public sealed Dog permits Hound, Terrier, Toy {...}
public non-sealed Fish {...}sealed interfaces and `record`sПока не знаем ни что такое interface, ни что такое record, но просто запомним:
public sealed interface Expr
permits Add, Subtract, Multiply, Divide, Literal {}
//implicitly final!
public record Add(Expr left, Expr right) implements Expr {}
public record Subtract(Expr left, Expr right) implements Expr {}
public record Multiply(Expr left, Expr right) implements Expr {}
public record Divide(Expr left, Expr right) implements Expr {}
public record Literal(int value) implements Expr {}Method signature is defined by its name and argument types:
//org.junit.jupiter.api.Assertions
void assertEquals(short expected, short actual)
void assertEquals(short expected, short actual, String message)
void assertEquals(short expected, short actual, Supplier<String> messageSupplier)
void assertEquals(byte expected, byte actual)
void assertEquals(byte expected, byte actual, String message)
void assertEquals(byte expected, byte actual, Supplier<String> messageSupplier)
void assertEquals(int expected, int actual)
void assertEquals(int expected, int actual, String message)
void assertEquals(int expected, int actual, Supplier<String> messageSupplier)
. . .Data common to all instances of the class:
class Employee {
/*WARNING: This example
does not work in multi-threaded environment*/
private static int nextId = 1;
private int id;
. . .
public void setId() {
id = nextId;
nextId++;
}
}Allocate memory once
public class Math {
. . .
public static final double PI = 3.14159265358979323846;
. . .
}
. . .
Math.PI // returns 3.14...Only static variables and other static methods are available to static methods
class Employee {
private static int nextId = 1;
private int id;
. . .
public static int getNextId() {
return nextId; // returns static field
}
}
. . .
Employee.nextId() //class name instead of objectNow we understand: the main method is available everywhere and does not require instantiation of the object:
public class App {
public static void main(String... args) {
System.out.println("Hello, world!");
}
}public class Person {
//public constructor without arguments
public Person() {
....
}
//package-private constructor with argument
Person(String name) {
....
}
}Конструктор обязан быть у каждого класса.
If we don’t 1) explicitly write a constructor, 2) the parent class has a constructor without arguments — then implicitly the class will have a public constructor with no default arguments.
If we explicitly wrote at least one constructor, the default constructor disappears.
If the parent class does not have a constructor without arguments, the default constructor is not created.
Constructor does not have to be public.
Если у суперкласса нет доступного конструктора без параметров, цепочка конструкторов подкласса должна явно вызвать super(…).
До Java 25: super(…) должен быть первым оператором конструктора.
Java 25+: перед super(…) можно выполнять код, который не использует создаваемый объект — например, проверяет и преобразовывает аргументы.
class Person {
Person(String name){
... }
}
class Employee extends Person{
Employee(String name) {
if (name.isBlank()) //Java 25+
throw new IllegalArgumentException("name is blank");
super(name);
... }
}this(…): вызов другого конструктора того же класса.
До Java 25: должен быть первым оператором.
Java 25+: перед ним можно выполнять код, который не использует создаваемый объект — например, проверяет и преобразовывает аргументы.
public class Person {
Person(String name){
...
}
Person() {
String name = System.getProperty(
"user.name", "unknown"); // Java 25+
this(name);
}
}class Employee {
private static int nextId;
private int id;
// static initialization block
static {
nextId = ThreadLocalRandom.current().nextInt(10000);
}
// object initialization block
{
id = nextId;
nextId++;
}
}There are no destructors!
Don’t even try to use 'finalize'
Why the finalize method was a bad idea?
class Outer {
int field = 42;
class Inner {
public void show() {
//есть доступ к состоянию внешнего класса!
System.out.println(field);
//печатает 42
}
}
void initInner(){
//инициализация вложенного класса внутри
new Inner();
}
}
//инициализация вложенного класса извне
Outer.Inner in = new Outer().new Inner();Each instance of Inner has an implicit reference to Outer.

class Outer {
int field = 42;
class Inner {
//поле вложенного класса перекрывает поле внешнего класса
int field = 18;
public void show() {
System.out.println(field);
//печатает 18
}
}
}class Outer {
int field = 42;
class Inner {
//поле вложенного класса перекрывает поле внешнего класса
int field = 18;
public void show() {
System.out.println(Outer.this.field);
//печатает 42!
}
}
}class Outer {
void outerMethod() {
//final (или effectively final) тут существенно
final int x = 98;
System.out.println("inside outerMethod");
class Inner {
void innerMethod() {
System.out.println("x = " + x);
}
}
Inner y = new Inner();
y.innerMethod();
}
}class Outer {
void outerMethod() {
//они не захватывают внешнее состояние
record Foo (int a, int b) {};
enum Bar {A, B};
interface Baz {};
//NB:
//static not allowed here!
static class X {};
}
}In fact, they are no different from just classes:
class Outer {
private static void outerMethod() {
System.out.println("inside outerMethod");
}
static class Inner {
public static void main(String[] args) {
System.out.println("inside inner class Method");
outerMethod();
}
}
}
. . .
Outer.Inner x = new Outer.Inner();
// в отличие от не статического: new Outer().new Inner();class Demo {
void show() {
System.out.println("superclass");
}
}
class Flavor1Demo {
public static void main(String[] args){
Demo d = new Demo() {
void show() {
super.show();
System.out.println("subclass");
}
};
d.show();
}
}Most often - as an implementation of abstract classes and interfaces "in place"
An anonymous class is a nested class, so before the introduction of lambdas and method references, this was the only way to organize a callback
. . .
button.onMouseClick(new EventListener(){
void onClick(Event e) {
//здесь у нас доступ ко всем внешним полям
//и effectively final-переменным
}
});
public abstract class Person
{
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract String getDescription();
}public class Student extends Person
{
private String major;
public Student(String name, String major) {
super(name);
this.major = major;
}
@Override
public String getDescription() {
return "a student majoring in " + major;
}
}A class in which at least one of the methods is not implemented must be marked as abstract
Unimplemented methods in a class occur in two ways:
explicitly declared as abstract
Inherited from abstract classes or interfaces and not overridden.
Abstract classes cannot be instantiated through new operator.
new Person('John Doe'); — compilation error: 'Person is abstract, cannot be instantiated'.
//его нельзя инстанцировать!
public interface Prism
{
//это --- final-переменная!
double PI = 3.14;
//these are public abstract methods!
double getArea();
double getHeight();
//this method can call other methods and read constants
default double getVolume() {
return getArea() * getHeight();
}
}public class Parallelepiped implements Prism {
private double a;
private double b;
private double h;
@Override
public double getArea() {
return a * b;
}
@Override
public double getHeight() {
return h;
}
}If any of the interface methods are not overridden, the class should be marked as abstract.
No internal state and constructors
You can inherit (via extends) from only one class, but implement (via implements) as many interfaces as you want — multiple inheritance.
Therefore, as an abstraction, the interface is preferred.
Enumeration Classes
Records
Annotation Interfaces (о них речь пойдёт значительно позже)
public enum Size
{ SMALL, MEDIUM, LARGE, EXTRA_LARGE };
. . .
Size s = Size.MEDIUM;
for (Size s: Size.values()) . . .
switch (s) {
case SMALL: . . .
case LARGE: . . .
}public enum Size
{
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
private final String abbreviation;
private Size(String abbreviation) {
this.abbreviation = abbreviation;
}
public String getAbbreviation() {
return abbreviation;
}
}
. . .
Size s = . . .
s.getAbbreviation(); // вернёт S, M, L или XLinterface Rule { boolean canGo(); }
enum TrafficLight implements Rule {
RED {
@Override public boolean canGo() { return false; }
@Override int durationSeconds() { return 55; }
},
YELLOW {
@Override public boolean canGo() { return false; }
@Override int durationSeconds() { return 5; }
},
GREEN {
@Override public boolean canGo() { return true; }
@Override int durationSeconds() { return 45; }
};
// Abstract method implemented individually by each constant
abstract int durationSeconds();
}Иммутабельные объекты (один раз создав, нельзя менять состояние)
Компактный синтаксис
Наследование запрещено, но можно реализовывать интерфейс
Автоматическая поддержка equals/hashCode (что это, мы узнаем вскоре)
Любой record может быть value-типом (value record).
//Defines a record with two int fields
public record Point(int x, int y) { }public record Point(int x, int y) {
public double distance(Point other) {
int dx = x - other.x;
int dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Point p1 = new Point(1, 2);
//Get value via implicit accessor method
System.out.println(p1.x());
System.out.println(p1.distance(new Point(3, 4)));value-объект не имеет identity: два экземпляра с одинаковым состоянием неразличимы.
Все поля экземпляра неявно final; JVM может хранить объект без дополнительной косвенности.
value можно объявить обычный класс или record.
Конкретный value-класс неявно final; наследование возможно через abstract value class.
| |
Минимизируйте область видимости (private всё, что только можно)
Минимизируйте мутабельность (final на всём, что только можно)
Документируйте точки расширения через наследование, или запрещайте наследование (final, sealed)