Пакеты и права доступа
Модификаторы доступа 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 класс.
