Инкапсуляция данных в классе. Управление доступом в 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<=7))
            value = _value;
    }

    // дополнительный метод
    // возвращает название дня недели, которому соответствует value
    public String GetName() {
        String day = "Monday";
        switch (value) {
            case 2: day = "Tuesday"; break;
            case 3: day = "Wednesday"; break;
            case 4: day = "Thursday"; break;
            case 5: day = "Friday"; break;
            case 6: day = "Saturday"; break;
            case 7: day = "Sunday"; break;
        }
        return day;
    }
}

Использовать такой класс в другом коде (методе) можно, например, следующим образом

public class HiddenData {
    public static void main(String[] args) {
        // доступ к private-члену данных класса
        DayWeek dw = new DayWeek();
        int Day;
        String strDay;

        Day = dw.Get(); // Day = 1
        strDay = dw.GetName(); // strDay = "Monday"

        System.out.println("Day = " + Day);
        System.out.println("strDay = " + strDay);

        // установить новое значение дня
        dw.Set(3);

        // проверка
        Day = dw.Get(); // Day = 3
        strDay = dw.GetName(); // strDay = "Wednesday"

        System.out.println("Day = " + Day);
        System.out.println("strDay = " + strDay);
    }
}

Результат выполнения вышеприведенной программы

Day = 1
strDay = Monday
Day = 3
strDay = Wednesday

 

7. Доступ к элементам класса, которые объявлены с модификатором доступа public. Примеры

К элементам класса, которые объявлены с модификатором доступа public есть доступ из любого программного кода пакета, в котором объявлен данный класс.

Если нужно иметь доступ к public-элементам класса из других пакетов, то этот класс должен быть объявлен с модификатором public. Более подробно об особенностях доступа к public-классам из других пакетов описывается в теме:

 

7.1. Пример доступа к public-элементу класса в пределах пакета

Задан класс A с членом данных value, который объявлен с модификатором public. Ниже продемонстрированы три возможных способа доступа к public-члену данных value:

  • из текущего класса A;
  • из класса B, который объявлен в данном пакете;
  • из класса C, который объявлен в данном пакете и есть производным от класса A.

 

// некоторый класс
class A {
    public int value; // переменная, объявленная с модификатором public

    // доступ из метода текущего класса
    void methodA() {
        value = 5;
    }
}

class B {
    // доступ из метода другого класса данного пакета
    void methodB() {
        A objA = new A();
        objA.value = 10;
    }
}

// класс C наследует класс A
class C extends A {
    // доступ из метода производного класса
    void methodC() {
        value = 30;
    }
}

 

7.2. Пример доступа к public-элементу класса из другого пакета

Чтобы иметь доступ к public-элементу класса из другого пакета этот класс может быть также объявлен как public.
Пусть в пакете Package1 задан класс с именем DemoPackage1. В классе объявляется public-переменная value.

// модификатор доступа public, пакет Package1
package Package1;

// Пакет Package1
// public-класс DemoPackage1
public class DemoPackage1 {
    public int value; // эта переменная есть доступна за пределами пакета

    public static void main(String[] args) {

    }
}

Тогда, доступ из другого пакета Package2 к переменной value может быть:

  • с помощью использования экземпляра (объекта) класса Package1.DemoPackage1;
  • с помощью класса, который есть производным от класса Package1.DemoPackage1.
// Пакет Package2
package Package2;

// подключение класса DemoPackage1 из пакета Package1.
import Package1.DemoPackage1;

// некоторый класс в пакете Package2
public class DemoPackage2 {

    public static void main(String[] args) {
        // создание экземпляра класса DemoPackage1,
        // который размещается в пакете Package1 - разрешено
        Package1.DemoPackage1 dp1 = new Package1.DemoPackage1();

        // доступ к public-переменной класса из другого пакета, который объявлен как public
        dp1.value = 30;
    }
}

// класс, который наследует (расширяет) класс DemoPackage1 из пакета Package1
class DemoPackage3 extends DemoPackage1 {
    // метод, который имеет доступ к Package1.DemoPackage1.value
    void IncValue(int _delta) {
        value = value + _delta; // увеличить value на величину delta
    }
}

Следует заметить, если в пакете Package2 размещается директива

import Package1.DemoPackage1;

