Почему переменные экземпляра в Java всегда закрыты?

Я новичок в Java, изучаю инкапсуляцию и видел пример, в котором переменные экземпляра объявляются как частные в классе.

http://www.tutorialspoint.com/java/java_encapsulation.htm

У меня есть 2 запроса:

  1. Почему переменные экземпляра закрыты? Почему не публично?
  2. Что, если переменные экземпляра станут общедоступными и доступны напрямую? Видим ли мы какие-либо ограничения?

Можете ли вы объяснить на примере, что пойдет не так, если переменные экземпляра будут объявлены общедоступными в классе в Java?


person Deepak    schedule 01.10.2011    source источник
comment
Можете ли вы объяснить на примере, что пойдет не так, если переменные экземпляра будут объявлены общедоступными в классе Java?   -  person Deepak    schedule 02.10.2011
comment
Изображение, если поле никогда не должно быть пустым. Если у вас есть метод setter, вы можете проверить null и бросить и IllegalArgumentException, если null когда-либо будет передано. Однако, если поле public, то любой пользователь вашего класса может установить для поля любое значение, включая null. Тогда ваш код может получить NullPointerException, потому что он всегда ожидал, что поле никогда не будет null.   -  person John B    schedule 02.10.2011
comment
В конце ссылки, которую вы разместили, есть раздел «Преимущества инкапсуляции», на который было бы полезно взглянуть.   -  person Eva    schedule 23.07.2012
comment
Они не всегда закрыты. Обычно так, но не всегда.   -  person user207421    schedule 10.04.2017


Ответы (8)


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

Использование методов позволит вам, например, ограничить доступ только для чтения, т. е. поле может быть прочитано, но не записано, если нет установщика. Это было бы невозможно, если бы поле было общедоступным.

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

Если phone был закрытым:

Рассмотрим этот случай:

class Address {
  private String phone;

  public void setPhone(String phone) {
    this.phone = phone;
  }
}

//access:
Address a = new Address();
a.setPhone("001-555-12345");

Если бы мы начали с такого класса, а позже потребовалось бы выполнять проверки phoneNumber (например, некоторую минимальную длину, только цифры и т. д.), вам просто нужно было бы изменить установщик:

class Address {
  private String phone;

  public void setPhone(String phone) {
    if( !isValid( phone) ) { //the checks are performed in the isValid(...) method
     throw new IllegalArgumentException("please set a valid phone number");
    }

    this.phone = phone;
  }
}

//access:
Address a = new Address();
a.setPhone("001-555-12345"); //access is the same

Если phone был общедоступным:

Кто-то мог установить phone вот так, и вы ничего не могли с этим поделать:

Address a = new Address();
a.phone="001-555-12345";

Если теперь вы хотите принудительно выполнить проверки проверки, вам придется сделать это приватным, и тот, кто написал вышеуказанные строки, должен будет изменить вторую строку на это:

a.setPhone("001-555-12345");

Таким образом, вы не могли просто добавить проверки, не нарушая остальной код (он больше не будет компилироваться).

Кроме того, если вы получаете доступ ко всем полям/свойствам класса с помощью методов, вы сохраняете доступ согласованным, и пользователю не придется беспокоиться о том, хранится ли свойство (т. е. поле экземпляра) или вычисляется (есть только методы и нет полей экземпляра). ).

person Thomas    schedule 01.10.2011
comment
Томас, не могли бы вы просмотреть свой пример. Поскольку я думаю, что телефон String в первом фрагменте кода должен быть общедоступным.!!!! - person Deepak; 02.10.2011
comment
Мне кажется, все нормально, с оговоркой в ​​комментарии if phone was public. - person user; 02.10.2011
comment
@Deepak Я переформатировал ответ, чтобы прояснить условие: пример показывает, как он должен выглядеть (phone является частным), а последняя часть каждого блока должна показывать, что могло бы произойти, если бы он был общедоступным. - person Thomas; 02.10.2011
comment
›Это было бы невозможно, если бы поле было общедоступным. - Как насчет модификатора final? - person Display Name; 20.07.2013
comment
@SargeBorsch вы можете использовать поле public final в качестве константы только для чтения, но во многих случаях это поле не является константой. Часто только для чтения влияет только на интерфейс, т. е. пользователи объекта не могут изменять поле. Однако внутри объект может обновить поле, например. путем выполнения некоторых вычислений, что было бы невозможно с final полями. - person Thomas; 22.07.2013
comment
Почему необходимо заставлять пользователей этих классов использовать методы для доступа к ним? Почему Address a = new Address(); тел=001-555-12345; плохой? - person Nayana; 05.02.2016
comment
@Наяна, ты прочитала весь мой ответ? На самом деле я уже ответил на это, но повторюсь: если вы позже решите добавить некоторую проверку (скажем, номер телефона должен соответствовать шаблону xxx-xxx-xxxxx), вы можете добавить это в сеттер вместо изменения каждого места в вашем коде, который напрямую вызывает a.phone="whatever" (обратите внимание, что то, что является заполнителем и не означает, что вы всегда используете литерал, это также может быть вводом пользователем). - person Thomas; 05.02.2016

Они не должны обязаны быть частными, но должны быть ими. Поле — это деталь реализации, поэтому его следует держать в секрете. Если вы хотите разрешить пользователям извлекать или устанавливать его значение, вы используете для этого свойства (методы получения и установки) — это позволяет вам делать это безопасно (например, проверять ввод), а также позволяет изменять детали реализации (например, чтобы делегировать некоторые значения другим объектам и т. д.) без потери обратной совместимости.

