Javaの「最終クラス」のポイントは何ですか?


569

私はJavaに関する本を読んでいて、クラス全体をとして宣言できると書いてありますfinal。これをどこで使うかなんて考えられない。

私はプログラミングの初心者であり、プログラマが実際にこれをプログラムで使用しているかどうか疑問に思っています。彼らがそうするなら、いつそれを使うのか私はそれをよりよく理解し、いつ使うべきかを知ることができます。

Javaがオブジェクト指向で、クラスを宣言する場合final、オブジェクトの特性を持つクラスの考えを止めませんか?

回答:


531

まず、この記事をお勧めします:Java:最終クラスを作成するタイミング


彼らがそうするなら、いつそれを使うのか私はそれをよりよく理解し、いつ使うべきかを知ることができます。

finalクラスは、単にクラスで拡張することはできません

(クラスのオブジェクトへのすべての参照が、として宣言されているかのように動作することを意味するわけではありませんfinal。)

クラスをfinalとして宣言すると便利な場合は、この質問の回答で説明されています。

Javaがオブジェクト指向で、クラスを宣言する場合final、オブジェクトの特性を持つクラスの考えを止めませんか?

ある意味ではそうです。

クラスを最終としてマークすることにより、コードのその部分の言語の強力で柔軟な機能を無効にします。ただし、一部のクラスは、サブクラス化を適切に考慮に入れるように設計してはなりません(場合によってはできません)。これらの場合、OOPを制限しますが、クラスを最終としてマークすることは理にかなっています。(ただし、最終クラスはまだ非最終クラスを拡張できることに注意してください。)


39
答えを付け加えると、Effective Javaの原則の1つは、継承よりも構成を優先することです。finalキーワードを使用すると、その原則を適用するのにも役立ちます。
Riggy

9
「主に効率とセキュリティの理由でそれをします。」私はこの発言を頻繁に聞きます(ウィキペディアでもこれを述べています)が、私はまだこの議論の背後にある理由を理解していません。誰かが、最終ではないjava.lang.Stringがどのようにして非効率的または安全でなくなったのかを説明したいと思いますか?
MRA 2012

27
@MRA文字列をパラメータとして受け入れるメソッドを作成した場合、文字列は不変であるため、それは不変であると想定します。この結果、渡された文字列を変更せずに、文字列オブジェクトの任意のメソッドを安全に呼び出すことができることを知っています。文字列を拡張し、サブ文字列の実装を変更して実際の文字列を変更すると、不変であると予期されていた文字列オブジェクトは不変ではなくなります。
ランチャー

1
@Sortofabeginnerそして、あなたがすべてのStringメソッドとフィールドをfinalにしたいと言ったらすぐに、追加機能を備えたクラスを作成できるようにします...その時点で、文字列とその文字列を操作するメソッドを作成します。
ランチャー2014年

1
@Shayファイナル(その他のもの)は、オブジェクトを不変にするために使用されるため、相互に何の関係もないとは言えません。ここdocs.oracle.com/javase/tutorial/essential/concurrency/…を
Celeritas 2014

184

Javaでは、final修飾子のあるアイテムは変更できません。

これには、最終クラス、最終変数、および最終メソッドが含まれます。

  • 最終クラスは他のクラスによって拡張できません
  • 最終変数に別の値を再割り当てすることはできません
  • 最後のメソッドはオーバーライドできません

40
実際の問題は、なぜではなく、なぜなのかです。
Francesco Menzani 2015

8
「Javaでは、final修飾子のあるアイテムは変更できません!」というステートメントは、あまりにも明確であり、実際には完全に正しいわけではありません。Grady Boochが言うように、「オブジェクトには状態、動作、およびアイデンティティがあります」。参照が最終としてマークされた後でオブジェクトのIDを変更することはできませんが、新しい値をその非フィールドに割り当てることでその状態を変更する機会がありfinalます(もちろん、それが提供されている場合)。 Oracle Java認定(1Z0-808など)の取得を計画している場合は、この点に
注意してください

33

セキュリティ上の理由から、クラスの継承防止する場合に、finalが重要になる1つのシナリオ。これにより、実行中のコードが誰かによって上書きさないようにすることができます

別のシナリオは最適化です。Javaコンパイラーが最終クラスからのいくつかの関数呼び出しをインライン化することを覚えているようです。したがって、をa.x()宣言してaを宣言するとfinal、コンパイル時にコードがどのようになるかがわかり、呼び出し元の関数にインライン化できます。これが実際に行われたかどうかはわかりませんが、最終的には可能です。


7
インライン化は通常、実行時にジャストインタイムコンパイラーによってのみ行われます。これもfinalなしで機能しますが、JITコンパイラーは、拡張クラスがないこと(またはこれらの拡張クラスがこのメソッドに影響しないこと)を確認するために、もう少し作業が必要です。
パウロEbermann

