Який сенс "заключного класу" на Java?


569

Я читаю книгу про Java, і вона говорить про те, що ви можете оголосити весь клас як final. Я нічого не можу придумати, де б це використав.

Я просто новачок у програмуванні, і мені цікаво, чи програмісти насправді використовують це у своїх програмах . Якщо вони це роблять, коли вони ним користуються, щоб я міг краще зрозуміти це і знати, коли ним користуватися.

Якщо Java об'єктно орієнтована, і ви оголошуєте клас final, чи не зупиняє це уявлення про клас, що має характеристики об'єктів?

Відповіді:


531

Перш за все, я рекомендую цю статтю: Java: Коли створити заключний клас


Якщо вони це роблять, коли вони ним користуються, щоб я міг краще зрозуміти це і знати, коли ним користуватися.

finalКлас просто клас , який не може бути продовжений .

(Це не означає, що всі посилання на об'єкти класу діятимуть так, ніби вони були оголошені як final.)

Коли корисно оголосити клас остаточним, висвітлено відповіді на це запитання:

Якщо Java об'єктно орієнтована, і ви оголошуєте клас final, чи не зупиняє це уявлення про клас, що має характеристики об'єктів?

У певному сенсі так.

Позначивши клас як остаточний, ви відключите потужну та гнучку функцію мови для цієї частини коду. Деякі класи, однак, не повинні (а в деяких випадках не можуть ) бути розроблені так, щоб добре враховувати підкласи. У цих випадках має сенс маркувати клас як остаточний, навіть якщо він обмежує OOP. (Однак пам’ятайте, що підсумковий клас все ще може поширити інший нефінальний клас.)


39
Щоб додати відповідь, одним із принципів Ефективної Java є надання переваги композиції над успадкуванням. Використання кінцевого ключового слова також допомагає виконувати цей принцип.
Riggy

9
"Ви робите це переважно з міркувань ефективності та безпеки". Я чую це зауваження досить часто (про це говорить навіть Вікіпедія), але все ще не розумію міркувань цього аргументу. Хтось не хоче пояснити, як, скажімо, некончаткова java.lang.String закінчилася б неефективною чи небезпечною?
MRA

27
@MRA Якщо я створюю метод, який приймає рядок як параметр, я вважаю, що він незмінний, тому що це рядки. У результаті цього я знаю, що я можу безпечно викликати будь-який метод на об'єкті String, а не змінювати передану String. Якщо я хотів би розширити String і змінити реалізацію підрядки, щоб змінити фактичну String, то об'єкт String, який ви очікували бути незмінним, більше не змінюється.
Cruncher

1
@Sortofabeginner І як тільки ти скажеш, що хочеш, щоб усі String-методи та поля були остаточними, просто так, щоб ти міг створити якийсь клас з додатковою функціональністю ... У цей момент ти можеш просто створити клас, який має-string та створити методи, що працюють на цьому рядку.
Cruncher

1
@Shay final (серед іншого) використовується для того, щоб зробити об'єкт непорушним, тому я б не сказав, що вони не мають нічого спільного один з одним. Дивіться тут docs.oracle.com/javase/tutorial/essential/concurrency/…
Celeritas

184

У Java елементи з finalмодифікатором неможливо змінити!

Сюди входять заключні класи, кінцеві змінні та кінцеві методи:

  • Кінцевий клас не може бути продовжений жодним іншим класом
  • Кінцевій змінній не можна перепризначити інше значення
  • Кінцевий метод не можна перекрити

40
Актуальне питання - чому , а не що .
Франческо Мензані

8
Заява "У Java елементи з finalмодифікатором змінити не можна!", Занадто категоричне і, власне, не зовсім правильне. Як заявив Грейді Бух, "Об'єкт має стан, поведінку та ідентичність". Хоча ми не можемо змінити ідентичність об’єкта, як тільки його посилання буде позначено як остаточне, у нас є шанс змінити його стан , присвоївши нові значення його finalполям (за умови, звичайно, він має їх). Кожен, хто плануючи отримати сертифікат Oracle Java (наприклад, 1Z0-808 тощо), слід пам’ятати про це, оскільки на іспиті можуть виникнути запитання щодо цього аспекту ...
Ігор Судакевич

33

Один сценарій, коли остаточний важливий, коли ви хочете запобігти успадкуванню класу з міркувань безпеки. Це дозволяє вам переконатися, що код, який ви використовуєте, не може перекрити хтось.

Інший сценарій - це оптимізація: я, мабуть, пам’ятаю, що компілятор Java містить ряд викликів функцій з останніх класів. Отже, якщо ви зателефонували, a.x()а а оголошено final, ми під час компіляції знаємо, який буде код і може бути вбудований у функцію виклику. Я поняття не маю, чи це насправді зроблено, але з остаточним - це можливість.


7
Встановлення, як правило, виконується лише тимчасовим компілятором під час виконання. Він працює і без остаточного, але JIT-компілятору належить зробити трохи більше роботи, щоб бути впевненим у відсутності розширених класів (або що ці розширені класи не торкаються цього методу).
Paŭlo Ebermann

Хороший запис про проблему вбудовування та оптимізації можна знайти тут: lemire.me/blog/archives/2014/12/17/…
Джош

24

Найкращий приклад - це

публічний заключний клас String

який є незмінним класом і не може бути розширений. Звичайно, є багато іншого, ніж зробити так, щоб фінал класу був незмінним.


Хе-хе, іноді це захищає розробників Rube Goldergian від самих себе.
Зойдберг

16

Відповідне читання: Відкритий принцип Боб Мартін.

Ключова цитата:

Елементи програмного забезпечення (класи, модулі, функції тощо) повинні бути відкритими для розширення, але закритими для модифікації.

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


6
@Sean: Чи не оголошує це finalробити клас закритим для розширення, а не відкритим? Або я сприймаю це занадто буквально?
Горан Йович

4
@Goran глобально застосовує фінал, так. Головне - вибіркове застосування фіналу в місцях, де ви не хочете модифікувати (і, звичайно, забезпечити хороші гачки для розширення)
Шон Патрік Флойд

26
В OCP "модифікація" відноситься до зміни вихідного коду, а "розширення" відноситься до успадкування реалізації. Тому використання finalв оголошенні класу / методу не має сенсу, якщо ви хочете, щоб код реалізації був закритий для модифікації, але відкритий для розширення за спадщиною.
Rogério

1
@Rogerio Я запозичив посилання (та інтерпретацію) у Spring Framework Reference (MVC) . IMHO це має набагато більше сенсу, ніж оригінальна версія.
Шон Патрік Флойд

Розширення мертве. Марно. Розрізаний. Знищено. Мене не хвилює OCP. Ніколи не є приводом продовжити клас.
Джош Вуддок

15

Якщо ви уявляєте ієрархію класів як дерево (як це в Java), абстрактні класи можуть бути лише гілками, а заключні класи - це ті, які можуть бути лише листям. Класи, які не належать ні до однієї з цих категорій, можуть бути як гілками, так і листям.

Тут немає порушень принципів ОО, остаточне - це просто приємна симетрія.

На практиці ви хочете використовувати final, якщо ви хочете, щоб ваші об'єкти були незмінні або якщо ви пишете API, щоб повідомити користувачам API, що клас просто не призначений для розширення.


13

Саме ключове слово finalозначає, що щось є остаточним, і його не слід змінювати жодним чином. Якщо клас, якщо він позначений, finalвін не може бути розширений або підкласифікований. Але питання полягає в тому, чому ми відзначаємо клас final? ІМО є різні причини:

  1. Стандартизація: Деякі класи виконують стандартні функції, і вони не повинні бути змінені, наприклад, класи, що виконують різні функції, пов'язані з маніпуляціями з рядками або математичними функціями тощо
  2. Причини безпеки : Іноді ми пишемо класи, які виконують різні функції автентифікації та пароля, і не хочемо, щоб їх змінювали інші.

Я чув, що маркування класу finalпідвищує ефективність, але, чесно кажучи, я не міг знайти цей аргумент, який би мав велику вагу.

Якщо Java об'єктно-орієнтована, і ви оголошуєте клас остаточним, чи не зупиняє це уявлення про клас, що має характеристики об'єктів?

Можливо, так, але іноді це є цільовим призначенням. Іноді ми робимо це для досягнення більшої вигоди від безпеки тощо, жертвуючи здатність цього класу розширюватися. Але остаточний клас все ж може продовжити один клас, якщо це потрібно.

Зі сторони, ми повинні віддавати перевагу складу над успадкуванням, а finalключове слово насправді допомагає виконувати цей принцип.


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.


5

Якщо клас позначений final, це означає, що структуру класу неможливо змінити будь-яким зовнішнім. Там, де це найбільш видно, коли ти робиш традиційне поліморфне успадкування, в основному class B extends Aпросто не вийде. Це в основному спосіб захистити деякі частини коду (в міру) .

Для уточнення, маркування класу finalне позначає його поля як finalтаке і не захищає властивості об'єкта, а фактичну структуру класу.


1
Що означає властивості об'єкта? Чи означає це, що я міг би змінити змінну члена класу, якщо клас оголошено остаточним? Тож єдиною метою заключного класу є запобігання успадкуванню.
Адам Лю

5

АДРЕСАЦІЯ ПРОБЛЕМИ ЗВІТНОГО КЛАСУ:

Існує два способи зробити підсумковий клас. Перший - використовувати ключове слово final у декларації класу:

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

Другий спосіб зробити остаточний клас - оголосити всіх його конструкторів приватними:

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 . . . 
  }
}

