Модификаторы private, protected, public в Java

Модификаторы доступа private, protected, public ставятся перед именем класса, метода или поля и ограничивают доступ к нему. К локальным переменным модификаторы доступа не применимы.
Помимо этих трех явных модификаторов, есть еще так называемый default-модификатор, или модификатор по умолчанию, иначе говоря – это отсутствие всякого модификатора.  Но это отсутствие тоже подразумевает свои правила доступа (видимость только внутри пакета).

Вопросы приветствуются, задавайте их в комментариях.

Зачем нужны модификаторы доступа

Модификаторы доступа существуют для того, чтобы сделать код надежнее и защищеннее. Нужно максимально ограничивать видимость своих классов, методов и полей, и открывать их только там, где это действительно необходимо. Если вы откроете что-то лишнее, то другой разработчик (или даже вы сами) может по ошибке воспользоваться открытым классом/методом. Чем это чревато? А тем, что если в дальнейшем вы исправите свой код (отвечающий за внутреннюю реализацию, но открытый для пользования извне), то код другого программиста перестанет работать, так как опирается на ваш код. Открывать  нужно только то, что вы планируете поддерживать и что будет стабильно работать (без изменения контракта) во всех последующих версиях. Все остальное – внутренняя реализация, которая касается только вас и может меняться, ее никто не должен использовать.

Нормально сделать видимым, например, один класс вашего пакета и только методы, предназначенные для внешнего использования (методы API). Все остальное скрыть. Это называется инкапсуляцией (скрытием реализации).

Правила доступа

На картинке показаны правила доступа к полю или методу с конкретным модификатором (последний столбец – про модули, они появились в Java 9):

Модификаторы доступа в Java
Модификаторы доступа в Java

 

Модификатор private

Это самый ограничивающий модификатор. К полям и методам, помеченным ключевым словом private, можно обратиться только из того же класса, где они находятся.

Допустим у нас есть класс A с private полем privateVar и с private методом privateMethod(). Из класса A мы можем обращаться к полю, см. обращение this.privateVar:

package accessmodifiers.priv;

public class A {
    private int privateVar = 1;

    private void privateMethod() {
        System.out.println("A private method is printing " + this.privateVar);
    }

    public static void main(String[] args) {
        A a = new A();
        a.privateVar = 2;
        a.privateMethod();
    }
}

А теперь попробуем обратиться к этому полю и методу из класса B, код не скомпилируется:

package accessmodifiers.priv;

public class B {
    void testAccess() {
        A a = new A();
        a.privateVar = 10; // illegal
        a.privateMethod(); // illegal
    }
}

Вышеприведенный код выдает ошибки компиляции:

The field A.privateVar is not visible
The method privateMethod() from the type A is not visible

Иногда возникает вопрос

Может ли объект A получить доступ к private методам и полям другого объекта A?

Да, может. Обратите внимание на функцию main() из вышеприведенного класса A, в которой создается новый объект A и идет обращение к его методам и полям (не через this):

public static void main(String[] args) {
    A a = new A();
    a.privateVar = 2;
    a.privateMethod();
}

Как показано выше, мы обращаемся в методе main() к private полю privateVar другого объекта A, и это законно. Все потому, что в Java ограничения доступа применимы на уровне класса, а не на уровне объекта (не обязательно, чтоб обращение шло к тому же экземпляру, главное, что он в том же классе).

В Scala, например, существуют модификаторы доступа на уровне объекта
Можно ли переопределить private метод?

Нельзя, метод в подклассе не будет иметь никакого отношения к методу в суперклассе, так как private метод нигде не виден. Давайте попытаемся унаследоваться от класса A и “переопределить” private метод privateMethod():

public class SubA extends A {
    private void privateMethod() {
        System.out.println("B private method is printing ");
    }
}

Попробуем создать объект SubA и вызвать privateMethod() на A:

A a=new SubA();
a.privateMethod();

Как видите, срабатывает метод privateMethod() класса A, то есть переопределения не происходит:

A private method is printing 2

Это происходит потому, что метод privateMethod() класса SubA не переопределяет метод  privateMethod() класса A, а является независимым методом.

Модификатор default

Если мы не ставим никакого модификатора доступа перед методом, полем или классом,  то этот метод/поле/класс видимы из кода только внутри пакета, в котором они находятся.

Давайте продемонстрируем это. Создадим снова класс A в пакете .def:

package ru.sysout.accessmodifiers.def;

public class A {
    int defaultVar = 1;

    void defaultMethod() {
        System.out.println("A default method is printing " + this.defaultVar );
    }

}

И создадим в этом же пакете класс B, из которого будем пытаться получить доступ к полям и методам A, как и раньше:

package ru.sysout.accessmodifiers.def;

public class B {
    void testAccess() {
        A a = new A();
        a.defaultVar = 10; // legal
        a.defaultMethod(); // legal
    }
}

В этот раз код компилируется, все в порядке – доступ есть.

Если бы класс B находится в другом пакете (отличном от ru.sysout.accessmodifiers.def, в том числе в подпакете), то доступа бы не было.

Модификатор protected

Следующий по строгости – модификатор protected. Он также разрешает доступ к помеченным с  помощью него полям и методам из кода внутри того же пакета. Но помимо этого, он дает поблажки подклассам, находящимся в другом пакете. Подкласс может обращаться к protected полям и методам суперкласса, даже если подкласс находится в другом пакете.

Снова создадим класс A с protected полем и методом:

package ru.sysout.accessmodifiers.prot;

public class A {
    protected int protectedVar = 1;

    protected void protectedMethod() {
        System.out.println("A protected method is printing " + this.protectedVar);
    }

}

Создадим в другом пакете класс C – наследника класса A и попытаемся получить доступ к полям  методам класса A из класса C:

package ru.sysout.accessmodifiers.prot.sub;

import ru.sysout.accessmodifiers.prot.A;

public class C extends A {
    void testAccess(A a, C c) {
        
        this.protectedVar = 1;// legal
        this.protectedMethod();// legal
        
        // a.protectedVar = 10; // illegal
        c.protectedVar = 10; // legal
        // a.protectedMethod(); // illegal
        c.protectedMethod(); // legal
    }
}

Как показано выше, обращение к полю и методу через this работает из другого пакета.

Также работает обращение ко всем другим экземплярам типа C,  но к другим экземплярам типа A обращение не работает.

Модификатор public

Тут все просто – к полю и методу с модификатором public имеет доступ любой код. Давайте еще раз перепишем класс A:

package ru.sysout.accessmodifiers.pub;

public class A {
    public int publicVar = 1;

    public void publicMethod() {
        System.out.println("A public method is printing " + this.publicVar);
    }

}

И обратимся к его полю и методу из класса B, который находится в другом пакете и никакого отношения к классу A не имеет:

package ru.sysout.accessmodifiers.pub.sub;

import ru.sysout.accessmodifiers.pub.A;

public class B {
    void testAccess() {
        A a = new A();
        a.publicVar = 10; // legal
        a.publicMethod(); // legal
    }
}

Все получилось, обращение работает.

Какой модификатор выбрать?

Правило выбора модификатора такое – надо по возможности выбирать:

  1. private
  2. default
  3. protected
  4. public

То есть надо максимально ограничивать видимость члена класса. Сначала надо попробовать сделать все private, и при необходимости открывать видимость.

Итог

Мы рассмотрели тонкости использования модификаторов доступа. Код примеров можно посмотреть на GitHub.

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *