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) родительский класс имеет конструктор без аргументов — то неявным образом у класса появляется публичный конструктор без аргументов по умолчанию.
Если мы явно написали хотя бы один конструктор, конструктор по умолчанию исчезает.
Если в родительском классе нет конструктора без аргументов, конструктор по умолчанию не создаётся.
Конструктор не обязан быть публичным.
До Java25: если у суперкласса нет конструктора без аргументов, первым вызовом должен был быть super(…)
.
После Java25: пишем как угодно, главное в конструкторе субдкласса не брать значения не проинстанцированных полей суперкласса.
public class Person {
Person(String name){
...
}
}
class Employee extends Person{
Employee(String name) {
super(name);
...
}
}
Вызов this(…)
Java 25+: свобода в плане того, где он может быть вызван.
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
оказался плохой идеей
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-переменным
}
});
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
) сколько угодно интерфейсов — множественное наследование.
Поэтому как абстракция, интерфейс предпочтительнее.
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 или XL
interface 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
(что это, мы узнаем вскоре).
//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)));
Минимизируйте область видимости (private
всё, что только можно)
Минимизируйте мутабельность (final
на всём, что только можно)
Документируйте точки расширения через наследование, или запрещайте наследование (final
, sealed
)