インライン化と最適化の問題には良いのライトアップは、ここで見つけることができます:lemire.me/blog/archives/2014/12/17/...
ジョシュHemann

24

最良の例は

public final class String

これは不変のクラスであり、拡張できません。もちろん、クラスをfinalにするだけでは不変です。


へへ、時々それはルーベ・ゴールダージアンの開発者を彼ら自身から保護します。
ゾイドバーグ

16

関連する読み物:ボブ・マーティンによる開閉原理

主な引用:

ソフトウェアエンティティ(クラス、モジュール、関数など)は、拡張のために開いている必要がありますが、変更のために閉じている必要があります。

finalキーワードは、メソッドやクラスに使われているかどうか、Javaでこれを強制するための手段です。


6
@Sean:それfinalを宣言すると、クラスはオープンではなく拡張のためにクローズされますか?それとも私は文字通りそれを取っていますか?
Goran Jovic

4
@Goranはファイナルをグローバルに適用しています、はい。重要なのは、変更を加えたくない場所にfinalを選択的に適用することです(もちろん、拡張に適切なフックを提供するためです)
Sean Patrick Floyd

26
OCPでは、「変更」はソースコードの変更を意味し、「拡張」は実装の継承を意味します。したがって、finalクラス/メソッド宣言での使用は、実装コードを変更のためにクローズし、継承によって拡張のためにオープンしたい場合には意味がありません。
ホジェリオ

1
@Rogerio私は、Spring Framework Reference(MVC)からリファレンス(および解釈)を借用しました。私見これは元のバージョンよりもずっと理にかなっています。
Sean Patrick Floyd

延長は死にました。役に立たない。間引き。破壊されました。OCPは気にしません。クラスを拡張するための言い訳は決してありません。
ジョシュウッドコック2018年

15

クラス階層を(Javaのように)ツリーとして想像すると、抽象クラスはブランチのみであり、最終クラスはリーフのみであることができます。これらのカテゴリのいずれにも該当しないクラスは、ブランチとリーフの両方になる可能性があります。

ここでOOの原則に違反することはありません。finalは単純に優れた対称性を提供します。

実際には、オブジェクトを不変にしたい場合、またはAPIを作成している場合は、finalを使用して、クラスが拡張用ではないことをAPIのユーザーに通知します。


13

キーワードfinal自体は、何かが最終的なものであり、いかなる方法でも変更されるべきではないことを意味します。クラスがマークされている場合、finalそれを拡張またはサブクラス化することはできません。しかし問題は、なぜクラスをマークするのかということfinalです。IMOにはさまざまな理由があります。

  1. 標準化:一部のクラスは標準関数を実行し、それらは変更することを意図していません。たとえば、文字列操作や数学関数などに関連するさまざまな関数を実行するクラスなどです。
  2. セキュリティ上の理由:場合によっては、さまざまな認証およびパスワード関連の機能を実行するクラスを作成し、他のユーザーに変更されたくない場合があります。

クラスをマークfinalすることで効率が向上すると聞いたことがありますが、率直に言って、この議論が大きな重みをもたらすとは思えませんでした。

Javaがオブジェクト指向であり、クラスをfinalと宣言した場合、クラスはオブジェクトの特性を持つという考えを止めませんか?

おそらくそうですが、それが意図された目的である場合もあります。拡張するこのクラスの機能を犠牲にすることで、セキュリティなどの大きな利点を達成するために時々それを行います。ただし、最終クラスは、必要に応じて1つのクラスを拡張できます。

サイドノートでは、我々はすべき相続上の組成を好むfinalキーワード実際にこの原則を施行するのに役立ちます。


6

クラスを「最終」にするときは注意してください。最終クラスのユニットテストを記述したい場合、この最終クラスをサブクラス化して、Michael C. Feathersの著書「レガシーコードを効果的に使用する」で説明されている依存関係を解除する手法「サブクラスとオーバーライドメソッド」を使用することはできません。 。この本の中で、フェザーズ氏は次のように述べています。「まじめに、封印された最終的なものは頭の悪い間違いであると信じがちですが、それらはプログラミング言語に追加されるべきではありませんでした。私たちの手に負えないライブラリ、私たちは単に問題を求めています。」


6

final class 新しいメソッドを追加するときにパブリックAPIが壊れることを回避できます

Baseクラスのバージョン1で次のようにするとします。

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
オブジェクトのプロパティは何を意味しますか?クラスがfinalと宣言されている場合、クラスのメンバー変数を変更できるということですか?したがって、最終クラスの唯一の目的は継承を防ぐことです。
Adam Lyu 2016

5

最終クラスの問題に対処するには:

クラスをファイナルにする方法は2つあります。1つ目は、クラス宣言でキーワードfinalを使用することです。

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

クラスをfinalにする2つ目の方法は、すべてのコンストラクターをプライベートとして宣言することです。

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

