Хочу продолжить делиться приобретенными знаниями и своими впечатлениями от подготовки к экзамену. Огромное спасибо всем тем, кто дал рекомендации к нулевой части этой серии! Сегодня я поговорю еще немножко о модификаторах доступа и их взаимоотношениях с наследованием и пакетами, рассмотрю varargs и перечисления, а также массивы и способы их инициализации. Я надеюсь, что хабражители снова откликнутся и дополнят то, о чем я забыл упомянуть или попросту не знал.

Продолжаем готовиться к экзамену под катом.

Методы, поля, локальные переменные и их модификаторы

Как я уже говорил, в Java существуют четыре модификатора доступа: public, private, protected и отсутствие модификатора (он же модификатор по умолчанию). К невложенным классам и интерфейсам применимы только два из них: public и модификатор по умолчанию. К методам и полям класса применим весь набор.

  1. Если метод или поле имеют модификатор public, то они потенциально доступны всей вселенной.
  2. Если метод или поле имеют модификатор доступа private, то они доступны только в рамках класса. Такие члены класса не наследуются, поэтому их невозомжно заместить в подклассах. Помните об этом.
  3. Если метод или поле имеют модификатор доступа по умолчанию, то они доступны только в рамках пакета.
  4. Если метод или поле имеют модификатор доступа protected, то они, прежде всего, доступны самому классу и его наследникам. Кроме того, доступ к этим членам класса могут получить их собратья по пакету.

Проверяя предложенный в рамках экзамена код, следует быть аккуратным. Всегда обращайте внимание как на модификатор доступа метода или поля, так и на модификатор доступа класса. Часто можно встретить ситуацию, когда метод имеет модификатор public, в то время как класс, его содержащий, доступен только из пакета. В этой ситуации метод из вне пакета доступен не будет. Можно легко получить минус, не обратив на эту деталь внимания.

Хочу также обратить внимание на некоторые особенности, которые возникают при использовании доступа по умолчанию и модификатора protected. Рассмотрим следующуий пример. Пусть имеется базовый класс, объявленный в пакете test. Этот класс обладает двумя полями. Первое объявлено с доступом по умолчанию, второе - protected.

Package org.kimrgrey.scjp.test; public class BaseClass { int defaultValue; protected int protectedValue; public BaseClass() { this.defaultValue = 1; this.protectedValue = 1; } }

Если объявить в этом пакете класс SamePackageAccess, который не будет наследоваься от BaseClass, то он все равно получит доступ и к полю defaultValue, и к полю protectedValue. Об этой особенности модификатора protected стоит помнить: члены класса, объявленные как protected, в рамках пакета доступны как через наследование, так и через ссылку. Пример:

Package org.kimrgrey.scjp.test; public class SamePackageAccess { public SamePackageAccess() { BaseClass a = new BaseClass(); a.defaultValue = 2; a.protectedValue = 2; } }

В случае с наследованием в этом пакете доступ по-прежнему сохраняется к обоим полям, причем как по ссылке, так и через наследование.

Package org.kimrgrey.scjp.test; public class SamePackageSubclass extends BaseClass { public SamePackageSubclass() { this.defaultValue = 3; this.protectedValue = 3; BaseClass a = new BaseClass(); a.defaultValue = 3; a.protectedValue = 3; } }

Теперь давайте посмотрим, что будет, если мы выйдем за пределы пакета. Первое, что случится - мы потеряем доступ к полю, объявленному без явного указания модификатора доступа. Его не будут видеть абсолютно все классы вне родного пакета, в том числе и прямые наследники BaseClass. Поле же с модификатором protected будет доступно через наследование всем своим подклассам. Однако даже наследник не сможет его использовать через ссылку. Кроме того, будучи однажды унаследованным классом вне пакета, поле становится закрытым для любых классов, за исключением дальнейших наследников.

