Пакеты и права доступа
Модификаторы доступа public
, private
, protected
Как было рассказано ранее, наличие модификатора public
перед методом или полем означает, что его можно
использовать где угодно, а модификатора private
- что его можно использовать только внутри данного класса. Например:
class A { private int myValue1; public int myValue2; ... void f() { ... myValue1 = 1; // внутри класса можно использовать любое его поле myValue2 = 2; ... A a; a.myValue1; // так тоже можно ... } } ... A a; a.myValue1 = 1; // ошибка компиляции - private поле a.myValue2 = 2; // public поле можно использовать везде
Рассказ о protected. Часть 1.
Если член (поле или метод) класса объявлен с модификатором protected
, то он доступен не только внутри
самого класса, но и внутри всех классов-наследников:
class Base { protected int myValue; ... } class Derived extends Base { ... myValue = 1; Derived d = ...; d.myValue = 2; // это поле тоже можно использовать Base b = ...; b.myValue = 3; // ошибка компиляции ... }
protected
- "прозрачное" для наследников private
поле (метод). Поэтому в данном примере произойдёт ошибка,
причём в независимости от того, каким объектом на самом деле является b
(компилятор этого не может знать).
Большинство ошибок прав доступа выявляются именно на стадии компиляции.
Этот рассказ был бы правильным, если бы в Java существовали только три модификатора прав доступа -
public
, private
и protected
- но это не совсем так.
Пакеты (Packages)
В самом начале мы выяснили, что при создании нового класса мы должны поместить его обязательно в файл с таким же именем, но это не единственная особенность Java, касающаяся организации файлов и папок.
Когда мы до этого писали файл Main.java, мы компилировали его в той же папке, запуская компилятор
строчкой javac Main.java
. Но если наш файл лежит в каком-то другом каталоге (например dir1/dir2),
нам нужно не просто прописать путь в командной строке, но указать это и в самом файле следующим образом:
package dir1.dir2; // директива package может идти только первой строкой в файле public class Main { ... }
Для того, чтобы теперь скомпилировать наш Main.java мы вводим javac dir1/dir2/Main.java
, при этом
создается файл Main.class, лежащий в dir1/dir2. А для того, чтобы запустить наш класс, нужно
ввести строку java dir1.dir2.Main
(все разделители пути превращаются в точки).
Таким образом, имя пакета, указанного в файле, совпадает с относительным путём каталога, в котором он находится. Пакет содержит только те файлы, которые лежат в самом каталоге. Файлы из подкаталогов не являются файлами корневого пакета.
dir1.dir2.Main называется полным именем класса (fully qualified name). При необходимости в программе можно прописывать полные имена классов:
org.amse.np.list.List l = new org.amse.np.list.List;
Но зачастую удобнее импортировать нужный нам класс или группу классов, чтобы использовать их короткие имена:
package org.amse.np.hw10; import org.amse.np.list.List; // импортируем класс List import org.amse.np.dllist.*; // импортируем все классы из пакета dllist (классы подкаталогов не импортируются!) class Main { ... List l = new List(); }
В любой класс всегда импортируются все классы пакета, в котором находится данный класс, и часть стандартной
библиотеки Java (как если бы мы каждый раз писали в начале нашего файла строку import java.lang.*;
),
что позволяет использовать такие стандартные классы и методы как String
, System.out.println()
и т.п.
Пакеты можно использовать, чтобы группировать классы, но также для разделения пространства имен.
Классы с одинаковым коротким именем List
можно различать по именам их пакетов. Поэтому для именования
пакетов пользуются следующими общепринятыми соглашениями:
- рекомендуется всегда использовать package
- все имена package должны начинаться со строчной буквы
- коммерческие организации используют пакеты, начинающиеся с com (например com.borland), остальные - с org (наример org.amse)
Однако package - это не только способ разделения на каталоги, но и важная сущность языка.
Права доступа по умолчанию
Отсутствие явно указанного модификатора прав доступа перед членом класса означает, что этот член будет доступен во всех классах из данного пакета и не доступен вне пакета. Этот уровень доступа также называют package local.
Таким образом, в Java есть всего четыре уровня доступа: public, private, protected и default (package local)
Рассказ о protected. Часть 2
В Java правилом является то, что у метода класса-наследника при перекрытии должны быть права не более ограничительные, чем у метода класса-родителя:
class Base { public void f() { ... } } class Derived extends Base { private void f() { // ошибка прав доступа во время компиляции ... } }
По ограничительности уровни доступа можно распределить так:
private < protected, default < public
Если верить первой части рассказа о protected, то нельзя сказать, какой уровень доступа - protected или default - более ограничительный. Можно придумать случаи, когда будет доступно поле default и не доступно поле protected и наоборот.
Чтобы не возникало неоднозначностей с правами доступа, например при наследовании, в Java protected-уровень включает ещё и default. То есть protected член доступен всем подклассам, плюс классам внутри пакета.
Иерархия уровней доступа выглядит следующим образом:
private < default < protected < public
Таким образом, при использовании модификатора protected мы позволяем всем классам из данного пакета иметь доступ к нашему полю/методу, что в большинстве случаев является не той ситуацией, к которой мы стремимся.
Вложенные классы
Мы рассмотрим вложенные классы лишь вскользь, как ещё один способ ограничения использования классов.
Существует два основных типа вложенных классов: static классы (nested)(?) и просто классы-члены (inner). Общий синтаксис:
class Outer { [static] class Inner { ... } ... }
Достаточно часто применяются вложенные private static классы:
class List { private static class ListElement { ... } }
При компиляции класса с вложенным классом создаются два файла, в данном случае: List.class и List$ListElement.class. При этом полное имя вложенного класса будет выглядеть следующим образом: org.amse.np.list.List.ListElement
class Outer { static class Inner { int myValue; ... } ... Inner i = new Inner(); // экземпляр вложенного класса можно создать внутри самого класса... } ... Outer.Inner i = new Outer.Inner(); // ...или снаружи
Статические вложенные классы ведут себя так же, как обычные классы, но они имеют доступ к другим статическим членам данного класса.
Нестатический вложенный класс привязан не к самому окружающему классу, а к его экземпляру, поэтому
помимо всех своих полей он ещё имеет неявное поле this
, указывающее на конкретный экземпляр, частью
которого является вложенный класс. Такой вложенный класс имеет доступ ко всем полям и методам
окружающего его класса.
class Outer { class Inner { ... } ... Inner i = new Inner(); // создается неявное поле, куда записывается Outer.this } Outer o = ...; Inner i = o.new Inner();
Дополнение: модификаторы доступа для внешних классов
По мимо обычных внешних public классов в Java можно также создавать так называемые local классы. Они могут лежать в одном файле с public классом и являются аналогами вложенных классов.
public class A { ... } class B { ... }
В одном файле может быть только один public класс и сколько угодно local. Файл должен называться так же, как и public класс.