На жаль, оскільки єдиний конструктор класу є приватним, розширити цей клас неможливо. У випадку з тестовим класом немає причини, щоб клас був остаточним. Тест-клас є хорошим прикладом того, як неявні підсумкові класи можуть викликати проблеми.

Тож слід відзначити його остаточним, коли ви неявно робите остаточний клас, роблячи його конструктором приватним.


4

Заключний клас - це клас, який неможливо продовжити. Також методи можуть бути оголошені як остаточні, щоб вказати, що їх не можна перекрити підкласами.

Запобігання підкласу класу може бути особливо корисним, якщо ви пишете API чи бібліотеки і хочете уникнути їх розширення для зміни базової поведінки.


4

Одна з переваг збереження класу як остаточного: -

Клас струн залишається остаточним, щоб ніхто не міг перекрити його методи та змінити функціональність. наприклад, ніхто не може змінити функціональність методу length (). Завжди буде повертатися довжина рядка.

Розробник цього класу не хотів, щоб ніхто міняв функціональність цього класу, тому він зберігав це як остаточне.


3

Так, іноді ви можете цього хотіти, з міркувань безпеки або швидкості. Це також робиться в C ++. Це може бути не те, що стосується програм, але більше для фреймворків. http://www.glenmccl.com/perfj_025.htm