Package org.kimrgrey.scjp.main; import org.kimrgrey.scjp.test.BaseClass; public class OtherPackageSubclass extends BaseClass { public OtherPackageSubclass() { this.defaultValue = 10; // Line 8: не получим доступ, потому что другой пакет this.protectedValue = 10; BaseClass a = new BaseClass(); a.protectedValue = 10; // Line 12: по ссылке не могут обращаться даже наследники BaseClass } }

В этом примере содержится также еще одна важная деталь. Предположим, вас спрашивают, что же случиться если скомпилировать приведенный выше код? И дают следующие варианты ответа:

  1. Код будет успешно скомпилирован
  2. Возникнет ошибка компиляции на строке с номером 8
  3. Возникнет ошибка компиляции на строке с номером 12
В этом случае, если явно не указано обратное, выбрать нужно все правильные варианты ответов: 2 и 3, - а не останавливаться на первом подходящем. Внимательно читайте все ответы и проверяйте их на корректность. Именно этот подход работает лучше всего. Прочитав и поняв суть вопроса, проверяйте и анализируйте именно ответы, а не код, приведенный в формулировке.

Среди модификаторов, связанных с наследованием, следует также рассмотреть final. На методы final действует также, как на классы: запрещает их переопределение наследниками. При этом расширять сам класс, в котром находится final метод, по-прежнему можно.

Разрешается применять модификатор final к полям, аргументам методов и локальным переменным. В случае примитивных типов будет запрещено любое изменение значения переменной, кромее ее инициализации. Тут следует помнить, что моментом инициализации локальных переменных считается первое присваивание им значения в рамках метода. До этого переменную использовать нельзя: получите ошибку при компиляции. Помеченное final поле также придется явным образом инициализировать. Это можно сделать либо непосредственно при объявлении, в инициализационном блоке, либо в конструкторе того класса, в котором оно объявлено. Оставлять инициализацию final полей на совести наследников не разрешается. В случае ссылок модификатор final запретит переприсваивать ссылку. Сам объект, на который ссылка указывает, все еще можно изменять: вызывать изменяющие его состояния методы, присваивать полям новое значение и так далее.

Важно помнить, что к локальным переменным неприменимы никакие модификаторы, кроме final. Поэтому, если вы видите в объявлении локальной переменной что-то вроде private int a , то можно смело говорить, что это не скомпилируется. А что же с полями?

  1. К полям, как я уже говорил, применимы все четыре уровня доступа.
  2. Поле может быть помечено как final.
  3. Поле может быть помечено как transient.
  4. Поле может быть помечено как static.
  5. Поле может быть помечено как volatile.
  6. Поле не может быть помечено как abstract.
  7. Поле не может быть помечено как synchronized.
  8. Поле не может быть помечено как strictfp.
  9. Поле не может быть помечено как native.
Некоторые из модификаторов, упомянутых выше, я раньше не описывал. Постараюсь рассмотреть их позже, в соответсвующих темах (transient будет рассмотрен в рамках сериализации, а synchronized и volatile - в многопоточности).

Методы с переменным количеством аргументов

  1. Когда вы указываете параметр vararg, то базовым типом может быть любой тип: примитивный или нет.
  2. Чтобы объявить такой параметр, вы пишите тип, потом три точки, пробел, затем имя массива, который будет использоваться в рамках метода: void f (int... a) . Можно также разделить тип, три точки и идентифкаторы пробелами, так: void f(int ... a) . Внимательно следите за точками. Авторы экзамена любят переносить их за идентификатор. Такой подход не работает.
  3. В метод могут передаваться другие параметры, но в этом случае параметр vararg должен быть последним: void f (double x, int... a)
  4. В методе может быть один и только один vararg параметр.
Для наглядности приведу хороший пример вопроса по этой теме. Выберите такое объявление метода doSomething(), чтобы приведенный ниже код был удачно скомпилирован?

Package org.kimrgrey.scjp.main; public class Application { public static void main(String args) { doSomething(1); doSomething(1, 2); } }

  1. static void doSomething(int... values) {}
  2. static void doSomething(int values) {}
  3. static void doSomething(int x, int... values) {}

Правильными являются первый и третий варианты. И тот, и другой корректны как с точки семантики вызова, так и с точки зрения синтаксиса. Использовать же массив как тип для передачи нескольких параметров так просто не получится. А вот обратное не верно. Пример:

Package org.kimrgrey.scjp.main; public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { System.out.println(a[i]); } } public static void main(String args) { f(new int {1, 2 ,3}); } }