то к классу DemoPackage1 из пакета Package1 можно обращаться любым из двух способов:

  • с учетом имени пакета: Package1.DemoPackage1;
  • без использования имени пакета перед именем класса: DemoPackage1.

 

8. Доступ к элементам класса, которые объявлены с модификатором доступа protected. Примеры

Рассматривают два случая доступа к protected-элементу класса:

  • в пределах пакета, в котором класс с данным protected-элементом объявлен (в текущем пакете);
  • из другого пакета.

Если в некотором пакете элемент класса объявлен с ключевым словом protected, то:

  • в текущем пакете этот элемент есть видимый из любого кода (также как и public-элемент);
  • в другом пакете этот элемент есть видимым только в производных классах.

 

8.1. Пример доступа к protected-члену данных класса в пределах пакета

Задан класс A, в котором объявлен protected-член данных value. Из данного пакета к элементу класса value можно иметь доступ следующими способами:

  • из текущего класса A;
  • из класса B, который объявлен в данном пакете;
  • из класса C, который объявлен в данном пакете и есть производным от класса A.
// модификатор доступа protected, пакет Package1
package Package1;

// некоторый класс
class A {
    protected int value; // переменная, объявленная с модификатором protected

    // доступ из метода текущего класса
    void methodA() {
        value = 5;
    }
}

class B {
    // доступ из метода другого класса данного пакета
    void methodB() {
        A objA = new A();
        objA.value = 10;
    }
}

// класс C наследует класс A
class C extends A {
    // доступ из метода производного класса
    void methodC() {
        value = 30;
    }
}

Из вышеприведенного кода можно сделать следующий вывод:

  • в пределах пакета доступ к protected-элементам класса такой же как и к public-элементам класса.

 

8.2. Пример доступа к protected-члену данных класса из другого пакета

Для того, чтобы protected-элемент класса был доступен за пределами данного пакета, этот класс обязательно должен быть объявлен с модификатором public.
Чтобы доступиться к protected-элементу класса из классов других пакетов нужно, чтобы эти классы были производными от данного класса.

Рассмотрим это на примере.

Пусть задан пакет с именем Package1. В этом пакете реализован класс DemoPackage1, в котором объявляется protected-переменная value.

// модификатор доступа protected,
// пакет Package1
package Package1;

// public-класс DemoPackage1
public class DemoPackage1 {
    // за пределами пакета эта переменная доступна только в производных классах
    protected int value;

    public static void main(String[] args) {

    }
}

В нижеследующем коде продемонстрирован доступ из другого пакета Package2 к protected-переменной value класса DemoPackage1 (пакет Package1).

// Пакет Package2
package Package2;

// подключение класса DemoPackage1 из пакета Package1.
import Package1.DemoPackage1;

// некоторый класс в пакете Package2
public class DemoPackage2 {
    public static void main(String[] args) {
        // создание экземпляра класса DemoPackage1,
        // который размещается в пакете Package1 - разрешен
        Package1.DemoPackage1 dp1 = new Package1.DemoPackage1();

        // доступ к protected-переменной класса из другого пакета
        // dp1.value = 30; - ЗАПРЕЩЕНО, ошибка
    }
}

// класс, который наследует (расширяет) класс DemoPackage1 из пакета Package1
// этот класс имеет доступ к protected-переменной value
class DemoPackage3 extends DemoPackage1 {
    // метод, который имеет доступ к Package1.DemoPackage1.value
    void IncValue(int _delta) {
        value = value + _delta; // доступ РАЗРЕШЕН
    }
}

Если снять комментарий из строки

dp1.value = 30;

то компилятор выдаст ошибку

The field DemoPackage1.value is not visible

 

9. Особенности доступа к элементам класса в случае отсутствия модификатора доступа. Примеры

При объявлении модификатор доступа к членам класса может отсутствовать. В этом случае к члену класса имеют доступ:

  • методы этого класса;
  • методы производного подкласса, который объявлен в этом самом пакете;
  • методы любого класса, который размещен в этом самом пакете.

Запрещен доступ из других пакетов к элементам класса не имеющим модификатора доступа.

Пример 1. В примере демонстрируется доступ к переменной value класса A из методов других классов. Переменная value класса A объявлена без модификатора доступа.

// пакет Package1
// модификатор доступа отсутствует
package Package1;

