Avatar ·

В чем разница между public, protected, package-private и private в Java?

Всем привет! Я только начал изучать Java и никак не могу разобраться с модификаторами доступа. Вроде бы понимаю, что они контролируют видимость классов, методов и полей, но на практике постоянно путаюсь.

Например, у меня есть такой код:

public class Animal {
    public String name;
    protected int age;
    String breed; // package-private
    private String id;
    
    public void makeSound() {
        System.out.println("Some sound");
    }
    
    protected void sleep() {
        System.out.println("Sleeping...");
    }
    
    void eat() {
        System.out.println("Eating...");
    }
    
    private void breathe() {
        System.out.println("Breathing...");
    }
}

public class Dog extends Animal {
    public void testAccess() {
        name = "Buddy"; // доступно
        age = 5;        // доступно
        breed = "Lab";  // доступно
        // id = "123";  // НЕ доступно - private
        makeSound();    // доступно
        sleep();        // доступно
        eat();          // доступно
        // breathe();   // НЕ доступно - private
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.name = "Test";    // доступно
        // animal.age = 10;      // НЕ доступно - protected (другой пакет)
        // animal.breed = "Mix"; // НЕ доступно - package-private (другой пакет)
        // animal.id = "001";    // НЕ доступно - private
    }
}

Я вижу, что private работает только внутри класса, а public — везде. Но что такое package-private? И чем protected отличается от package-private? Когда что использовать? Объясните, пожалуйста, простыми словами с примерами!

<<>>

Подробное объяснение модификаторов доступа в Java

Модификаторы доступа — это ключевой механизм инкапсуляции в Java. Они определяют, какие части вашего кода могут обращаться к классам, методам и полям. Давайте разберем каждый по порядку, от самого открытого к самому закрытому.

1. public — доступ отовсюду

Самый открытый модификатор. Элемент с public виден всем классам из любых пакетов. Используется для API вашего кода — того, что вы хотите предоставить другим разработчикам.

// Пакет: com.example.api
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

// Пакет: com.other.app
public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        calc.add(2, 3); // Работает! public доступен отовсюду
    }
}

2. protected — доступ в пакете + наследники

Элемент с protected доступен:

  • Внутри того же класса (очевидно)
  • Всем классам внутри того же пакета (package-private +)
  • Всем наследникам (даже если они в других пакетах)
// Пакет: com.example.shapes
public class Shape {
    protected int x, y; // Координаты
}

// Пакет: com.example.graphics
public class Circle extends Shape {
    public void move(int dx, int dy) {
        this.x += dx; // Работает! Наследник видит protected поле
        this.y += dy;
    }
}

// Пакет: com.other
public class Test {
    public static void main(String[] args) {
        Shape s = new Shape();
        // s.x = 5; // ОШИБКА! Не наследник и не тот же пакет
    }
}

3. package-private (по умолчанию) — только в пакете

Если не указан никакой модификатор, это package-private (иногда называют default). Доступен только внутри того же пакета. Даже наследники из другого пакета его не видят!

// Пакет: com.example.utils
class Helper { // package-private класс — виден только в пакете
    String secret = "package-private";
}

// Пакет: com.example.utils
public class User {
    public void test() {
        Helper h = new Helper(); // Работает! Оба в одном пакете
        System.out.println(h.secret); // Работает
    }
}

// Пакет: com.other
public class OtherUser {
    public void test() {
        // Helper h = new Helper(); // ОШИБКА! Helper не виден
        // h.secret — тоже не виден
    }
}

4. private — только внутри класса

Самый строгий модификатор. Доступен только внутри того же класса. Даже наследники не видят private-члены. Именно private обеспечивает настоящую инкапсуляцию.

public class BankAccount {
    private double balance; // Только внутри этого класса
    
    public void deposit(double amount) {
        balance += amount; // Работает
    }
    
    private void logTransaction() {
        System.out.println("Transaction logged");
    }
}

public class SavingsAccount extends BankAccount {
    public void showBalance() {
        // System.out.println(balance); // ОШИБКА! private не виден
        // logTransaction(); // ОШИБКА! private не виден
    }
}

Сводная таблица видимости

Модификатор Внутри класса Внутри пакета Наследник (другой пакет) Любой класс (другой пакет)
public✔✔✔✔
protected✔✔✔✘
package-private✔✔✘✘
private✔✘✘✘

Практические рекомендации

  • private — используйте для полей класса по умолчанию. Всегда скрывайте данные.
  • public — только для методов, которые являются частью API (например, геттеры/сеттеры).
  • package-private — для внутренних утилит, которые не должны выходить за пределы пакета.
  • protected — когда нужно дать доступ наследникам, но скрыть от остальных.

Запомните золотое правило: давайте самый минимальный уровень доступа, который необходим. Это защитит ваш код от непреднамеренного использования и упростит его поддержку.

<<>>

Альтернативный взгляд: модификаторы как уровни изоляции

Представьте себе систему вложенных контейнеров:

  • private — это сейф внутри вашей комнаты. Даже ваши дети (наследники) не могут в него заглянуть без вашего разрешения.
  • package-private — это ваша квартира. Соседи по этажу (классы из того же пакета) могут зайти, но посторонние — нет.
  • protected — это ваша квартира с правом наследования. Ваши дети (наследники) могут зайти, даже если они живут в другом городе (другом пакете).
  • public — это витрина магазина. Любой прохожий может увидеть и потрогать.

На практике часто возникает путаница между protected и package-private. Ключевое отличие: protected "пробивает" границы пакетов для наследников, а package-private — нет. Если вы пишете библиотеку и хотите, чтобы пользователи могли расширять ваши классы (наследовать), используйте protected для методов, которые должны быть доступны наследникам, но не всем подряд.

Ещё один полезный совет: в современных Java-проектах (Java 9+) появилась модульная система, которая добавляет ещё один уровень инкапсуляции — module. Но для начала достаточно освоить эти четыре модификатора. Практикуйтесь, и со временем выбор правильного модификатора станет интуитивным!