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-классов, но это скорее плохая практика.
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());
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
не указывает ни на какой объект (и его нельзя никуда передать). Это лишь указание компилитору вызвать метод суперкласса.
Возвращаемый тип может быть того же типа или субтипа
Типы аргументов обязаны совпадать
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
оказался плохой идеей
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
) сколько угодно интерфейсов — множественное наследование.
Поэтому как абстракция, интерфейс предпочтительнее.
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) {...}
//Before Java 10
if (obj instanceof Number) {
Number n = (Number) obj;
System.out.println(n.longValue());
}
//Java 10+
if (obj instanceof Number) {
var n = (Number) obj;
System.out.println(n.longValue());
}
//Java 14+
if (obj instanceof Number n) {
System.out.println(n.longValue());
}
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());
};
}
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
.
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
невозможно наследование от класса и от рекорда, но возможна реализация интерфейсов