class A {
    int value; // переменная, объявленная без модификатора доступа

    // доступ из метода текущего класса
    void method() {
        value = 5; // доступ разрешен
    }
}

class B {
    // доступ из метода другого класса данного пакета
    void method() {
        A obj = new A(); // экземпляр класса A
        obj.value = 10; // доступ разрешен
    }
}

class C extends A {
    // доступ из метода производного класса
    void method() {
        value = 30; // доступ разрешен
    }
}

Пример 2. Демонстрация запрета доступа из другого пакета к переменной value класса DemoPackage1, которая объявлена без модификатора доступа.
Заданы два пакета с именами Package1 и Package2. В пакете Package1 объявляется public-класс с именем DemoPackage1. В этом классе объявляется переменная value без модификатора доступа.
Текст пакета Package1 следующий:

// Пакет Package1
// класс DemoPackage1
public class DemoPackage1 {
    int value; // эта переменная доступна только в этом пакете
    public static void main(String[] args) {

    }
}

В другом пакете Package2 объявляется класс DemoPackage2 и директива import, которая подключает пакет Package1.

// Пакет Package2
package Package2;

// подключение класса DemoPackage1 из пакета Package1.
import Package1.DemoPackage1;

// некоторый класс в пакете Package2
public class DemoPackage2 {
    public static void main(String[] args) {
        // создание экземпляра класса DemoPackage1,
        // который размещается в пакете Package1 - разрешено
        Package1.DemoPackage1 dp1 = new Package1.DemoPackage1();

        // невозможно доступиться к переменной другого пакета,
        // которая объявлена без модификатора
        // dp1.value = 10; - ошибка, доступ к переменной dp1.value ЗАПРЕЩЕН!
    }
}

Как видно из примера, попытка вызвать строку

dp1.value = 10;

из пакета Package2 вызовет ошибку компилятора со следующим сообщением

The field DemoPackage1.value is not visible

Это значит, что поле value класса DemoPackage1 есть невидимым.

 

10. Какой тип доступа имеют члены класса по умолчанию?

Перед объявлением переменной, экземпляру или методу в классе не обязательно указывать модификатор доступа. Если модификатор доступа отсутствует перед объявлением членов класса, то принимается доступ по умолчанию. Для членов класса в Java установлен общедоступный (public) доступ по умолчанию.

 

11. Можно ли объявлять конструктор класса с модификатором доступа private?

Да можно. Такой конструктор никогда не будет вызван за пределами класса, то есть из экземпляра (объекта) класса. Но такой конструктор может быть использован в методах, которые объявляются в самом классе при создании экземпляров этого класса.

Например. Пусть задан класс с именем CData. В классе объявляются:

  • конструктор без параметров с модификатором доступа public;
  • скрытый (private) конструктор с одним параметром. Этот конструктор будет использоваться в методе UseCData(), который объявлен в этом же классе;
  • метод CData(), который использует скрытый (private) конструктор CData с одним параметром.

Текст объявления и использования класса имеет вид:

// класс, который содержит скрытый конструктор
class CData {
    int d; // член данных d имеет доступ public по умолчанию

    // public-конструктор, видимый за пределами класса
    CData() {
        d = 0;
    }

    // скрытый конструктор, видимый только в пределах класса
    private CData(int _d) {
        d = _d;
    }

    // метод, который использует скрытый (private) конструктор класса
    public void UseCData(int _d)
    {
        CData DD = new CData(_d); // использовать private-конструктор класса
        System.out.println("DD = "+DD.d);
    }
}

public class TestClass {
    public static void main(String[] args) {
        // создать экземпляр класса CData
        CData D1 = new CData(); // вызов public-конструктора без параметра

        // Запрещено вызывать private-конструктор класса CData
        //CData D2 = new CData(2);
        // доступ к члену данных d
        D1.d = 30;
        System.out.println("D1.d = " + D1.d);

        D1.UseCData(55); // вывести на экран DD=55
    }
}

Как видно из объявления класса, в методе UseCData() будет вызван скрытый (private) конструктор с одним параметром

...
CData DD = new CData(_d); // использовать private-конструктор класса
...

В результате выполнения вышеприведенного кода, на экран будет выведено:

D1.d = 30
DD = 55

 

Связанные темы