Какой смысл «финального класса» в Java?

569

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

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

Если Java является объектно-ориентированным, и вы объявляете класс final, разве это не останавливает идею класса, имеющего характеристики объектов?

новичок
источник

Ответы:

531

Прежде всего, я рекомендую эту статью: Java: когда создавать финальный класс


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

finalКласс просто класс , который не может быть продлен .

(Это не означает, что все ссылки на объекты класса будут действовать так, как если бы они были объявлены как final.)

Когда полезно объявить класс как окончательный, это описано в ответах на этот вопрос:

Если Java является объектно-ориентированным, и вы объявляете класс final, разве это не останавливает идею класса, имеющего характеристики объектов?

В каком-то смысле да.

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

aioobe
источник
39
Чтобы добавить к ответу, один из принципов эффективной Java - отдать предпочтение композиции перед наследованием. Использование ключевого слова final также помогает реализовать этот принцип.
Ригги
9
«Вы делаете это в основном из соображений эффективности и безопасности». Я слышу это замечание довольно часто (даже Википедия утверждает это), но я до сих пор не понимаю причины этого аргумента. Неужели кто-то хочет объяснить, как, скажем, неокончательная java.lang.String оказалась бы неэффективной или небезопасной?
MRA
27
@MRA Если я создаю метод, который принимает строку в качестве параметра, я предполагаю, что он неизменен, потому что строки являются. В результате я знаю, что могу безопасно вызывать любой метод объекта String и не изменять передаваемый String. Если бы я расширил String и изменил реализацию подстроки, чтобы изменить фактическую String, то объект String, который вы ожидали сделать неизменным, больше не будет неизменным.
Cruncher
1
@Sortofabeginner И как только вы скажете, что хотите, чтобы все методы и поля String были окончательными, просто чтобы вы могли создать некоторый класс с дополнительными функциями ... В этот момент вы могли бы просто создать класс, который имеет строку и создавать методы, которые работают с этой строкой.
Cruncher
1
@Shay final (среди прочего) используется для того, чтобы сделать объект неизменным, поэтому я бы не сказал, что они не имеют ничего общего друг с другом. Смотрите здесь docs.oracle.com/javase/tutorial/essential/concurrency/...
Celeritas
184

В Java элементы с finalмодификатором не могут быть изменены!

Это включает в себя финальные классы, финальные переменные и финальные методы:

  • Последний класс не может быть расширен ни одним другим классом
  • Окончательная переменная не может быть переназначена другое значение
  • Последний метод не может быть переопределен
andyqee
источник
40
Фактический вопрос - почему , а не что .
Франческо Мензани
8
Утверждение «В Java элементы с finalмодификатором не могут быть изменены!» Слишком категорично и, на самом деле, не совсем правильно. Как сказал Грэди Буч: «Объект имеет состояние, поведение и идентичность». Хотя мы не можем изменить идентификатор объекта после того, как его ссылка была помечена как окончательная, у нас есть шанс изменить его состояние , назначив новые значения его неполе final(при условии, конечно, что они есть). Любой, кто При планировании получения Oracle Java Certification (например, 1Z0-808 и т. д.) следует помнить об этом, поскольку на экзамене могут возникнуть вопросы по этому аспекту ...
Игорь Судакевич
33

Один сценарий, в котором важен final, когда вы хотите предотвратить наследование класса по соображениям безопасности. Это позволяет вам убедиться, что выполняемый вами код не может быть переопределен кем-либо.

Другой сценарий для оптимизации: мне кажется, я помню, что компилятор Java включает некоторые вызовы функций из конечных классов. Итак, если вы вызываете a.x()и объявляется a final, мы знаем во время компиляции, каким будет код, и можем встроить его в вызывающую функцию. Я понятия не имею, действительно ли это сделано, но с финалом это возможно.

Шей Рожанский
источник
7
Встраивание, как правило, выполняется только во время выполнения компилятором. Он также работает без final, но JIT-компилятору нужно немного больше поработать, чтобы убедиться, что нет расширяющих классов (или что эти расширяющие классы не затрагивают этот метод).
Paŭlo Ebermann
Хорошее описание проблемы встраивания и оптимизации можно найти здесь: lemire.me/blog/archives/2014/12/17/…
Джош Хеманн
24

Лучший пример

открытый финальный класс String

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

javadeveloper
источник
Хе-хе, иногда это защищает разработчиков Rube Goldergian от самих себя.
Зойдберг
16

