class ClassName
{
field1
field2
. . .
constructor1
constructor2
. . .
method1
method2
. . .
}
@inponomarev
Иван Пономарёв, КУРС/МФТИ
Любой код — метод некоторого класса
Любые данные хранятся в полях некоторого класса
Любые типы данных (исключая примитивные, но включая массивы) — наследники класса Object
edu.phystech.foo
edu.phystech.foo.bar
Каждый .java-файл начинается с объявления пакета:
package edu.phystech.hello;
В корне пакета может быть package-info.java
, не содержащий классы, а только JavaDoc над ключевым словом package
.
<Имя пакета>.<имя класса> задаёт полный идентификатор любого класса, доступного в исходном коде или через библиотеки (например, edu.phystech.hello.App
)
Вложенные пакеты — это разные пакеты с точки зрения Java (package-private классы одного пакета не будут видны в другом)
class ClassName
{
field1
field2
. . .
constructor1
constructor2
. . .
method1
method2
. . .
}
package org.megacompany.staff;
class Employee {
// instance fields
private String name;
private double salary;
private LocalDate hireDay;
// constructor
public Employee(String n, double 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
. . .
}
//При необходимости, импортируем
import org.megacompany.staff.Employee;
//где-то в теле метода
. . .
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"
В отличие от локальных переменных, поля можно не инициализировать явно.
В этом случае примитивные типы получают значение по умолчанию (0
, false
), а поля со ссылками — значение null
.
Проинициализировать поле по месту его определения не возбраняется:
int a = 42
или даже int a = getValue()
.
{ ...
int value;
setValue(int value) {
//поле перекрыто аргументом
this.value = value;
}
registerMe(Registrator r) {
//нужна ссылка на себя
r.register(this);
}
}
public class Employee {
int age = 18;
public static void main(String[] args) {
Employee e = new Employee();
int a = 1;
foo(e, a);
System.out.printf("%d - %d", e.age, a);
//prints 42 - 1
}
static void foo(Employee e, int a) {
//e passed by reference, a passed by value
e.age = 42;
a = 5;
}
}
new Employee("Bob")
Employee hacker = new Employee("Bob");
Employee junior = hacker;
hacker = null;
junior = new Employee("Charlie");
Область видимости | Кому доступно |
| только классу |
package-private | только пакету (по умолчанию) |
| классу, пакету, и классам-наследникам |
| всем |
В одном .java файле может быть один публичный класс, названный так же, как и .java-файл (public class Foo
в файле Foo.java
).
Может быть сколько угодно package-private-классов, но это скорее плохая практика.
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 200 class Employee { - name - salary + getName() + getSalary() } class Manager { - bonus + setBonus(bonus) } Employee <|- Manager @enduml
public class Manager extends Employee {
private double bonus;
. . .
public void setBonus(double 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());
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 200 Employee <|-- Manager Employee <|-- Secretary Employee <|-- Programmer Manager <|-- Executive @enduml
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 200 Employee <|- Manager Manager <|- Executive @enduml
Executive ex = new Executive (...);
//для ex доступны члены, объявленные в Manager, Employee и Executive
Manager m = ex;
//для m доступны члены, объявленные в Employee и Manager
Employee e = m;
//для e доступны члены, объявленные только в Employee
class Employee {
private int salary;
public int getSalary() {
return salary;
}
public int getTotalPayout(){
return getSalary();
}
}
class Manager extends Employee {
private int bonus;
@Override //не обязательная, но крайне желательная штука
public int getTotalPayout() {
return getSalary() + bonus;
}
}
super
class Manager extends Employee {
private int bonus;
@Override
public int getTotalPayout() {
return super.getTotalPayout() + bonus;
}
}
В отличие от this
, super
не указывает ни на какой объект (и его нельзя никуда передать). Это лишь указание компилитору вызвать метод суперкласса.
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 200 class Producer { + Object produce() } Producer <|-- IntegerProducer class IntegerProducer { + Integer produce() } Object <|-- Integer Producer . Object IntegerProducer . Integer @enduml
Возвращаемый тип может быть того же типа или субтипа
Типы аргументов обязаны совпадать
final
-классы и методыКлючевое слово final
:
на уровне класса запрещает наследование класса
на уровне метода запрещает наследование метода
Зачем это нужно?
Паттерн "Шаблонный метод"
J. Bloch: 'Design and document for inheritance, or else prohibit it'
sealed
-типы (Java 15+)Наследоваться можно, но только тем, кому разрешено:
public sealed class Pet
permits
//никакие другие не могут наследоваться от него
Cat, Dog, Fish {
}
public final Cat {...}
public sealed Dog permits Hound, Terrier, Toy {...}
public non-sealed Fish {...}
sealed
-интерфейсы и record-ыПока не знаем ни что такое 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 {}
Сигнатура метода определяется его названием и типами аргументов:
//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)
. . .
Данные, общие для всех экземпляров класса:
class Employee {
/*WARNING: данный пример
не работает при многопоточном исполнении*/
private static int nextId = 1;
private int id;
. . .
public void setId() {
id = nextId;
nextId++;
}
}
Выделяем память один раз
public class Math {
. . .
public static final double PI = 3.14159265358979323846;
. . .
}
. . .
Math.PI // возвращает 3.14...
Статическим методам доступны только статические переменные и вызовы других статических методов
class Employee {
private static int nextId = 1;
private int id;
. . .
public static int getNextId() {
return nextId; // returns static field
}
}
. . .
Employee.nextId() //имя класса вместо объекта
Теперь мы понимаем: метод main доступен всем и не требует инстанцирования объекта:
public class App {
public static void main(String... args) {
System.out.println("Hello, world!");
}
}
public class Person {
//public-конструктор без аргументов
public Person() {
....
}
//package-private конструктор с аргументом
Person(String name) {
....
}
}
Конструктор обязан быть.
Если мы 1) явно не написали конструктор, 2) родительский класс имеет конструктор без аргументов — то неявным образом у класса появляется публичный конструктор без аргументов по умолчанию.
Если мы явно написали хотя бы один конструктор, конструктор по умолчанию исчезает.
Если в родительском классе нет конструктора без аргументов, конструктор по умолчанию не создаётся.
Конструктор не обязан быть публичным.
Если у суперкласса нет конструктора без аргументов, первым вызовом должен быть super(…)
.
public class Person {
Person(String name){
...
}
}
class Employee extends Person{
Employee(String name) {
super(name);
...
}
}
Вызов this(…)
public class Person {
Person(String name){
...
}
Person(){
this("unknown");
}
}
class Employee {
private static int nextId;
private int id;
// static initialization block
static {
nextId = ThreadLocalRandom.current().nextInt(10000);
}
// object initialization block
{
id = nextId;
nextId++;
}
}
А его нет!
Даже не пытайтесь переопределять finalize
Почему метод finalize
оказался плохой идеей
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 120 abstract class Person Person <|-- Student Person <|-- Employee @enduml
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;
}
}
Класс, в котором хотя бы один из методов не реализован, должен быть помечен как abstract
Нереализованные методы в классе возникают двумя способами:
явно объявлены как abstract
унаследованы из абстрактных классов или интерфейсов и не переопределены.
Абстрактные классы нельзя инстанцировать через new.
new Person("John Doe");
— ошибка компиляции: 'Person is abstract, cannot be instantiated'.
//его нельзя инстацировать!
public interface Prism
{
//это --- final-переменная!
double PI = 3.14;
//это --- публичные абстрактные методы!
double getArea();
double getHeight();
//этот метод может вызывать другие и читать final-поля
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;
}
}
Если какой-то из методов интерфейса не будет переопределён, класс нужно пометить как abstract.
Нет внутреннего состояния и конструкторов
Можно наследоваться (extends
) только от одного класса, но реализовывать (implements
) сколько угодно интерфейсов — множественное наследование.
Поэтому как абстракция, интерфейс предпочтительнее.
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 150 class C1 class C2 class C3 interface I1 C1 <|-- C2 I1 <|.. C2 @enduml
C1 c1; C2 c2; C3 c3; I1 i1;
x instanceof A // false, если x == null
c1 instanceof С2 // true или false
i1 instanceof C2 // true или false
c2 instanceof C1 // всегда возвращает true
с3 instanceof C2 // не скомпилируется
Person p = . . .;
if (p instanceof Student) {
//если не защитить instanceof, возможен ClassCastException
Student s = (Student) p;
. . .
}
Person p = . . .;
if (p instanceof Student s) {
//здесь доступна переменная Student s
. . .
} else {
//здесь недоступна переменная Student s
}
//Скомпилируется
if (obj instanceof String s && s.length() > 5) {
. . .
s.contains(..)
}
//НЕ скомпилируется
if (obj instanceof String s || s.length() > 5) {...}
public int calculate(Expr expr) {
return switch (expr) {
//Не скомпилируется, если мы забудем что-то из реализаций Expr!
case Literal l -> l.value();
case Divide d -> calculate(d.left()) / calculate(d.right());
case Multiply m -> calculate(m.left()) * calculate(m.right());
case Add a -> calculate(a.left()) + calculate(a.right());
case Subtract s -> calculate(s.left()) - calculate(s.right());
};
}
when
guards (Java 21)static String describe(Object o) {
return switch (o) {
case null -> "got null";
case String s when s.isBlank() -> "blank string";
case String s -> "string(" + s.length() + " chars)";
case Integer i when i < 0 -> "negative int " + (-i);
case Integer i -> "int " + i;
default -> "something else";
};
}
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();
Каждый экземпляр Inner
имеет неявную ссылку на Outer
.
Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH). @startuml skinparam dpi 200 class Outer class Inner Outer *-- Inner @enduml
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 {};
}
}
По сути, ничем не отличаются от просто классов:
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();
}
}
Чаще всего — как реализация абстрактных классов и интерфейсов «по месту»
Анонимный класс — вложенный класс, поэтому до появления лямбд и ссылок на методы это был единственный способ организовать коллбэк
. . .
button.onMouseClick(new EventListener(){
void onClick(Event e) {
//здесь у нас доступ ко всем внешним полям
//и effectively final-переменным
}
});
Любой класс в Java является наследником Object
Писать class Employee extends Object
не надо
В этом классе определены важные методы
equals
и hashCode
toString
equals()
и hashCode()
boolean equals(Object other)
возвращает true
т. и т. т., когда внутреннее состояние совпадает
int hashCode()
возвращает целое значение, которое обязано совпадать для объектов с одинаковым внутренним состоянием
Это нужно для хеш-таблиц (и, пожалуй, является протекшей абстракцией)
equals
Рефлексивность:
\(\forall x \ne \mathrm{null} (x.equals(x))\)
Симметричность:
\(\forall x \ne \mathrm{null} \, \forall y \ne \mathrm{null} (x.equals(y) \iff y.equals(x))\)
Транзитивность:
\(\forall x \ne \mathrm{null} \, \forall y \ne \mathrm{null} \, \forall z \ne \mathrm{null} (x.equals(y) \& y.equals(z) \Rightarrow x.equals(z))\)
Консистентность: если сравниваемые объекты не изменялись, повторный вызов equals
должен возвращать одно и то же значение.
\(\forall x \ne \mathrm{null} (x.equals(\mathrm{null}) = \mathrm{false})\)
hashCode
Консистентность: если объект не изменялся, повторный вызов hashCode
должен возвращать одно и то же значение (но не обязательно одно и то же между разными запусками приложения)
Связь с equals
:
\(\forall x \forall y (x.equals(y) \Rightarrow x.hashCode() = y.hashCode())\)
Хотя
\(x.hashCode() = y.hashCode() \Rightarrow x.equals(y)\)
и не обязательно, но желательно для большинства случаев.
Переопределять equals
и hashCode
нужно только вместе и согласованно, чтобы выполнить контракт \(x.equals(y) \Rightarrow x.hashCode() = y.hashCode()\)
Грамотно реализовать equals
и hashCode
трудно, но, к счастью, самостоятельно это делать не нужно.
Для тестирования есть специальная библиотка EqualsVerifier.
Для генерации equals
и hashCode
можно использовать возможности IDE или библиотеки Lombok.
equals
и hashCode
equals
и hashCode
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
// никогда, НИКОГДА не пишите это сами
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class Person {
private int age;
private String name;
}
public class Person {
private String name;
private int age;
. . .
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
. . .
Person person = . . .
System.out.println(person);
import lombok.ToString;
@ToString
public class Person {
private int age;
private String name;
}
public class Point {
private final int x;
private final int y;
/*Подождите! Нам нужны:
* конструктор
* getX() и getY()
* equals и hashCode
* toString
* 40 строчек кода из ничего!
*/
public double distance(Point other) {
...
}
}
import lombok.Data;
@Data
public class Point {
private final int x;
private final int y;
public double distance(Point other) {
...
}
}
record Point(int x, int y) {
public double distance(Point other) {
...
}
}
private final
поля
конструктор
доступ через x()
и y()
equals
, hashCode
и toString
невозможно наследование от класса и от рекорда, но возможна реализация интерфейсов