Все чудесно соберется и отработает. Формального объяснения этому я не знаю, но предполагаю, что это связано с тем, что vararg-параметр является всего лишь синтаксическим сахаром и воспринимается компилятором как ссылка на массив, поэтому никаких проблем не возникает.

Перечисления

  1. У перечислений могут быть конструкторы.
  2. У перечислений могут быть поля.
  3. У перечислений могут быть методы.
  4. Если перечисление объявляется вне класса, оно может получить только два уровня доступа: public или по умолчанию.
  5. У перечислений есть статический метод values() , который возвращает массив, содержащий все возможные значения перечисления, причем строго в том порядке, в котором они были объявлены.
Для каждого из значений в рамках перечисления вы можете объявить свое собственное «тело» - его специфическое описание. При этом специфичные для значения версии методов перегружают вариант, который используется для всего перечисления в целом. Это позволяет менять поведение членов перечисления в зависимости от нужд приложения. Пример:

Package org.kimrgrey.scjp.main; import static java.lang.System.*; enum Currency { UNKNOWN, USD { public String getStringCode() { return "USD"; } public int getSomethingElse() { return 10; } }, UAH { public String getStringCode() { return "UAH"; } }, RUR { public String getStringCode() { return "RUR"; } }; public String getStringCode() { return ""; } } public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { out.println(a[i]); } } public static void main(String args) { out.println(Currency.USD.getStringCode()); // out.println(Currency.USD.getSomethingElse()); } }

В результате выполения этого кода в стандартный поток вывода будет помещена строка «USD». Обратите внимание на метод getSomethingElse(). Он объявлен для значения USD, однако не упоминается для всего перечисления. Не смотря на то, что в объявлении стоит public, никто из вне доступ к этому методу получить не сможет. Если строку за номером 44 раскомментировать, то код даже не скомпилируется.

Немного о массивах

В Java допустимы два варианта объявления массивов. Квадратные скобки могут быть размещены после имени типа, так: int a , - или после идентификатора, так: int a . Важно понимать, что оба способа абсолютно равноправны с точки зрения синтаксиса, хотя первый из них и является рекомендуемым. Таким образом, String s - это ни что иное, как двумерный массив строк. Скомпилируется без вопросов.

При объявлении массива нельзя указать его размер, так как память выделяется только в момент создания массива: int a = new int . Поэтому код int a вызовет ошибку компиляции. В случае с массивом ссылок на объекты важно помнить, что при создании массива сами объекты не создаются. К примеру, код Thread threads = new Thread создаст массив из двадцати null"ов, никаких конструкторов вызываться не будет.

При построении многомерных массивов о них нужно думать, как о массивах, каждый элемент которых ссылается снова на массив. Абстрактные конструкции вроде матриц и кубов упрощают программирование, но могут усложнить сдачу экзамена. К примеру, конструкция int a = new int вполне допустима и создаст двумерный массив, элементы которого могут быть проинициализированы позже: a = new int , - причем совсем не обязательно массивами равной длины: a = new int .

Для того, чтобы проинициализировать массив быстро (не элемент за элементом), можно применять синтаксис вроде этого: int x ={1, 2, 3} . В фигурных скобках могут стоять не только константы, но и переменные и даже выражения. Можно также создавать анонимные массивы, что часто используется когда нужно передать строго определенный массив в функцию: f(new int {2, 4, 8}) . Если вы видите на экзамене такую конструкцию, то обязательно присмотритесь внимательнее. Есть вероятность, что будет написано что-то вроде этого: f(new int {2, 4, 8}) . Такой код не будет скомпилирован, так как размер анонимного массива вычисляется исходя из его объявления и не должен указываться явным образом.

На этом я закончу на сегодня. В ближайшее время обязательно поговорим об особенностях некоторых важных операций в Java (присваивание, сравнение, instanceOf, арифметика), а также о неявных классах и потоках.

Последнее обновление: 20.04.2018

Все члены класса в языке Java - поля и методы, свойства - имеют модификаторы доступа. В прошлых темах мы уже сталкивались с модификатором public . Модификаторы доступа позволяют задать допустимую область видимости для членов класса, то есть контекст, в котором можно употреблять данную переменную или метод.

В Java используются следующие модификаторы доступа:

    public : публичный, общедоступный класс или член класса. Поля и методы, объявленные с модификатором public, видны другим классам из текущего пакета и из внешних пакетов.

    private : закрытый класс или член класса, противоположность модификатору public. Закрытый класс или член класса доступен только из кода в том же классе.

    protected : такой класс или член класса доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах

    Модификатор по умолчанию . Отсутствие модификатора у поля или метода класса предполагает применение к нему модификатора по умолчанию. Такие поля или методы видны всем классам в текущем пакете.

Рассмотрим модификаторы доступа на примере следующей программы:

Public class Program{ public static void main(String args) { Person kate = new Person("Kate", 32, "Baker Street", "+12334567"); kate.displayName(); // норм, метод public kate.displayAge(); // норм, метод имеет модификатор по умолчанию kate.displayPhone(); // норм, метод protected //kate.displayAddress(); // ! Ошибка, метод private System.out.println(kate.name); // норм, модификатор по умолчанию System.out.println(kate.address); // норм, модификатор public System.out.println(kate.age); // норм, модификатор protected //System.out.println(kate.phone); // ! Ошибка, модификатор private } } class Person{ String name; protected int age; public String address; private String phone; public Person(String name, int age, String address, String phone){ this.name = name; this.age = age; this.address = address; this.phone = phone; } public void displayName(){ System.out.printf("Name: %s \n", name); } void displayAge(){ System.out.printf("Age: %d \n", age); } private void displayAddress(){ System.out.printf("Address: %s \n", address); } protected void displayPhone(){ System.out.printf("Phone: %s \n", phone); }}

В данном случае оба класса расположены в одном пакете - пакете по умолчанию, поэтому в классе Program мы можем использовать все методы и переменные класса Person, которые имеют модификатор по умлчанию, public и protected. А поля и методы с модификатором private в классе Program не будут доступны.

Если бы класс Program располагался бы в другом пакете, то ему были бы доступны только поля и методы с модификатором public.

Модификатор доступа должен предшествовать остальной части определения переменной или метода.

Инкапсуляция

Казалось бы, почему бы не объявить все переменные и методы с модификатором public , чтобы они были доступны в любой точке программы вне зависимости от пакета или класса? Возьмем, например, поле age, которое представляет возраст. Если другой класс имеет прямой доступ к этому полю, то есть вероятность, что в процессе работы программы ему будет передано некорректное значение, например, отрицательное число. Подобное изменение данных не является желательным. Либо же мы хотим, чтобы некоторые данные были достуны напрямую, чтобы их можно было вывести на консоль или просто узнать их значение. В этой связи рекомендуется как можно больше ограничивать доступ к данным, чтобы защитить их от нежелательного доступа извне (как для получения значения, так и для его изменения). Использование различных модификаторов гарантирует, что данные не будут искажены или изменены не надлежащим образом. Подобное сокрытие данных внутри некоторой области видимости называется инкапсуляцией .

Так, как правило, вместо непосредственного использования полей, как правило, используют методы доступа. Например:

Public class Program{ public static void main(String args) { Person kate = new Person("Kate", 30); System.out.println(kate.getAge()); // 30 kate.setAge(33); System.out.println(kate.getAge()); // 33 kate.setAge(123450); System.out.println(kate.getAge()); // 33 } } class Person{ private String name; private int age; public Person(String name, int age){ this.name = name; this.age = age; } public String getName(){ return this.name; } public void setName(String name){ this.name = name; } public int getAge(){ return this.age; } public void setAge(int age){ if(age > 0 && age < 110) this.age = age; } }

И затем вместо непосредственной работы с полями name и age в классе Person мы будем работать с методами, которые устанавливает и возвращают значения этих полей. Методы setName, setAge и наподобие еще называют мьютейтерами (mutator), так как они изменяют значения поля. А методы getName, getAge и наподобие называют аксессерами (accessor), так как с их помощью мы получаем значение поля.

Причем в эти методы мы можем вложить дополнительную логику. Например, в данном случае при изменении возраста производится проверка, насколько соответствует новое значение допустимому диапазону.

Мы поговорим о модификаторах: какие бывают модификаторы, области видимости, модификаторы для классов, полей, методов. Думаю, будет не скучно.

Модификаторы в Java – это ключевые слова, которые придают классу, полю класса или методу определенные свойства.

Для обозначения видимости класса его методов и полей есть 4 модификатора доступа:

  • private члены класса доступны только внутри класса;
  • package-private или default (по умолчанию) члены класса видны внутри пакета;
  • protected члены класса доступны внутри пакета и в классах-наследниках;
  • public члены класса доступны всем.

Если Вы помните , то в конце, когда мы уже импортировали класс Cat, у нас все равно была ошибка компиляции.

Все дело в том, что мы не прописали никаких модификаторов доступа к нашим полям и методам и они имеют свойство по умолчанию (члены класса видны внутри пакета). Чтобы исправить ошибку компиляции для нашего кода и наконец то запустить его, нужно сделать наш конструктор и методы public. Тогда их можно будет вызывать с других пакетов.

Вы можете начать задаваться вопросом: а для чего все это нужно? Почему не сделать видимость кода из любого пакета или класса, а нужно разграничить доступ? Эти вопросы сами пропадут, когда придет время писать сложные и громоздкие проекты. Сейчас, когда мы пишем приложения, у которых функционал ограничен одним или двумя классами, то смысла что либо ограничить вроде как не видно.

Представьте, что у Вас есть класс который отображает объект некоего продукта. Например машина. У машины может быть цена. Вы создали поле цена и еще множество других полей, кучу методов которые отвечают за функционал. Все вроде хорошо. Ваш класс машина является частью огромного проекта и все довольны. Но допустим, что кто-то по ошибке или специально создал экземпляр класса автомобиль и поставил отрицательную цену. Разве может товар иметь отрицательную цену? Это очень примитивный пример и вряд ли такое может случиться в реальной жизни, но думаю, идея понятна. Иногда нужно дать доступ не напрямую, а через определенные методы. Может быть, что код отвечает за функционал другого кода, и Вы не хотите, чтобы кто-то изменял и редактировал часть Вашего. Для этого всего и есть ограничение доступа.

Модификатор доступа у конструкторов, методов и полей может быть любой. Класс может быть только либо public, либо default, причем в одном файле может находиться только один public класс.

Пока об модификаторах доступа будет достаточно. В статье «Объектно ориентированное программирование» мы о них поговорим подробнее, а сейчас давайте поговорим о других модификаторах которых, к стати, немало.

Сейчас на очереди модификатор static . Его можно применять перед методом, полем и даже классом, когда хотим объявить вложенный класс. В Java можно писать классы внутри других классов и если модификатор перед классом внутри класса static, то такой класс называют вложенным, если другой модификатор или по умолчанию, то такой класс называется внутренним. О вложенных и внутренних классах будет отдельная статья, поскольку там не все так просто.

static модификатор перед методом или полем говорит о том, что они не принадлежат к экземпляру данного класса. Что это означает для нас? Когда мы описали поле класса или метод как static, его можно вызвать без использования экземпляра класса. То есть вместо такой конструкции: Cat cat = new Cat(); cat.method(), можно написать просто Cat.method(). При условии, что метод объявлен как static. Статические переменные едины для всех объектов класса. У них одна ссылка.

    public class Modificators {

    static int anotherStaticField = 5 ;

    public static void myStaticMethod() {

    someField = "My field" ;

    //nonStaticField = ""; ошибка компиляции

    //нельзя использовать нестатические поля

    //в статических методах

    public void myNonStaticMethod() {

    anotherStaticField = 4 ; //ститические поля можно использовать

    //в нестатических методах

    //main метод тоже имеет модификатор static

    new Modificators() .myNonStaticMethod () ;

    Modificators.myStaticMethod () ; //вызов статических методов и полей

    //через имяКласса.метод

Еще одно важное замечание, которое нужно сказать по поводу static модификаторов: статические поля инициализируются во время загрузки класса. Часто в разного рода тестах по Java можно встретить такой код:

Вопрос: что будет выведено на консоль? Нужно помнить, что static блок будет выведен первым при любом раскладе. Далее будет идти блок по умолчанию. Далее смотрите на скрин консоли:

Следующий модификатор, который мы рассмотрим будет final.

Думаю, слово final говорит само за себя. Применяя final модификатор Вы говорите, что поля не могут быть изменены, методы переопределены, а классы нельзя наследовать (о наследовании будет отдельная статья). Этот модификатор применяется только к классам, методам и переменным (также и к локальным переменным).

С модификатором final к методам и классам мы будем говорить в статье ООП.

Далее пойдут модификаторы, которые новичкам или читающим данный цикл статей с нуля будут не очень понятными. И хотя я пока не смогу Вам все объяснить (в силу того, что Вы не знаете сопутствующего материала), все же советую просто ознакомиться с ними. Когда придет время использования данных модификаторов, Вы уже будете понимать большинство терминов используемых ниже.

Модификатор synchronized — говорит о том, что метод может быть использован только одним потоком одновременно. Хотя, возможно, это Вам ни о чем не говорит, полезность этого модификатора будет видно, когда мы будем изучать многопоточность.

Модификатор transient — говорит о том, что во время сериализации объекта некоторое поле нужно игнорировать. Как правило, такие поля хранят промежуточные значения.

Модификатор volatile — используется при многопоточности. Когда поле с модификатором volatile будет использоваться и изменяться несколькими потоками, данный модификатор гарантирует, что поле будет изменяться по очереди и путаницы с ним не возникнет.

Модификатор native перед объявлением метода указывает что метод написан на другом языке программирования. Обычно на языке C.

Модификатор strictfp — Обеспечивает выполнение операций над числами типа float и double (с плавающей запятой) по стандарту IEEE 754. Или говоря проще, гарантирует что в пределах метода результаты вычислений будут одинаковыми на всех платформах.

Я еще не говорил о модификаторе abstract . О нем скажу вкратце, так как без знаний основ объектно ориентированного программирования говорить о нем не вижу смысла.

Класс, который имеет модификатор abstract не может создать экземпляр. Единственная цель для него быть расширенным. Класс abstract может содержать как абстрактные методы, а также и обычные.

Подробнее о модификаторе abstract будем говорить в статье ООП.

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

Привет! В сегодняшней лекции мы познакомимся с понятием «модификаторы доступа » и рассмотрим примеры работы с ними. Хотя слово «познакомимся» будет не совсем правильным: с большинством из них ты уже знаком по предыдущим лекциям. На всякий случай освежим в памяти главное. Модификаторы доступа - это чаще всего ключевые слова, которые регулируют уровень доступа к разным частям твоего кода. Почему «чаще всего»? Потому что один из них установлен по умолчанию и не обозначается ключевым словом:) Всего в Java есть четыре модификатора доступа. Перечислим их в порядке от самых строгих до самых «мягких»:

  • private;
  • protected;
  • default (package visible);
  • public.
Давай рассмотрим каждый из них, определимся, когда они могут нам пригодиться и приведем примеры:)

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

Private - наиболее строгий модификатор доступа. Он ограничивает видимость данных и методов пределами одного класса. Этот модификатор тебе известен из лекции про геттеры и сеттеры. Помнишь этот пример? public class Cat { public String name; public int age; public int weight; public Cat (String name, int age, int weight) { this . name = name; this . age = age; this . weight = weight; } public Cat () { } public void sayMeow () { System. out. println ("Мяу!" ) ; } } public class Main { public static void main (String args) { Cat cat = new Cat () ; cat. name = "" ; cat. age = - 1000 ; cat. weight = 0 ; } } Мы рассматривали его в одном раньше. Здесь мы допустили серьезную ошибку: открыли наши данные, в результате чего коллеги-программисты получили доступ напрямую к полям класса и изменению их значения. Более того, эти значения присваивались без проверок, в результате чего в нашей программе можно создать кота с возрастом -1000 лет, именем «» и весом 0. Для решения этой проблемы мы использовали геттеры и сеттеры , а также ограничили доступ к данным с помощью модификатора private . public class Cat { private String name; private int age; private int weight; public Cat (String name, int age, int weight) { this . name = name; this . age = age; this . weight = weight; } public Cat () { } public void sayMeow () { System. out. println ("Мяу!" ) ; } public String getName () { return name; } public void setName (String name) { this . name = name; } public int getAge () { return age; } public void setAge (int age) { this . age = age; } public int getWeight () { return weight; } public void setWeight (int weight) { this . weight = weight; } } Собственно, ограничение доступа к полям и реализация геттеров-сеттеров - самый распространенный пример использования private в реальной работе. То есть реализация инкапсуляции в программе - главное предназначение этого модификатора. Это касается не только полей, кстати. Представь, что в твоей программе существует метод, который реализует какую-то ОЧЕНЬ сложную функциональность. Что бы придумать такого для примера… Скажем, твой метод readDataFromCollider() принимает на вход адрес с данными, считывает данные из Большого Адронного Коллайдера в байтовом формате, преобразует эти данные в текст, записывает в файл и распечатывает его. Даже описание метода выглядит жутковато, что уж говорить про код:) Чтобы повысить читаемость кода, было бы хорошо не писать сложную логику метода в одном месте, а наоборот - разбить функциональность на отдельные методы. Например, метод readByteData() отвечает за считывание данных, convertBytesToSymbols() конвертирует считанные с коллайдера данные в текст, saveToFile() сохраняет полученный текст в файл, а printColliderData() - распечатывает наш файл с данными. Метод readDataFromCollider() в итоге стал бы намного проще: public class ColliderUtil { public void readDataFromCollider (Path pathToData) { byte colliderData = readByteData (pathToData) ; String textData = convertBytesToSymbols (colliderData) ; File fileWithData = saveToFile (textData) ; printColliderData (fileWithData) ; } public byte readByteData (Path pathToData) { // считывает данные в байтах } public String convertBytesToSymbols (byte colliderDataInBytes) { } public File saveToFile (String colliderData) { } public void printColliderData (File fileWithColliderData) { // печатает данные из файла } } Однако, как ты помнишь из лекции про интерфейсы, пользователь получает доступ только к конечному интерфейсу. А наши 4 метода не являются его частью. Они вспомогательные : мы создали их, чтобы улучшить читаемость кода и не засовывать четыре разные задачи в один метод. Давать доступ пользователю к этим методам не нужно. Если у пользователя при работе с коллайдером появится доступ к методу convertBytesToSymbols() , он скорее всего просто не поймет, что это за метод и зачем нужен. Какие байты конвертируются? Откуда они взялись? Зачем их конвертировать в текст? Логика, которая выполняется в этом методе, не является частью интерфейса для пользователя. Только метод readDataFromCollider() - часть интерфейса. Что же делать с этими четырьмя «внутренними» методами? Правильно! Ограничить доступ к ним модификатором private . Так они смогут спокойно выполнять свою работу внутри класса и не вводить в заблуждение пользователя, которому логика каждого из них по отдельности не нужна. public class ColliderUtil { public void readDataFromCollider (Path pathToData) { byte colliderData = readByteData (pathToData) ; String textData = convertBytesToSymbols (colliderData) ; File fileWithData = saveToFile (textData) ; printColliderData (fileWithData) ; } private byte readByteData (Path pathToData) { // считывает данные в байтах } private String convertBytesToSymbols (byte colliderDataInBytes) { // конвертирует байты в символы } private File saveToFile (String colliderData) { // сохраняет считанные данные в файл } private void printColliderData (File fileWithColliderData) { // печатает данные из файла } }

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

Следующий по строгости модификатор доступа - protected . Поля и методы, обозначенные модификатором доступа protected , будут видны:
  • в пределах всех классов, находящихся в том же пакете, что и наш;
  • в пределах всех классов-наследников нашего класса.
Сходу трудно представить, когда это может понадобиться. Не удивляйся: случаев применения protected гораздо меньше, чем private , и они специфические. Представь, что у нас есть абстрактный класс AbstractSecretAgent , обозначающий секретного агента какой-то спецслужбы, а также пакет top_secret , в котором лежит этот класс и его наследники. От него наследуются конкретные классы - FBISecretAgent , MI6SecretAgent , MossadSecretAgent и т.п. Внутри абстрактного класса мы хотим реализовать счетчик агентов. При создании где-то в программе нового объекта-агента он будет увеличиваться. package top_secret; public abstract class AbstractSecretAgent { public static int agentCount = 0 ; } Но агенты-то у нас секретные! А значит, об их числе должны знать только они и никто другой. Мы легко можем добавить модификатор protected к полю agent_counter , и тогда получить его значение смогут либо объекты других классов секретных агентов, либо те классы, которые расположены в нашем «секретном» пакете top_secret . public abstract class AbstractSecretAgent { protected static int agent_counter = 0 ; } Вот для таких специфических задач и нужен модификатор protected:)

Модификатор package visible

Дальше у нас по списку идет модификатор default или, как его еще называют, package visible . Он не обозначается ключевым словом, поскольку установлен в Java по умолчанию для всех полей и методов. Если написать в твоем коде - int x = 10 … у переменной x будет этот самый package visible доступ. Запомнить, что он делает, легко. По сути, default = protected -наследование:) Случаи его применения ограничены, как и у модификатора protected . Чаще всего default -доступ используется в пакете, где есть какие-то классы-утилиты, не реализующие функциональность всех остальных классов в этом пакете. Приведем пример. Представь, что у нас есть пакет «services ». Внутри него лежат различные классы, которые работают с базой данных. Например, есть класс UserService , считывающий данные пользователей из БД, класс CarService , считывающий из этой же БД данные об автомобилях, и другие классы, каждый из которых работает со своим типом объектов и читает данные о них из базы. package services; public class UserService { } package services; public class CarService { } Однако легко может случиться ситуация, когда данные в базе данных лежат в одном формате, а нам они нужны в другом. Представь, что дата рождения пользователя в БД хранится в формате TIMESTAMP WITH TIME ZONE... 2014 - 04 - 04 20 : 32 : 59.390583 + 02 ...нам вместо этого нужен самый простой объект - java.util.Date . Для этой цели можем создать внутри пакета services специальный класс Mapper . Он будет отвечать за конвертацию данных из базы в привычные нам Java-объекты. Простой вспомогательный класс. Обычно мы создаем все классы как public class ClassName , но это не обязательно. Мы можем объявить наш вспомогательный класс просто как class Mapper . В таком случае он все равно делает свою работу, но не виден никому за пределами пакета services ! package services; class Mapper { } package services; public class CarService { Mapper mapper; } А это, по сути, правильная логика: зачем кому-то за пределами пакета видеть вспомогательный класс, работающий только с классами этого же пакета?

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

И последний по списку, но не по значимости - модификатор public ! С ним ты познакомился в первый день учебы на JavaRush, впервые в жизни запустив public static void main(String args) . Теперь, когда ты изучил лекции об интерфейсах, для тебя очевидно его предназначение:) Ведь public создан для того, чтобы отдавать что-то пользователям. Например, интерфейс твоей программы. Допустим, ты написал программу-переводчик, и она умеет переводить русский текст в английский. Ты создал метод translate(String textInRussian) , внутри которого реализована необходимая логика. Этот метод ты отметил словом public , и теперь он станет частью интерфейса: public class Translator { public String translate (String textInRussian) { // переводит текст с русского на английский } } Можно связать вызов этого метода с кнопкой «перевести» на экране программы - и все! Кто угодно может этим пользоваться. Части кода, помеченные модификатором public , предназначаются для конечного пользователя. Если привести пример из жизни, private - это все процессы, происходящие внутри телевизора, когда он работает, а public - это кнопки на пульте телевизора, с помощью которых пользователь может им управлять. При этом ему не нужно знать как устроен телевизор и за счет чего он работает. Пульт - это набор public -методов: on() , off() , nextChannel() , previousChannel() , increaseVolume() , decreaseVolume() и т.д.

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