Соответствующее чтение: принцип Открытого и закрытого Боба Мартина.

Ключевая цитата:

Программные объекты (классы, модули, функции и т. Д.) Должны быть открыты для расширения, но закрыты для модификации.

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

Шон Патрик Флойд
источник
6
@Sean: не объявляя это finalделает класс закрытым для расширения, а не открытым? Или я воспринимаю это слишком буквально?
Горан Йович
4
@ Горан, применяя окончательно, да. Ключ заключается в том, чтобы выборочно применять final в местах, где вы не хотите модифицировать (и, конечно, чтобы обеспечить хорошие возможности для расширения)
Шон Патрик Флойд
26
В OCP «модификация» относится к модификации исходного кода, а «расширение» относится к наследованию реализации. Следовательно, использование finalобъявления класса / метода не имеет смысла, если вы хотите, чтобы код реализации был закрыт для модификации, но открыт для расширения посредством наследования.
Рожерио
1
@Rogerio Я заимствовал ссылку (и интерпретацию) из Spring Framework Reference (MVC) . ИМХО в этом гораздо больше смысла, чем в оригинальной версии.
Шон Патрик Флойд
Расширение умерло. Бесполезный. Косит. Разрушенный. Мне плевать на OCP. Там никогда не оправдание, чтобы продлить класс.
Джош Вудкок
15

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

Здесь нет нарушения принципов ОО, финал просто обеспечивает хорошую симметрию.

На практике вы хотите использовать final, если хотите, чтобы ваши объекты были неизменяемыми, или если вы пишете API, чтобы сигнализировать пользователям API о том, что класс просто не предназначен для расширения.

biziclop
источник
13

Само ключевое слово finalозначает, что что-то является окончательным и не должно быть изменено каким-либо образом. Если класс помечен, finalон не может быть расширен или подклассифицирован. Но вопрос в том, почему мы отмечаем класс final? ИМО есть разные причины:

  1. Стандартизация: некоторые классы выполняют стандартные функции, и они не предназначены для изменения, например, классы, выполняющие различные функции, связанные с манипуляциями со строками или математическими функциями и т. Д.
  2. Причины безопасности : Иногда мы пишем классы, которые выполняют различные функции аутентификации и паролей, и мы не хотим, чтобы кто-то еще их изменял.

Я слышал, что класс маркировки finalповышает эффективность, но, честно говоря, я не мог найти этот аргумент, чтобы иметь большой вес.

Если Java является объектно-ориентированным, и вы объявляете класс финальным, разве это не останавливает идею класса, имеющего характеристики объектов?

Возможно, да, но иногда это является целью. Иногда мы делаем это для достижения большей пользы от безопасности и т. Д., Жертвуя способностью этого класса расширяться. Но последний класс все еще может расширять один класс, если это необходимо.

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

akhil_mittal
источник
6

Будьте осторожны, когда вы делаете класс "выпускной". Потому что, если вы хотите написать модульный тест для финального класса, вы не можете создать подкласс этого финального класса, чтобы использовать технику разрушения зависимостей «Метод подкласса и переопределения», описанную в книге Майкла С. Фезерса «Эффективная работа с устаревшим кодом» , В этой книге Перья сказал: «Серьезно, легко поверить, что запечатанная и окончательная ошибка является ошибочной, что их никогда не следовало добавлять в языки программирования. Но настоящая ошибка лежит на нас. Когда мы напрямую зависим от библиотеки, которые находятся вне нашего контроля, мы просто напрашиваемся на неприятности ".

Бен Ву
источник
6

final class можно избежать взлома публичного API при добавлении новых методов

Предположим, что в версии 1 вашего Baseкласса вы делаете:

public class Base {}

и клиент делает:

class Derived extends Base {
    public int method() { return 1; }
}

Затем, если в версии 2 вы хотите добавить methodметод для Base:

class Base {
    public String method() { return null; }
}

это сломало бы клиентский код.

Если бы мы использовали final class Baseвместо этого, клиент не смог бы наследовать, и добавление метода не сломало бы API.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
5

Если класс помечен final, это означает, что структура класса не может быть изменена ничем внешним. Где это наиболее заметно, это когда вы делаете традиционное полиморфное наследование, в основном class B extends Aпросто не сработает. Это в основном способ защитить некоторые части вашего кода некоторой степени) .

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

