Урок 9. Классы и объекты
До сих пор вся программа была одним классом Main с методами. Но Java — объектно-ориентированный язык: в нём программу собирают из объектов, каждый из которых хранит свои данные и умеет что-то делать.
Класс — это описание, чертёж: какие у объекта есть данные и что он умеет. Объект — конкретный экземпляр по этому чертежу. Класс Student описывает студента вообще, а объекты — Анну и Олега, у каждого свои имя и возраст.
Первый класс
public class Main {
public static void main(String[] args) {
Student a = new Student("Анна", 16);
Student b = new Student("Олег", 17);
a.introduce();
b.introduce();
a.birthday();
a.introduce();
}
}
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
void introduce() {
System.out.println(name + ", " + age + " лет");
}
void birthday() {
age++;
}
}Анна, 16 лет
Олег, 17 лет
Анна, 17 летЧто здесь есть:
- Поля
nameиage— данные объекта. У каждого объекта они свои: день рождения Анны не меняет возраст Олега. - Конструктор
Student(String name, int age)— особый метод, который вызывается при создании объекта. Его имя совпадает с именем класса, а типа возвращаемого значения у него нет, дажеvoid. this.name— поле этого объекта. Оно нужно, когда параметр называется так же, как поле:this.name = nameкладёт параметр в поле.- Методы
introduceиbirthday— безstatic: они работают с полями конкретного объекта и вызываются через него —a.introduce(). new Student("Анна", 16)создаёт объект и вызывает конструктор.
Оба класса здесь в одном файле Main.java. Публичным может быть только один из них — тот, чьё имя совпадает с именем файла. Если запускаете командой java Main.java, класс с main должен идти в файле первым. В больших проектах каждый класс обычно лежит в своём файле: Student.java, Main.java.
Если в классе нет ни одного конструктора, Java добавляет пустой — тогда объект создаётся как new Student(). Но как только объявлен свой конструктор с параметрами, пустого больше нет, и new Student() не скомпилируется.
Переменная хранит ссылку
Переменная типа класса хранит не сам объект, а ссылку на него — как и у массивов:
Student c = a;
c.age = 30;После этого a.age тоже 30: a и c указывают на один объект. Переменная, которой не присвоили объект, хранит null, и вызов метода через неё даёт NullPointerException.
Инкапсуляция: private и геттеры
Сейчас любой код может написать a.age = -5, и объект окажется в бессмысленном состоянии. Инкапсуляция — правило прятать поля внутри класса и менять их только через методы, которые проверяют значения.
public class Main {
public static void main(String[] args) {
Account acc = new Account("Иван");
acc.deposit(100);
acc.withdraw(30);
acc.withdraw(500);
System.out.println(acc.getOwner() + ": " + acc.getBalance());
}
}
class Account {
private final String owner;
private int balance;
Account(String owner) {
this.owner = owner;
}
void deposit(int amount) {
if (amount > 0) {
balance += amount;
}
}
void withdraw(int amount) {
if (amount > balance) {
System.out.println("Недостаточно средств");
return;
}
balance -= amount;
}
int getBalance() {
return balance;
}
String getOwner() {
return owner;
}
}Недостаточно средств
Иван: 70private— поле видно только внутри своего класса. Строкаacc.balance = 1000;вmainне скомпилируется:balance has private access in Account.- Методы
getBalanceиgetOwner— геттеры: они только отдают значение. Методы, которые меняют поле с проверкой, называют сеттерами (setAge) или по смыслу —deposit,withdraw. finalу поляowner— значение задаётся один раз, в конструкторе, и больше не меняется.- Поле
balanceне получило значения в конструкторе — у полей есть значения по умолчанию:0у чисел,falseуboolean,nullу объектов. У локальных переменных метода такого нет.
Модификаторы доступа: private — только свой класс; без модификатора — классы того же пакета (группы классов, как java.util); protected — ещё и наследники; public — все.
Метод toString
Если вывести объект через println, Java напечатает имя класса и служебный номер — что-то вроде Point@6d06d69c. Чтобы объект выводился понятно, в класс добавляют метод toString:
public class Main {
public static void main(String[] args) {
Point p = new Point(3, 4);
System.out.println(p);
System.out.println("Точка: " + p);
}
}
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "(" + x + ", " + y + ")";
}
}(3, 4)
Точка: (3, 4)println и склейка со строкой сами вызывают toString. Пометка @Override говорит компилятору: этот метод заменяет метод, унаследованный от родителя (о наследовании — в следующем уроке). Если в имени опечатка, компилятор сообщит об этом. Слово public здесь обязательно: исходный toString публичный, и заменяющий не может быть доступен меньше.
Частые ошибки
constructor Student in class Student cannot be applied to given types(илиno suitable constructor found) — объект создают с аргументами, которые не подходят ни к одному конструктору, напримерnew Student(), когда объявлен только конструктор с параметрами.- Забыли
this—name = name;в конструкторе присваивает параметр самому себе, а поле остаётсяnull. - Вызов метода без объекта —
introduce()вместоa.introduce(). Методы безstaticвызываются через объект:переменная.метод().
Коротко
- Класс — описание, объект — экземпляр; объект создаётся через
new, и вызывается конструктор. - Поля — данные объекта, методы без
staticработают с полями своего объекта. this.поле— поле текущего объекта; конструктор называется как класс и не имеет типа.- Инкапсуляция: поля
private, доступ через методы с проверками. toStringс@Overrideзадаёт, как объект выглядит при выводе.