Подтвердить что ты не робот

Почему конечное ключевое слово необходимо для неизменяемого класса?

Не могли бы вы пояснить, почему ключевое слово final требуется перед классом, когда мы делаем его неизменным. Я имею в виду, если мы объявим все атрибуты как частные, так и окончательные, то также это неизменный класс, не так ли?

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

Editted: Я знаю, что класс, объявленный final, не может быть подклассифицирован. Но если каждый атрибут является приватным и окончательным, то какая разница делает это?

4b9b3361

Ответ 1

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

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

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

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

public class Employee extends Person {
    private String company;

    public Employee(String name, String company) {
        super(name);
        this.company = company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public String getCompany() {
        return company; 
    }
}

Теперь экземпляры Employee небезопасны для обмена между потоками, потому что они не являются неизменяемыми. Но код, выполняющий общий доступ, может знать только о них как экземпляры Person... приводящие их к ложному пониманию безопасности.

То же самое касается кэширования - безопасно кэшировать и повторно использовать неизменяемые типы, верно? Ну, безопасно кэшировать экземпляры, которые поистине неизменяемы, но если вы имеете дело с типом, который сам не допускает мутации, но позволяет подклассы, он внезапно не безопасен.

Подумайте о java.lang.Object. У него нет каких-либо изменчивых полей, но, очевидно, это плохая идея рассматривать каждую ссылку Object, как если бы она ссылалась на неизменяемый тип. В основном это зависит от того, думаете ли вы о неизменности как свойства типа или объектов. Настоящий неизменный тип объявляет "когда вы видите ссылку этого типа, вы можете рассматривать его как неизменяемый", тогда как тип, который позволяет произвольное подклассирование, не может сделать это утверждение.

В стороне есть дом на полпути: если вы можете ограничить подклассификацию только "доверенными" местами, вы можете обеспечить, чтобы все неизменяемое, но все же позволяло это подклассу. Доступ в Java делает это сложным, но в С#, например, вы можете иметь открытый класс, который разрешал только подклассы в рамках одной и той же сборки, предоставляя публичный API, который является хорошим и сильным с точки зрения неизменности, но при этом допускает преимущества полиморфизма.

Ответ 2

Класс, объявленный окончательным, не может быть подклассом. См. Также http://docs.oracle.com/javase/tutorial/java/IandI/final.html

Разная семантика всех применений ключевого слова final описана в Спецификация языка Java

  • 4.12.4 final Variables Page 80
  • 8.1.1.2 final Classes Page 184
  • 8.3.1.2 окончательные поля Page 209
  • 8.4.3.3 окончательные методы Page 223

Ответ 3

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

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

Ответ 4

Вам не нужно окончательно делать неизменный класс. т.е. вы можете сделать неизменный класс, не будучи окончательным.

Однако, если вы не сделаете его окончательным, то кто-то может расширить класс и создать подкласс, который изменен (либо путем добавления новых изменяемых полей, либо переопределения методов таким образом, чтобы вы могли мутировать защищенные поля исходного неизменяемого класса). Это потенциальная проблема - она ​​нарушает Принцип замещения Лискова в том смысле, что вы ожидали сохранения свойства нерушимости всеми подтипами.

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

Ответ 5

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

Неизменяемые объекты несут некоторые очень желательные характеристики:

they are simple to understand and easy to use
they are inherently thread-safe: they require no synchronization
they make great building blocks for other objects 

Ясно, что окончательное решение поможет нам определить неизменяемые объекты. Во-первых, обозначение нашего объекта как непреложное, что упрощает его использование и понимание другими программистами. Во-вторых, гарантируя, что состояние объекта никогда не изменяется, что позволяет использовать потокобезопасное свойство: проблемы с потоком concurrency имеют значение, когда один поток может изменять данные, а другой поток считывает одни и те же данные. Поскольку неизменяемый объект никогда не изменяет свои данные, синхронизация доступа к нему не требуется.

Создайте неизменяемый класс, выполнив следующие условия:

Declare all fields private final.
Set all fields in the constructor.
Don't provide any methods that modify the state of the object; provide only getter methods (no setters).
Declare the class final, so that no methods may be overridden.
Ensure exclusive access to any mutable components, e.g. by returning copies.

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

Ответ 6

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

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

В качестве альтернативы выполнению такого рода неизменности версии bastardized, у вас есть несколько вариантов.

Если вы хотите добавить дополнительные данные в неизменяемый экземпляр, используйте Map. Например, если вы хотите добавить возраст для имени, вы бы не сделали class NameAge extends String...: -)

Если вы хотите добавить методы, создайте класс статических функций утилиты. Это немного klunky, но это текущий Java-способ, например, Apache commons заполнен такими классами.

Если вы хотите добавить дополнительные методы и данные, создайте класс-оболочку с методами делегирования методам неизменяемого класса. Любой, кто нуждается в дополнительных методах, должен знать о них в любом случае, и нет много практических различий в литье на производный не-непреложный класс или что-то вроде new MyWrapper(myImmutableObj) для многих случаев использования.

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

Ответ 7

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

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

Основной вопрос при принятии решения о том, должен ли запечатываться неизменяемый класс, тот же, что и для любого другого класса: не могут ли преимущества оставить тип незапечатанным перевесить любые опасности, которые могут возникнуть в результате этого. В некоторых случаях может иметь смысл иметь расширяемый неизменяемый класс или даже абстрактный класс или интерфейс, чьи конкретные реализации все подряд обязаны быть неизменными; например, пакет чертежа может иметь класс ImmutableShape с некоторыми конкретными полями, свойствами и методами для определения двумерных преобразований, но абстрактный метод Draw, позволяющий определять типы производных ImmutablePolygon, ImmutableTextObject, ImmutableBezierCurve и т.д. Если кто-то реализует класс ImmutableGradientFilledEllipse, но не может сделать этот тип собственной копией изменчивого GradientColorSelector, цвета полигонов с заполненным градиентом могут неожиданно измениться, но это будет ошибкой ImmutableGradientFilledEllipse, а не код потребления. Несмотря на возможность неудачной реализации, не поддерживающей контракт "непреложности", расширяемый класс ImmutableShape был бы гораздо более универсальным, чем запечатанный.