Эско
источник
1
Что означают свойства объекта? Означает ли это, что я могу изменить переменную-член класса, если класс объявлен как final? Таким образом, единственная цель финального класса - предотвратить наследование.
Адам Лю
5

РЕШЕНИЕ ЗАДАЧИ ЗАКЛЮЧИТЕЛЬНОГО КЛАССА:

Есть два способа сделать урок финальным. Первый - использовать ключевое слово final в объявлении класса:

public final class SomeClass {
  //  . . . Class contents
}

Второй способ сделать класс final - объявить все его конструкторы как частные:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

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

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

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

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

mel3kings
источник
4

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

Предотвращение подкласса класса может быть особенно полезным, если вы пишете API или библиотеки и хотите избежать расширения для изменения базового поведения.

JuanZe
источник
4

Одно из преимуществ сохранения класса как финального:

Класс String остается окончательным, чтобы никто не мог переопределить его методы и изменить функциональность. Например, никто не может изменить функциональность метода length (). Он всегда будет возвращать длину строки.

Разработчик этого класса не хотел, чтобы кто-то изменил функциональность этого класса, поэтому он сохранил его как окончательный.

Рахул
источник
3

Да, иногда вы можете этого хотеть, либо по соображениям безопасности, либо по соображениям скорости. Это сделано также в C ++. Это не может быть , что применимо для программ, но в большей степени , за рамки. http://www.glenmccl.com/perfj_025.htm

Джеймс
источник
3

В Java окончательное ключевое слово используется для случаев ниже.

  1. Конечные переменные
  2. Окончательные методы
  3. Финальные Занятия

В java конечные переменные не могут переназначаться, конечные классы не могут расширяться, а конечные методы не могут переопределяться.

Кавинда Пушпита
источник
1

Финальные занятия не могут быть продлены. Поэтому, если вы хотите, чтобы класс вел себя определенным образом, а кто-то не переопределял методы (возможно, с менее эффективным и более вредоносным кодом), вы можете объявить весь класс как финальные или особые методы, которыми вы не хотите быть. изменилось.

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

skhaapioloir
источник
1

думайте о ФИНАЛЕ как о «конце линии» - этот парень больше не может производить потомство. Поэтому, когда вы видите это таким образом, вы обнаружите множество реальных сценариев, которые требуют пометить маркер «конца строки» для класса. Это доменный дизайн - если ваш домен требует, чтобы данный ENTITY (класс) не мог создавать подклассы, пометьте его как FINAL.

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

Лучший подход - взглянуть на предметную область и позволить ей диктовать свои дизайнерские решения.

Kingz
источник
1

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

Пример: путь к файлу сервера приложений для загрузки / выгрузки, разбиение строки на основе смещения, такие методы вы можете объявить как Final, чтобы эти функции метода не были изменены. И если вам нужны такие конечные методы в отдельном классе, определите этот класс как класс Final. Таким образом, в классе Final будут все методы final, а метод Final может быть объявлен и определен в классе non-final.

лок
источник
1

Класс Android Looper является хорошим практическим примером этого. http://developer.android.com/reference/android/os/Looper.html

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

jaamit
источник
1

Допустим, у вас есть Employeeкласс, у которого есть метод greet. Когда greetметод вызывается, он просто печатает Hello everyone!. Так что это ожидаемое поведение по greetметоду

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Теперь позвольте GrumpyEmployeeподклассу Employeeи переопределить greetметод, как показано ниже.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Теперь в приведенном ниже коде посмотрите на sayHelloметод. Он принимает Employeeэкземпляр в качестве параметра и вызывает метод greet, надеясь, что он скажет, Hello everyone!но то, что мы получаем, это Get lost!. Это изменение в поведении происходит из-заEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Этой ситуации можно избежать, если Employeeкласс был сделан final. Только представьте, сколько хаоса может вызвать дерзкий программист, если StringClass не объявлен как final.

Энди
источник
1

Финальный класс не может быть продлен дальше. Если нам не нужно делать класс наследуемым в Java, мы можем использовать этот подход.

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

Г. Кит
источник
-1

Ориентация объекта - это не наследование, а инкапсуляция. И наследование нарушает инкапсуляцию.

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

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

Джошуа Блох в «Эффективной Java» рекомендует явно разрабатывать наследование или запрещать его, и отмечает, что проектирование наследования не так просто.

Вильгельм Мейньян
источник