person Jon Skeet    schedule 01.10.2011
comment
Привет, Джон. Не могли бы вы привести пример. Что, если я сделаю это общедоступным. - person Deepak; 02.10.2011
comment
@Deepak: Тогда это означает, что вы теряете большую часть контроля над ним - вы не можете изменить его имя или способ его использования, безопасность потоков и т. Д. В будущем, потому что остальной мир может просто читать его, когда им нравится. Вы не можете сделать его изменяемым внутри, также позволяя всем остальным изменять его без проверки и т. д. Для конечных переменных это не так же плохо, но все же не очень хорошая идея IMO . - person Jon Skeet; 02.10.2011
comment
Привет, Джон. Не могли бы вы предоставить пример фрагмента кода, где он терпит неудачу. Это сделало бы его более понятным. - person Deepak; 02.10.2011
comment
@Deepak, например, он дает сбой, как только у вас есть какой-то другой код, использующий переменную экземпляра (деталь реализации), и вы хотите что-то изменить в нем (реализация). Имея средства доступа и мутаторы (геттеры и сеттеры), вы можете свободно изменять реализацию любым удобным для вас способом, пока вы поддерживаете контракт общедоступного интерфейса типа. - person user; 02.10.2011
comment
@Deepak: Дело не в сбое кода, а в том, что это плохой дизайн. - person Jon Skeet; 02.10.2011

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

Общая идея инкапсуляции заключается в том, что класс не должен раскрывать свое внутреннее состояние. Он должен использовать его только для выполнения своих методов. Причина в том, что каждый класс имеет так называемое «пространство состояний». То есть набор возможных значений для его полей. Он может управлять своим пространством состояний, но если он выставит его напоказ, другие могут перевести его в недопустимое состояние.

Например, если у вас есть два логических поля, и класс может нормально работать только в 3 случаях: [false, false], [false, true] и [true, false]. Если вы сделаете поля общедоступными, другой объект может установить [true, true], не зная внутренних ограничений, и следующий метод, вызванный для исходного объекта, вызовет неожиданные результаты.

person Bozho    schedule 01.10.2011
comment
, Можете ли вы объяснить пример для этого оператора, но если он раскрывает его, другие могут поместить его в недопустимое состояние. - person Deepak; 02.10.2011

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

Технический документ Oracle

person rwyland    schedule 01.10.2011

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

Я видел пример в (как мне кажется) Clean Code, который очень хорошо это иллюстрирует. Если я правильно помню, это был тип комплексного числа (как в a+bi); во всяком случае, что-то очень похожее, у меня нет книги под рукой. Он предоставил методы для получения значения реальной и мнимой частей, а также метод для установки значения экземпляра. Большим преимуществом этого является то, что он позволяет полностью заменить реализацию без нарушения каких-либо потребителей кода. Например, комплексные числа можно хранить в одной из двух форм: в виде координат на комплексной плоскости (a+bi) или в полярной форме (φ и |z|). Сохранение внутреннего формата хранения в качестве детали реализации позволяет вам переключаться туда и обратно, по-прежнему отображая число в обеих формах, что позволяет пользователю класса выбирать то, что более удобно для операции, которую они в настоящее время выполняют.

В других ситуациях у вас может быть набор связанных полей, например, поле x должно иметь определенные свойства, если поле y попадает в заданный диапазон. В упрощенном примере x должно быть в диапазоне от y до y+z для числовых значений и некоторого произвольного значения z. Предоставляя средства доступа и мутаторы, вы можете установить эту связь между двумя значениями; если вы выставите переменные экземпляра напрямую, инвариант немедленно развалится, поскольку вы не можете гарантировать, что кто-то не установит одну, а другую, или не установит их так, что инвариант больше не будет выполняться.

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

person user    schedule 01.10.2011

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

В последние несколько лет (к лучшему или к худшему) стало более популярным использовать анемичные модели данных, где класс — это не что иное, как набор полей и соответствующих геттеров и сеттеров. Я бы сказал, что в этом дизайне вам было бы лучше использовать общедоступные поля, поскольку геттеры и сеттеры не обеспечивают реальной инкапсуляции, а просто обманывают вас, заставляя думать, что вы делаете настоящий ООП.

ОБНОВЛЕНИЕ: пример, приведенный в ссылке в вопросе, является прекрасным примером этой вырожденной инкапсуляции. Я понимаю, что автор пытается предоставить простой пример, но при этом не может передать никаких реальных преимуществ инкапсуляции (по крайней мере, не в коде примера).

person James Scriven    schedule 01.10.2011

  1. Потому что если вы измените структуру класса (удалив поля и т.д.); это вызовет ошибки. Но если у вас есть метод getX(), вы можете вычислить нужное значение там (если поле было удалено).

  2. У вас проблема в том, что класс не знает, изменилось ли что-то, и не может гарантировать целостность.

person MasterCassim    schedule 01.10.2011

Удержание полей в тайне имеет много преимуществ, как было предложено выше. Следующий лучший уровень - сохранить их пакет закрытым, используя уровень доступа Java по умолчанию.

Уровень по умолчанию позволяет избежать беспорядка в вашем собственном коде и не позволяет клиентам вашего кода устанавливать недопустимые значения.

person Mangoose    schedule 20.07.2013