На предыдущих
занятиях мы с вами объявляли классы с набором переменных и указывали
конструкторы. Пришло время узнать как добавлять функции в класс. По
общепринятой терминологии, функции внутри класса называются методами и
объявляются согласно следующему синтаксису:
[модификаторы]
тип имя_метода([аргументы]) {
// тело метода
}
Например,
запишем класс для представления прямоугольника и в нем пропишем метод для
вычисления площади прямоугольника:
class Rect {
int x1, y1;
int x2, y2;
Rect() {}
Rect(int x1, int y1, int x2, int y2) {
this.x1 = x1; this.y1 = y1;
this.x2 = x2; this.y2 = y2;
}
int square() {
return Math.abs(x1 - x2) * Math.abs(y1 - y2);
}
}
Здесь square – это обычная
функция, объявленная внутри класса Rect и благодаря
этому имеет доступ ко всем полям экземпляра этого класса. То есть, если в
функции main() создать два
таких объекта:
Rect r1 = new Rect(0, 0, 10, 20);
Rect r2 = new Rect(40, 20, 100, 200);
а, затем,
вызвать метод square:
int s1 = r1.square();
int s2 = r2.square();
то первый вызов
будет оперировать данными первого объекта r1, а второй –
данными второго объекта r2:
В результате,
при выводе в консоль:
мы увидим разные
значения.
Метод, как и
любая Java-функция, может
иметь самый разный тип данных и любое число входных аргументов. Например,
добавим еще один метод, который будет задавать координату верхнего левого угла:
void setLeftTop(int x, int y) {
this.x1 = x;
this.y1 = y;
}
Здесь тип метода
void, т.к. он не
возвращает никаких данных и прописаны два аргумента x, y. По аналогии
можно добавлять в класс любое число самых разных методов.
Модификаторы доступа
Теперь, когда мы
научились объявлять классы и создавать объекты, пришла пора поближе
познакомиться с китом ООП по имени инкапсуляция.
Итак, все поля и
методы класса можно дополнительно снабжать модификаторами доступа:
-
public – для
определения публичных (общедоступных) членов класса;
-
private – для
определения частных (закрытых) членов класса;
-
protected – для
определения защищенных (частично закрытых) членов класса.
Если ни один из
модификатор не указывается (как это было во всех наших примерах), то применяется:
-
модификатор
по умолчанию
– задает общедоступный режим ко всем членам класса текущего пакета.
В рамках этого
занятия мы с вами подробно познакомимся с режимами public и private, а о
модификаторе protected поговорим после
знакомства с механизмом наследования классов.
Геттеры и сеттеры
Давайте вернемся
к классу Rect и сделаем так,
чтобы поля x1, y1, x2, y2 были скрыты от
внешнего пользователя класса и были доступны только изнутри. Для этого нам
нужно добавить модификатор private перед типом этих
переменных:
class Rect {
private int x1, y1;
private int x2, y2;
...
}
Что в итоге
изменилось? Смотрите, если теперь попытаться выполнить вот такую команду:
то возникнет
ошибка доступа: к полю private нельзя обращаться напрямую через
ссылку на объект, то есть, извне. А если убрать этот модификатор в классе Rect, то ошибки не
будет. В этом отличие между закрытым (private) полем и
общедоступным, который определяется модификатором по умолчанию. Также открытое
(публичное) поле можно определять с помощью модификатора public:
class Rect {
public int x1, y1;
public int x2, y2;
...
}
Здесь также не
будет ошибок, при обращении к переменной x1 напрямую через
ссылку на экземпляр класса. В чем же тогда разница между модификатором по
умолчанию и public? На уровне
одного пакета, то есть, когда файлы программы находятся в одном каталоге,
разницы нет. Но, когда два класса расположены в разных пакетах (разных
каталогах), то поля с модификатором public будут доступны
всюду, а без него (по умолчанию) только в пределах одного пакета (каталога). Во
всем остальном они схожи.
Давайте вернем
для полей x1, y1, x2, y2 модификатор private и будем
полагать, что изменение координат возможно только через конструкторы и методы
класса. Для этого определим еще один метод setCoords:
void setCoords(int x1, int y1, int x2, int y2) {
this.x1 = x1; this.y1 = y1;
this.x2 = x2; this.y2 = y2;
}
И, так как у
этого метода не указан никакой модификатор, то используется модификатор по
умолчанию и он может быть вызван напрямую через ссылку на класс:
Rect r1 = new Rect();
r1.setCoords(0, 0, 10, 20);
То есть, мы
сначала создаем объект, а потом определяем координаты прямоугольника. Причем,
напрямую обратиться к полям x1, y1, x2, y2 нельзя, мы это
можем сделать только через метод setCoords(). Такие методы получили
название сеттеры. От префикса set, который обычно записывают в
названиях таких методов. Но спрашивается: зачем все так усложнять и не
использовать обращение к полям x1, y1, x2, y2 напрямую? Дело
в том, что у сеттеров есть одно важное преимущество: они позволяют не только
записывать данные в заданные поля, но и выполнять необходимую проверку на
корректность переданных данных. Например, мы требуем от пользователя, чтобы координаты
прямоугольника были положительными и варьировались в пределах:
0 <= x, y <= MAX_COORD
Чтобы
гарантировать наличие координат в этих пределах, в сеттере достаточно прописать
такую проверку:
void setCoords(int x1, int y1, int x2, int y2) {
if(isCorrect(x1) && isCorrect(y1) && isCorrect(x2) && isCorrect(y2)) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
}
Добавим закрытый
метод isCorrect для проверки
корректности значения координаты:
private boolean isCorrect(int arg) {
return (0 <= arg && arg <= MAX_COORD);
}
И в самом начале
класса пропишем константу MAX_COORD:
private static final int MAX_COORD = 1000;
Здесь
используется ключевое слово static, о котором мы еще будем
говорить. Все, теперь мы уверены, что координаты прямоугольника будут
находиться в указанных пределах. И это стало возможно использованию закрытых
полей и публичному сеттеру setCoords. Также этот пример показывает,
что методы класса также можно делать закрытыми (private) и функция isCorrect доступна только
внутри класса и не доступна извне. Что вполне логично для нашей текущей
реализации.
Нам здесь не
хватает еще одного важного метода – для получения текущих координат
прямоугольника. Чтобы сейчас не усложнять программу, я запишу четыре таких
метода – по одному для каждого значения:
public int getX1() {return x1;}
public int getX2() {return x2;}
public int getY1() {return y1;}
public int getY2() {return y2;}
Здесь
используется модификатор public для указания общедоступности
этих методов, а также префикс get, от которого пошло название – геттеры.
То есть, геттеры – это методы, служащие для получения информации из закрытых
полей экземпляра класса.
Давайте воспользуемся
этими методами и выведем значения координат в консоль:
System.out.println(r1.getX1() + ", " + r1.getY1() +
", " + r1.getX2() + ", " + r1.getY2());
Вот так
реализуется механизм инкапсуляции в классах языка Java.
Путь кодера
Подвиг 1. Объявить класс Person для описания
сотрудника с полями: Ф.И.О., возраст, вес, номер разряда (целое число от 1 до
5). Прописать конструктор(ы), сеттер(ы) и геттер(ы) для записи значений по
сотруднику и считывания данных. Обеспечить корректность представления данных:
возраст и вес – положительные числа в пределах [30; 200]; разряд в диапазоне [1;
5]; в Ф.И.О. могут использоваться только буквенные символы, пробел и дефис.
Создать несколько таких объектов и убедиться в их корректной работе.
Подвиг 2. Объявить класс Vec2 для
представления двумерного вектора с приватными полями a, b – значения
элементов вектора. Прописать конструктор(ы), сеттер(ы) и геттер(ы) для записи и
считывания данных вектора. Нужно обеспечить, чтобы значения элементов вектора
находились в диапазоне [-MAX_VAL; MAX_VAL], где MAX_VAL – константа,
заданная в этом же классе. Добавить в класс Vec2 еще два
метода:
-
Vec2 sum(Vec2 v)
-
Vec2 sub(Vec2 v)
для вычисления
суммы и разности двух векторов класса Vec2. Эти методы не должны менять
значений векторов, участвующих в операциях сложения и вычитания, а создавать
новый с соответствующими значениями.
Подвиг 3. Объявить первый
класс Book для
представления книги с полями: название, автор, год издания, число страниц.
Определить необходимые конструкторы, сеттеры и геттеры для записи и считывания
данных (данные должны быть с очевидными ограничениями по диапазону значений).
Объявить второй класс Lib, который будет хранить ссылки на книги Book в виде массива:
Book
lib[] = new Book[MAX_BOOKS];
где MAX_BOOKS – константа,
определяющая максимальное число книг. Обеспечить возможность добавления,
удаления и вывода списка книг посредством методов класса Lib. Прямого
доступа извне к массиву lib быть не должно.