Инкапсуляция в Java на простых примерах. Модификаторы доступа private, no modifier, protected, public
Все члены класса в языке Java — поля и методы, свойства — имеют модификаторы доступа. В прошлых темах мы уже сталкивались с модификатором public. Модификаторы доступа позволяют задать допустимую область видимости для членов класса, то есть контекст, в котором можно употреблять данную переменную или метод.
В Java используются следующие модификаторы доступа:
- public: публичный, общедоступный класс или член класса. Поля и методы, объявленные с модификатором public, видны другим классам из текущего пакета и из внешних пакетов.
- private: закрытый класс или член класса, противоположность модификатору public. Закрытый класс или член класса доступен только из кода в том же классе.
- protected: такой класс или член класса доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах
- Модификатор по умолчанию (default). Отсутствие модификатора у поля или метода класса предполагает применение к нему модификатора по умолчанию. Такие поля или методы видны всем классам в текущем пакете.
Рассмотрим все возможные случаи. Допустим, у нас есть проект со следующей структурой:
В данном случае имеется три пакета. Пакет encapsulation содержит главный класс программы, в котором и прописан метод main. Его мы трогать не будем.
И также определены два пакета encapsulation .package1, который содержит классы ClassA и ClassB, и пакет encapsulation .package2, который содержит классы ClassC и ClassD.
Класс ClassA содержит весь возможный набор модификаторов доступа:
package accessModifiersAndEncapsulation.encapsulation.package1;
public class ClassA {
private int num1 = 3;
int num2 = 2;
protected int num3 = 3;
public int num4 = 4;
public void displayNum1() {
System.out.println(num1);
}
protected void displayNum2() {
System.out.println(num2);
}
void displayNum3() {
System.out.println(num3);
}
private void displayNum4() {
System.out.println(num4);
}
}
package accessModifiersAndEncapsulation.encapsulation.package1; public void displayNum1() { System.out.println(num1); protected void displayNum2() { System.out.println(num2); System.out.println(num3); private void displayNum4() { System.out.println(num4); |
Модификатор доступа должен предшествовать остальной часть определения переменной или метода.
Пусть класс ClassB использует ClassA:
package accessModifiersAndEncapsulation.encapsulation.package1;
public class ClassB {
public void result() {
ClassA classA = new ClassA();
//System.out.println(classA.num1); // ошибка, так как num1 — private
classA.displayNum1(); // public
System.out.println(classA.num2); // идентификатор по умолчанию
classA.displayNum2(); // protected
System.out.println(classA.num3); // protected
classA.displayNum3(); // идентификатор по умолчанию
System.out.println(classA.num4); // public
//classA.displayNum4(); // ошибка, так как displayNum4() — private
}
}
package accessModifiersAndEncapsulation.encapsulation.package1; ClassA classA = new ClassA(); //System.out.println(classA.num1); // ошибка, так как num1 — private classA.displayNum1(); // public System.out.println(classA.num2); // идентификатор по умолчанию classA.displayNum2(); // protected System.out.println(classA.num3); // protected classA.displayNum3(); // идентификатор по умолчанию System.out.println(classA.num4); // public //classA.displayNum4(); // ошибка, так как displayNum4() — private |
Так как классы ClassB и ClassA находятся в одном пакете, то мы сможем использовать все поля и методы класса ClassA в ClassB кроме тех, что объявлены как private.
Теперь используем класс ClassA в классе ClassC:
package accessModifiersAndEncapsulation.encapsulation.package2;
import accessModifiersAndEncapsulation.encapsulation.package1.ClassA;
public class ClassC{
public void result(){
ClassA classA = new ClassA();
//System.out.println(classA.num1); // ошибка, так как num1 — private
classA.displayNum1(); // public
//System.out.println(classA.num2); // ошибка, так как num2 — идентификатор по умолчанию
//classA.displayNum2(); //ошибка, так как доступ — protected
//System.out.println(classA.num3); // ошибка, так как доступ — protected
//classA.displayNum3(); //ошибка, так как доступ — идентификатор по умолчанию
System.out.println(classA.num4); // public
//classA.displayNum4(); // ошибка, так как displayNum4() — private
}
}
package accessModifiersAndEncapsulation.encapsulation.package2;import accessModifiersAndEncapsulation.encapsulation.package1.ClassA; ClassA classA = new ClassA(); //System.out.println(classA.num1); // ошибка, так как num1 — private classA.displayNum1(); // public //System.out.println(classA.num2); // ошибка, так как num2 — идентификатор по умолчанию //classA.displayNum2(); //ошибка, так как доступ — protected //System.out.println(classA.num3); // ошибка, так как доступ — protected //classA.displayNum3(); //ошибка, так как доступ — идентификатор по умолчанию System.out.println(classA.num4); // public //classA.displayNum4(); // ошибка, так как displayNum4() — private |
Так как класс ClassC находится в другом пакете и не является наследником класса ClassA, то в нем можно использовать только те поля и методы класса ClassA, которые объявлены с модификатором public.
И последний случай — ClassD является наследником класса ClassA, но находится в другом пакете (чуть позже мы более подробно разберем наследование):
package accessModifiersAndEncapsulation.encapsulation.package2;
import accessModifiersAndEncapsulation.encapsulation.package1.ClassA;
public class ClassD extends ClassA {
public void result() {
//System.out.println(num1); // ошибка, так как num1 — private
displayNum1(); // public
//System.out.println(num2); // ошибка, так как доступ — идентификатор по умолчанию
displayNum2(); // protected
System.out.println(num3); // protected
//displayNum3(); //ошибка, так как доступ по умолчанию
System.out.println(num4); // public
//classA.displayNum4(); // ошибка, так как displayNum4() — private
}
}
package accessModifiersAndEncapsulation.encapsulation.package2;import accessModifiersAndEncapsulation.encapsulation.package1.ClassA;public class ClassD extends ClassA { //System.out.println(num1); // ошибка, так как num1 — private //System.out.println(num2); // ошибка, так как доступ — идентификатор по умолчанию displayNum2(); // protected System.out.println(num3); // protected //displayNum3(); //ошибка, так как доступ по умолчанию System.out.println(num4); // public //classA.displayNum4(); // ошибка, так как displayNum4() — private |
Так как ClassD — наследник класса ClassA, то мы можем напрямую использовать методы и поля ClassA без создания объекта. И здесь нам опять недоступны поля и методы private, и также нам недоступны поля и методы с модификатором доступа по умолчанию. В то же время в отличие от находящегося в том же пакете класса ClassC в классе ClassD мы можем использовать методы и поля с доступом protected.
Инкапсуляция
Казалось бы, почему бы не объявить все переменные и методы с модификатором public? Однако использование различных модификаторов гарантирует, что данные не будут искажены или изменены не надлежащим образом. Подобное сокрытие данных называется инкапсуляцией.
А вместо непосредственного использования полей, как правило, используют методы доступа:
class Book{
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
}
public void setName(String name){ |
И затем вместо непосредственной работы с полем name в классе Book мы будем работать с методами, которые устанавливает значение этого поля и возвращают его.
Java | Модификаторы доступа и инкапсуляция
Последнее обновление: 09.08.2021
Все члены класса в языке Java — поля и методы — имеют модификаторы доступа. В прошлых темах мы уже сталкивались с модификатором public. Модификаторы доступа позволяют задать допустимую область видимости для членов класса, то есть контекст, в котором можно употреблять данную переменную или метод.
В Java используются следующие модификаторы доступа:
- public: публичный, общедоступный класс или член класса. Поля и методы, объявленные с модификатором public, видны другим классам из текущего пакета и из внешних пакетов.
- private: закрытый класс или член класса, противоположность модификатору public. Закрытый класс или член класса доступен только из кода в том же классе.
- protected: такой класс или член класса доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах
- Модификатор по умолчанию. Отсутствие модификатора у поля или метода класса предполагает применение к нему модификатора по умолчанию. Такие поля или методы видны всем классам в текущем пакете.
Рассмотрим модификаторы доступа на примере следующей программы:
public class Program{
public static void main(String[] args) {
Person kate = new Person(«Kate», 32, «Baker Street», «+12334567»);
kate.displayName(); // норм, метод public
kate.displayAge(); // норм, метод имеет модификатор по умолчанию
kate.displayPhone(); // норм, метод protected
//kate.displayAddress(); // ! Ошибка, метод private
System.out.println(kate.name); // норм, модификатор по умолчанию
System.out.println(kate.address); // норм, модификатор public
System.out.println(kate.age); // норм, модификатор protected
//System.out.println(kate.phone); // ! Ошибка, модификатор private
}
}
class Person{
String name;
protected int age;
public String address;
private String phone;
public Person(String name, int age, String address, String phone){
this.name = name;
this.age = age;
this.address = address;
this.phone = phone;
}
public void displayName(){
System.out.printf(«Name: %s
«, name);
}
void displayAge(){
System.out.printf(«Age: %d
«, age);
}
private void displayAddress(){
System.out.printf(«Address: %s
«, address);
}
protected void displayPhone(){
System.out.printf(«Phone: %s
«, phone);
}}
В данном случае оба класса расположены в одном пакете — пакете по умолчанию, поэтому в классе Program мы можем использовать все методы и переменные класса Person, которые имеют модификатор по умолчанию, public и protected. А поля и методы с модификатором private в классе Program не будут доступны.
Если бы класс Program располагался бы в другом пакете, то ему были бы доступны только поля и методы с модификатором public.
Модификатор доступа должен предшествовать остальной части определения переменной или метода.
Инкапсуляция
Казалось бы, почему бы не объявить все переменные и методы с модификатором public, чтобы они были доступны в любой точке программы вне зависимости от пакета или класса? Возьмем, например, поле age, которое представляет возраст.
Если другой класс имеет прямой доступ к этому полю, то есть вероятность, что в процессе работы программы ему будет передано некорректное значение, например, отрицательное число. Подобное изменение данных не является желательным.
Либо же мы хотим, чтобы некоторые данные были достуны напрямую, чтобы их можно было вывести на консоль или просто узнать их значение. В этой связи рекомендуется как можно больше ограничивать доступ к данным, чтобы защитить их от нежелательного доступа извне (как для получения значения, так и для его изменения).
Использование различных модификаторов гарантирует, что данные не будут искажены или изменены не надлежащим образом. Подобное сокрытие данных внутри некоторой области видимости называется инкапсуляцией.
Так, как правило, вместо непосредственного применения полей используют методы доступа. Например:
public class Program{
public static void main(String[] args) {
Person kate = new Person(«Kate», 30);
System.out.println(kate.getAge()); // 30
kate.setAge(33);
System.out.println(kate.getAge()); // 33
kate.setAge(123450);
System.out.println(kate.getAge()); // 33
}
}
class Person{
private String name;
private int age = 1;
public Person(String name, int age){
setName(name);
setAge(age);
}
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
if(age > 0 && age < 110)
this.age = age;
}
}
И затем вместо непосредственной работы с полями name и age в классе Person мы будем работать с методами, которые устанавливают и возвращают значения этих полей. Методы setName, setAge и наподобие еще называют мьютейтерами (mutator), так как они изменяют значения поля. А методы getName, getAge и наподобие называют аксессерами (accessor), так как с их помощью мы получаем значение поля.
Причем в эти методы мы можем вложить дополнительную логику. Например, в данном случае при изменении возраста производится проверка, насколько соответствует новое значение допустимому диапазону.
Java. Инкапсуляция данных в классе. Управление доступом в Java. Модификаторы private, protected, public
Инкапсуляция данных в классе. Управление доступом в Java. Модификаторы private, protected, public
Содержание
Поиск на других ресурсах:
1. Что такое инкапсуляция в классе?
Инкапсуляция в классе означает предоставление соответствующего доступа к членам класса с помощью специальных модификаторов доступа: private, protected, public. Доступ можно задавать как к данным, так и к методам класса.
⇑
2. Какие существуют категории доступности членов данных класса в языке Java?
В языке Java выделяются 4 категории доступности:
- производные классы (подклассы) в одном пакете;
- классы в одном пакете, которые не являются производными;
- производные классы (подклассы) в разных пакетах;
- классы, которые находятся в разных пакетах и не являются производными (не являются подклассами).
⇑
3. Какие преимущества дает использование механизма инкапсуляции в классе?
Инкапсуляция данных в классе дает следующие преимущества:
- если данные класса есть скрытыми, то это защищает от злоупотреблений или случайного доступа к ним. Доступ к данным обеспечивается с помощью специально разработанных методов класса;
- если данные (методы) класса есть скрытыми, то они представляют своего рода «черный ящик». Взаимодействие с данными обеспечивается через соответствующие методы класса;
- гибкость в предоставлении доступа к разным видам данных класса с помощью соответствующих модификаторов;
- в методах доступа к скрытым данным класса можно делать соответствующие проверки на корректность.
⇑
4. Какие модификаторы доступа применяются к данным из методов класса?
Для управления доступом к данным из методов класса, в Java используются следующие модификаторы доступа:
- private. В этом случае данные (методы) есть скрытыми. Доступ к таким данным имеют только методы класса. Из экземпляра (объекта) класса private-элементы есть недоступны;
- public. Если член класса объявлен с модификатором доступа public, то этот член класса есть доступным из любого кода данного пакета и за пределами пакета (для всех методов всех классов);
- protected. Этот модификатор используется в случае, когда нужно, чтобы элемент класса был доступным за пределами текущего пакета, но только классам, непосредственно производным (унаследованным) от данного класса.
Также элементы класса могут быть объявлены без модификатора доступа.
⇑
5. Какая общая форма объявления элемента класса с заданным модификатором доступа?
Модификатор доступа к конкретному члену данных или методу устанавливается перед его объявлением.
Общая форма класса, в котором объявляются один член данных класса и один метод следующая
class ClassName { // объявление члена данных класса access_modifier type variable; // объявление метода в классе access_modifier return_type MethodName(parameters) { // тело метода // …
}
}
где
- ClassName – имя объявляемого класса;
- access_modifier – один из модификаторов доступа private, public, protected;
- type – тип члена данных класса с именем variable;
- variable – член данных класса, который объявлен с типом type;
- return_type – тип, который возвращает метод класса с именем MethodName();
- parameters – параметры, которые получает метод MethodName().
⇑
6. Доступ к элементам класса, которые объявлены с модификатором доступа private. Пример
Элементы класса, которые объявленные с модификатором доступа private, есть скрытыми. К ним имеют доступ только методы данного класса. Из всех других частей программного кода к private-членам класса нет доступа.
Такой способ скрытия данных в классе есть эффективным в случаях, если нужно:
- спрятать детали организации структур данных и реализации данного класса;
- избегнуть возможные случайные изменения данных в экземпляре класса;
- обеспечить удобство доступа к данным в классе с помощью специально разработанных методов. Здесь имеется ввиду разработка методов в классе которые обеспечивают чтение/запись данных класса. Эти методы могут включать соответствующие проверки на корректность при изменении данных в классе;
- избегнуть возможные злоупотребления данными в классе что может привести к возникновению трудноуловимых ошибок в программе.
Пример. Демонстрируется пример доступа к private-переменной класса с помощью методов доступа.
Объявляется класс, реализующий день недели. В классе объявляется скрытый (private) член данных класса с именем value. Для доступа к value в классе используются:
- конструктор DayWeek(), который инициализирует значение value=1. Конструктор объявлен без модификатора. Это значит, что в пределах пакета он имеет тип доступа public;
- метод Get(), возвращающий значение value. Поскольку метод объявлен в классе DayWeek, то в теле метода есть доступ к переменной value;
- метод Set(), устанавливающий новое значение value. Метод также объявлен в классе, поэтому имеет доступ к value. В методе осуществляется проверка на корректность значения value в границах 1..7. Если задать другое значение, то значение value не изменится;
- метод GetName(), возвращающий название дня недели в зависимости от значения value.
Реализация класса DayWeek следующая
// класс, который реализует день недели class DayWeek { private int value; // скрытая переменная класса // конструктор класса, инициализирует переменную value DayWeek() { value = 1; // по умолчанию — «Monday» } // методы доступа // метод, который возвращает данные в классе public int Get() { return value; } // метод, который устанавливает новый день недели public void Set(int _value) { // в методе выполняется проверка на корректность значения _value if ((_value>=1)&&(_value
Инкапсуляция в Java
Данная статья:
Привет! Это статья про инкапсуляцию — один из принципов ООП. Если Вы не знаете, что такое принципы ООП — почитайте эту статью:
В программировании очень важна безопасность. В ООП безопасность обеспечивается по-своему — с помощью принципа инкапсуляции (с англ. «encapsulation»). Инкапсуляцию можно перевести как «положить что-то во что-то», или для простоты «обернуть в капсулу» ????
С помощью инкапсуляции мы защищаем данные от неправомерного использования.
Если еще проще
Как не удивительно, мы можем видеть инкапсуляцию и в повседневной жизни. Например, Ваше имя и фамилия находится в относительно свободном доступе — они известны всем Вашим знакомым.
Но Вы не хотели бы, чтобы кто-то знал номер Вашей кредитки и ее пинкод? Если проводить аналогию с методами, Вы не хотели бы, чтобы кто-то, например, ездил на Вашей машине, спал в Вашей кровати и ел Вашу кашу? ???? Даже свои секреты мы доверяем ограниченному числу людей — можно сказать регулируем права доступа.
Для того, чтобы никто не получил доступ к тому, что не следует, мы пользуемся разными средствами для обеспечения безопасности — ставим замки на двери, пароль на телефон или ноутбук, и т.д.
Точно так же и в Java — мы пользуемся разными средствами для обеспечения принципа инкапсуляции. Но как же мы это делаем?
Как применяется инкапсуляция
Есть несколько способов регулировать доступ к нашим данным. Основные это:
- Модификаторы доступа (Access modifiers)
- Геттеры и Сеттеры (Getters and Setters)
- Модификаторы доступа
- Модификаторы доступа — это специальные слова, которые показывают, кому нельзя, а кому можно пользоваться данными.
- Существуют четыре модификатора доступа:
На самом деле по названиям не сложно понять, что каждый из них означает:
- public — «публичный, доступный всем»
- default — «по умолчанию». Когда мы не пишем модификатора доступа (как мы это делали в наших предыдущих уроках), он по умолчанию имеет значение default. Данные с этим модификатором видны в пределах package.
- protected — «защищенный». На самом деле это то же самое, что и default, только доступ имеют еще и классы-наследники.
- private — «частный, личный». Такие данные видны только самому классу.
Модификаторы доступа пишутся перед названиями переменных, методов и даже классов:
Как это обеспечивает безопасность? Давайте попробуем создать класс, в котором будет только одна переменная — String s. Допустим она имеет модификатор public:
class MyClass {
public String s = «Hello World!»;
}
public String s = «Hello World!»; |
Теперь попробуем вывести значение этой переменной на экран:
public class Test {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(obj.s);
}
}
public static void main(String[] args) { MyClass obj = new MyClass(); System.out.println(obj.s); |
Отлично! Получили:
Тем не менее, точно так же мы можем и поменять эту переменную. Например:
public class Test {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(obj.s);
obj.s = «It's modified!»;
System.out.println(obj.s);
}
}
public static void main(String[] args) { MyClass obj = new MyClass(); System.out.println(obj.s); obj.s = «It's modified!»; System.out.println(obj.s); |
Получим:
Как видите, мы спокойно поменяли строку. А это не очень хорошо ???? Потому что так кто угодно из любой части программы сможет ее изменить.
Если мы не хотим этого — поменяем модификатор с public на private:
class MyClass {
private String s = «Hello World!»;
}
private String s = «Hello World!»; |
Теперь при попытке доступа к переменной s — будь то чтение или запись — у нас возникнет ошибка:
Вот так мы защитили переменную от злодеев ????
Сейчас мы рассказали про модификаторы доступа в двух словах. Подробнее про модификаторы можете прочитать тут:
Геттеры и Сеттеры
Ну, вот мы защитили переменную — но ее же надо как-то менять? ???? Если влиять на переменную напрямую считается плохой практикой, то как по-другому, правильно можно изменять переменные?
Для этого существуют специальные методы — так называемые Геттеры и Сеттеры. Ну, они не то чтобы специальные — просто настолько часто используются, что были вынесены в отдельную категорию методов.
Геттер — от англ. «get», «получать» — это метод, с помощью которого мы получаем значение переменной, т.е. ее читаем. Например, создадим Геттер для нашей переменной s:
class MyClass {
private String s = «Hello World!»;
public String getS()
{
return s;
}
}
private String s = «Hello World!»; |
Сеттер — от англ. «set», «устанавливать» — это метод, с помощью которого мы меняем, или задаем значение переменной. Допишем Сеттер для переменной s:
class MyClass {
private String s = «Hello World!»;
public String getString()
{
return s;
}
public void setS(String newValue)
{
s = newValue;
}
}
private String s = «Hello World!»; public String getString() public void setS(String newValue) |
Отлично! Теперь, если переписать наш main() по-правильному, получим:
public class Test {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(obj.getS());
obj.setS(«It's modified!»);
System.out.println(obj.getS());
}
}
public static void main(String[] args) { MyClass obj = new MyClass(); System.out.println(obj.getS()); obj.setS(«It's modified!»); System.out.println(obj.getS()); |
Как видите, вместо того чтобы напрямую стучаться к переменной, мы меняем ее с помощью сеттеров и сеттеров. Мы получаем:
Если результат такой же, зачем это все было менять?
Тут у нас в каждом методе всего по одной строчке, но если нам понадобится добавить какую-то логику — например, присваивать новое значение строке s только если она больше какой-то длины, или если содержит слово «Java». Главное, мы получаем контроль над происходящим — никто не может просто так менять или читать наши переменные.
- Подробнее про Гетеры и Сеттеры можете прочитать тут:
- Итого
- Итак, теперь Вы знаете, что такое инкапсуляция и как она выглядит на практике. Напоследок хотелось бы сказать:
Создавая новый класс, Вы должны думать не только о функциональности, но и о безопасности — кто и при каких условиях может получать доступ к внутренностям Вашего класса.
Надеемся, что наша статья была Вам полезна. Можно записаться к нам на курсы по Java на сайте.
7 Что такое инкапсуляция. Геттеры и сеттеры (Getters and setters)
Здесь вы узнаете об инкапсуляции в Java.
Инкапсуляция (от лат. in capsula; «в коробочке») — это размещение в оболочке, изоляция, закрытие чего-либо инородного с целью исключения влияния на окружающее.
К примеру, поместить радиоактивные отходы в капсулу, закрыть кожухом механизм, убрать мешающее в шкаф. В общем случае в языках программирования термин «инкапсуляция» относится к одному или двум утверждениям сразу:
— это механизм языка, позволяющий ограничить доступ одних компонентов программы или пользователей к другим;
— это языковая конструкция, позволяющая связать данные с методами, предназначенными для обработки этих данных.
Пример инкапсуляции в реальной жизни:
Инкапсуляция — это когда грязные носки при уборке прячутся под диван, а при необходимости их надеть, они достаются из под дивана, проверяются на допустимую свежесть и, при необходимости, стираются или пшикаются одеколоном перед ноской. //не делайте так 🙂 Инкапсуляция защищает переменные экземпляра (требуемые поля) от передачи им (получения ими) недопустимых или нежелательных значений, позволяя внести изменения администратором спустя некоторое время.
Например, инкапсуляция может понадобится, если переменные экземпляра могут принимать только определённый диапазон значений (так возраст не может быть отрицательным). Таким образом, инкапсуляция нужна, чтобы обеспечивать корректное состояние объекта и скрывать переменные от прямого (непосредственного) доступа извне. Инкапсуляцию в Java реализуют (обеспечивают) при помощи служебных слов-модификаторов доступа (private, protected, package default, public), явно задающие область видимости каждого поля и метода класса (которые позволяют ограничивать доступ к элементам данных («прятать» данные)).
Философия инкапсуляции состоит в том, что изменять напрямую состояние объекта должен сам объект. Другие объекты должны осуществлять это только через его методы (геттеры, сеттеры) с предварительной валидацией (если это необходимо).
Общедоступные методы-геттеры и методы-сеттеры (getters and setters) используют для того, чтобы предоставить опосредованный (через использование методов) контролируемый доступ к важным переменным и их обновление.
Механизм действия инкапсуляции таков: для того, чтобы защитить объекты от нежелательных и недопустимых изменений она вынуждает остальной код обращаться к этим объектам не напрямую, а опосредованно, с помощью методов-сеттеров, в которые добавляют условия, гарантирующие допустимые значения полей.
Переменная, скрытая модификатором с ограниченным доступом (например, private) и доступна только через геттер и сеттер, инкапсулирована.
Некоторые фреймворки такие, как Hibernate, Spring, Struts и т. д. могут проверять информацию или внедрять свой служебный код через геттер и сеттер, поэтому при интеграции кода с ними необходимо иметь эти методы.
Также целью инкапсуляции является разрыв зависимости публичных методов класса (то, что могут использовать другие классы) от внутренних методов (скрытой части), реализующих функциональность, которая не должна быть видна другим классам. Это означает, что детали реализации скрыты от клиентского кода.
Один из аспектов, касающихся реализации, которые нужно помнить, заключается в следующем: изменения в реализации не должны требовать внесения изменений в пользовательский код.
Если публичные методы спроектированы надлежащим образом, то изменения в реализации не должны требовать внесения изменений в пользовательский код.
Это сделано для того чтобы малейшее изменение в классе (например изменение реализации способа хранения данных или их обработки и тд) не влекло за собой изменения в работе класса-клиента. Т.е. пользователи класса не должны страдать от его внутренних изменений.
Достоинства:
- сокрытие реализации позволяет сократить временные затраты на поиск ошибок: если мы контролируем доступ к атрибуту, то при возникновении проблемы нам не придется беспокоиться об отслеживании каждого фрагмента кода, который мог бы изменить значение соответствующего атрибута — оно может быть изменено только в одном месте (с помощью сеттера)
- также инкапсуляция заставляет пользователя играть по правилам, показывая ему только необходимые методы и скрывая не нужные
- инкапсуляция отделяет изменяемую часть класса от постоянной. Данное свойство полезно, т.к. позволяет безболезненно для пользователя менять код скрытых методов без каких-либо для него проблем, т.к. сигнатуры публичных методов, которые ему доступны, не меняются, а изменяются только скрытые (приватные) методы
Недостатки:
- необходимо писать дополнительный код в виде геттеров и сеттеров, которые зачастую захламляют код
О служебных словах-модификаторах доступа я написал здесь: https://javaika.blogspot.com/2018/12/3-java.html
Метод-геттер (Getter — с англ. получатель) — это метод, с помощью которого получают (считывают) значение переменной, доступ к которой напрямую ограничен.
Геттер иногда называют accessor.
Метод-сеттер (Setter, от англ.set — устанавливать) — это метод, с помощью которого задают или изменяют значение переменной (присваивают какое-либо значение инкапсулированному полю), например, обработав при этом недопустимые присваивания.
Сеттер иногда называют mutator.
Они могут предоставлять дополнительные функции: проверка корректности значения перед его присваиванием полю или обработка ошибок.
Внутри метода-сеттера можно делать что угодно: например, отвергнуть недопустимое значение, сгенерировать исключение, округлить параметр до минимально допустимого значения и т.п.
Таким образом, мы можем добавляется условная логику и обеспечить поведение в соответствии с потребностями (если сеттер не имеет подобной логики, а лишь присваивает полю какое-то значение, то его наличие не обеспечивает инкапсуляцию, а его присутствие становится фиктивным). При реализации только геттера (без сеттера) можно достичь неизменяемости объекта.
При помощи геттеров и сетеров достигается ещё один ключевой принцип ООП —
абстракция, которая скрывает детали реализации, чтобы никто не мог использовать поля непосредственно в других классах или модулях.
Пример метода-геттера и метода-сеттера: сlass Telefon { private int telefon_nomer; private int telefon_balance; // getBalance () – это метод getter, который считывает значение переменной account_balance: public int getBalance () { return this.telefon_balance; } // setNomer () – это метод-сеттер, который устанавливает или обновляет значение переменной telefon_nomer: public void setNomer (int num) { this.telefon_nomer = num; }
}
Правила именования геттеров и сеттеров
Принцип именования геттеров и сеттеров должен соответствовать конвенции Java об именовании:
- Геттеры именуются так:
- «get» + имя переменной, для которой реализуется этот метод с большой буквы (например, getMoney, getCake).
- Если переменная имеет тип boolean, то геттер будет содержать префикс is:
- «is» + имя переменной, для которой реализуется этот метод с большой буквы (например, isMoney, isCake)
- Сеттеры именуются так:
- «set» + имя переменной, для которой реализуется этот метод с большой буквы (например, setMoney, setCake).
Метод геттер не имеет параметров (т.е. в скобках ничего не пишется) и возвращает значение одной переменной (одного поля).
Метод сеттер всегда имеет модификатор void и только один параметр, для изменения значения одного поля.
*Вместо прописывания геттеров и сеттеров вручную можно генерировать их с помощью IDE и различных пакетов.
- Использование геттеров и сеттеров для public-полей делает их бесполезными.
- Если в сеттер передаётся ссылка на объект, то нужно не копировать её во внутреннюю переменную напрямую, а, вместо этого,
делать её копию (например, используя метод copyOf или copyOfRange) и только тогда присваивать её полю. - Возврат геттером ссылки на объект: не возвращайте ссылку на исходный объект в геттере, а возвращайте копию.
- Реализация геттеров и сеттеров для:
- — переменных примитивных типов
- Переменные примитивных типов можно свободно передавать/возвращать прямо в сеттере/геттере, потому что Java автоматически копирует их значения.
- — объектов системных классов
- Как и для примитивных типов, можно безопасно реализовать геттер и сеттер для переменной String.
- (String — это immutable-тип — после создания объекта этого типа, его значение нельзя изменить, и любые изменения будут приводить к созданию нового объекта String)
Для объектов типа Date внешние классы не должны иметь доступ к их оригиналам, т.к. объекты класса java.util.Date являются изменяемыми.
Данный класс реализует метод clone() из класса Object, который возвращает копию объекта, но использовать его для этих целей не стоит.
Поскольку Date не является окончательным классом, нет гарантии, что метод clone() возвратит объект, класс которого именно java.util.Date: он может вернуть экземпляр ненадежного подкласса, созданного специально для нанесения ущерба.
Такой подкласс может, например, записывать ссылку на каждый экземпляр в момент создания последнего в закрытый статический список, а затем предоставить злоумышленнику доступ к этому списку. В результате злоумышленник получит полный контроль над всеми экземплярами копий.
Чтобы предотвратить атаки такого рода, не используйте метод clone() для создания копии параметра, тип которого позволяет ненадежным сторонам создавать подклассы.
- Таким образом для объектов типа Date нужно создавать каждый раз новый экземпляр и работать с ним.
- — коллекций
- Для коллекций из элементов типа String не требуется специальной реализации, так как объекты этого типа неизменяемы (immutable).
- Для коллекции из String'ов одним из решений проблемы, при которой коллекция может быть изменена из кода, находящегося за пределами геттера и сеттера, является использование конструктора, который принимает другую коллекцию в качестве аргумента.
При создании новой коллекции на основе имеющейся, объекты, размещенные в оригинальной коллекции, не будут скопированы. Вместо этого в новую коллекцию будут скопированы только ссылки на эти объекты. И хоть в итоге две коллекции и различны, но содержат одни и те же объекты.
- Для коллекций пользовательских типов данных необходимо учитывать следующие ключевые моменты:
- — самостоятельно реализовать метод clone()
- — в сеттере добавить клонирование элементов из исходной коллекции в конечную
- — в геттере создать новую возвращаемую коллекцию, используя клонирование элементов из исходной коллекции в новую
Так, например, ArrayList, HashMap, HashSet и т. д. реализуют свои собственные методы clone(). Эти методы якобы возвращают копии, но на самом деле копируют лишь ссылки (происходит неглубокое копирование). Об этом прямо написано в Javadoc метода clone() класса ArrayList:
«возвращает копию этого экземпляра ArrayList (сами элементы не копируются)».
Таким образом, нельзя использовать метод clone() этих классов коллекций и необходимо самостоятельно реализовать метод clone().
— собственных классов
Если вы создаете объект своего пользовательского класса, вам следует для него реализовать метод clone().
- Правила реализации геттера и сеттера для собственного класса:
- — Реализуйте метод clone() самостоятельно
- — Возвращайте клонированный объект из геттера
- — Используйте клонированный объект в сеттере