3

У java заключне ключове слово використовується для наведених нижче випадків.

  1. Кінцеві змінні
  2. Заключні методи
  3. Підсумкові класи

У java кінцеві змінні не можуть перепризначити, остаточні класи не можуть розширюватися, а остаточні методи не можуть переосмислити.


1

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

Оскільки оголошення класу не перешкоджає екземпляру класу, це не означає, що воно зупинить клас не мати характеристик об'єкта. Просто вам доведеться дотримуватися методів саме так, як вони оголошені в класі.


1

подумайте про FINAL як про "кінець лінії" - той хлопець вже не може породити потомство. Отже, коли ви бачите це таким чином, вам трапляються тони справжніх сценаріїв, які вимагають відзначити марку "кінець рядка" класу. Це дизайн, керований доменом - якщо ваш домен вимагає, щоб заданий ENTITY (клас) не міг створити підкласи, то позначте його як КРАЙНИЙ.

Зазначу, що ніщо не заважає вам успадкувати клас "слід позначати як остаточний". Але це, як правило, класифікується як "зловживання спадщиною", і робиться тому, що найчастіше ви хочете успадкувати певну функцію від базового класу вашого класу.

Найкращий підхід - подивитися на домен і нехай він диктує ваші дизайнерські рішення.


1

Як було сказано вище, якщо ви хочете, щоб ніхто не міг змінити функціональність методу, ви можете оголосити його як остаточне.

Приклад: Шлях до файлу сервера додатків для завантаження / завантаження, розділення рядка на основі зміщення, такі методи ви можете оголосити його остаточним, щоб ці функції методу не були змінені. І якщо ви хочете такі кінцеві методи в окремому класі, то визначте цей клас як Final class. Таким чином, заключний клас матиме всі підсумкові методи, де як кінцевий метод можна оголосити та визначити у не завершальному класі.



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екземпляр як параметр і викликає метод привітання, сподіваючись, що він би сказав, 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, ми можемо використовувати цей підхід.

Якщо нам просто потрібно зробити конкретні методи в класі, щоб їх не переосмислити, ми просто можемо поставити перед ними заключне ключове слово. Там клас все ще є спадковим.


-1

Об'єктна орієнтація - це не спадкування, це інкапсуляція. І спадщина порушує інкапсуляцію.

Оголошення випускного класу має бездоганний сенс у багатьох випадках. Будь-який об'єкт, що представляє "значення", наприклад, колір або кількість грошей, може бути остаточним. Вони стоять самостійно.

Якщо ви пишете бібліотеки, зробіть свої класи остаточними, якщо ви чітко не позначаєте їх відведеннями. В іншому випадку люди можуть отримати ваші заняття та змінити методи, порушуючи ваші припущення / інваріанти. Це також може мати наслідки для безпеки.

Джошуа Блох в "Ефективній Яві" рекомендує чітко розробляти проект для спадкування або забороняти його, і він зазначає, що спроектувати спадщину не так просто.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.