最終とマークすることで、実際に最終であることが判明した場合に、このTestクラスを確認するためのトラブルを回避できます。一見して公共のように見えます。

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

残念ながら、クラスの唯一のコンストラクターはプライベートなので、このクラスを拡張することは不可能です。Testクラスの場合、クラスがfinalである必要はありません。Testクラスは、暗黙の最終クラスが問題を引き起こす可能性のある良い例です。

そのため、コンストラクタをプライベートにしてクラスを暗黙的にfinalにする場合は、finalとマークする必要があります。


4

最終クラスは、拡張できないクラスです。また、メソッドをfinalとして宣言して、サブクラスでオーバーライドできないことを示すこともできます。

クラスがサブクラス化されないようにすることは、APIまたはライブラリを作成し、基本動作を変更するように拡張されたくない場合に特に役立ちます。


4

クラスをファイナルとして維持する利点の1つ:-

Stringクラスは最終的に保持されるため、そのメソッドをオーバーライドして機能を変更することはできません。たとえば、誰もlength()メソッドの機能を変更できません。常に文字列の長さを返します。

このクラスの開発者は誰もこのクラスの機能を変更することを望んでいないので、彼はそれを最終的なものに保ちました。


3

はい。ただし、セキュリティまたは速度の理由から、これが必要になる場合があります。C ++でも行われます。それはプログラムには適用できないかもしれませんがフレームワークにも適用できます。 http://www.glenmccl.com/perfj_025.htm


3

Javaのfinalキーワードでは、以下の場合に使用されます。

  1. 最終変数
  2. 最終的な方法
  3. 最終クラス

Javaでは、final変数は再割り当てできません。final クラスは拡張できず、finalメソッドはオーバーライドできません。


1

最終クラスは拡張できません。したがって、クラスが特定の方法で動作し、誰かがメソッドをオーバーライドしないようにしたい場合は(おそらく効率が低く、より悪意のあるコードで)、クラス全体を不要な最終メソッドまたは特定のメソッドとして宣言できますかわった。

クラスを宣言しても、クラスのインスタンス化が妨げられるわけではないので、クラスがオブジェクトの特性を持つことを停止するわけではありません。クラスで宣言されているとおりにメソッドに固執する必要があるだけです。


1

FINALを「ラインの終わり」と考えてください-その男はもう子孫を生み出すことができません。したがって、このように見ると、クラスの「行末」マーカーにフラグを立てる必要がある、実際に遭遇するシナリオがたくさんあります。これはドメイン駆動設計です。ドメインで特定のENTITY(クラス)がサブクラスを作成できないことを要求する場合は、FINALとしてマークしてください。

「最終としてタグ付けする必要がある」クラスの継承を妨げるものは何もないことに注意してください。しかし、それは一般に「継承の乱用」として分類され、ほとんどの場合、クラスの基本クラスから関数を継承したいために行われます。

最善のアプローチは、ドメインを調べて、設計の決定を決定させることです。


1

上記のように、メソッドの機能を誰も変更できないようにする場合は、それをfinalとして宣言できます。

例:ダウンロード/アップロード用のアプリケーションサーバーファイルパス、オフセットに基づいて文字列を分割、これらのメソッドをFinalと宣言して、これらのメソッド関数が変更されないようにすることができます。また、そのようなfinalメソッドを別のクラスで使用する場合は、そのクラスをFinalクラスとして定義します。したがって、Finalクラスはすべてのfinalメソッドを持ち、Finalメソッドは非finalクラスで宣言および定義できます。



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!"
    }
}

クラスが作成された場合、この状況は回避できます。Classがとして宣言されていない場合に、生意気なプログラマーが引き起こし得る混乱の量を想像してみてください。EmployeefinalStringfinal


1

最終クラスをさらに拡張することはできません。クラスをJavaで継承可能にする必要がない場合は、このアプローチを使用できます。

オーバーライドされないようにクラスの特定のメソッドを作成する必要があるだけの場合は、それらの前にfinalキーワードを置くことができます。クラスはまだ継承可能です。


-1

オブジェクト指向は継承についてではなく、カプセル化についてです。そして継承はカプセル化を壊します。

クラスのファイナルを宣言することは、多くの場合に完全に理にかなっています。色や金額などの「価値」を表すオブジェクトはすべて最終的なものになる可能性があります。彼らは独力で立ちます。

ライブラリを作成する場合は、派生させるために明示的にインデントしない限り、クラスをfinalにします。そうしないと、人々がクラスを派生させてメソッドをオーバーライドし、仮定/不変条件を破る可能性があります。これは、セキュリティにも影響を与える可能性があります。

「Effective Java」のJoshua Blochは、継承のために明示的に設計するか、それを禁止することを推奨しており、継承のための設計はそれほど簡単ではないと指摘しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.