StringクラスがJavaでfinalと宣言されているのはなぜですか?


141

クラスjava.lang.StringがJavaでfinalとして宣言されていることを知ったときから、それがなぜなのか疑問に思いました。当時は答えが見つかりませんでしたが、この投稿:JavaでStringクラスのレプリカを作成する方法は?私の質問を思い出しました。

確かに、Stringは私が必要とするすべての機能を提供します。Stringクラスの拡張を必要とする操作については考えたことはありませんが、誰かが何を必要としているのかはわかりません。

それで、彼らがそれを最終的にすることに決めたとき、デザイナーの意図が何であったか誰でも知っていますか?


すべての回答、特にTrueWill、Bruno Reis、Thiloに感謝します。最高の答えを1つ以上選んでほしいのですが、残念ながら...!
Alex Ntousias、2010年

1
また、「ああ、Stringでいくつかのユーティリティメソッドがさらに必要になる」というプロジェクトの急増も考えてみてください。これらのプロジェクトはすべて、異なるクラスであるため、他のStringを使用できませんでした。
–ThorbjørnRavn Andersen 2013

この返信をありがとう、とても便利です。2つの事実があります。StringはFinalクラスであり、変更することはできませんが、別のオブジェクトから参照できるため、不変です。しかし、何について:-String a = new String( "test1"); 次に、s = "test2"; StringがFinalクラスオブジェクトの場合、どのように変更できますか?変更された最終オブジェクトをどのように使用できますか?何か間違って聞いたら教えてください。
Suresh Sharma

あなたはこの良い記事を見ることができます。
Aniket Thakur 2014

4
Javaでありがたいことに回避したことの1つは、「誰もが多くの追加メソッドを備えたStringの独自のサブクラスを持ち、これらは互いに互換性がない」ことです。
–ThorbjørnRavn Andersen 2015

回答:


88

文字列を不変オブジェクトとして実装すると非常に便利です不変性の詳細を理解するには、不変性について読む必要があります。

不変オブジェクトの 1つの利点は、

単一のインスタンスをポイントすることで、重複を共有できます。

ここから)。

Stringがfinalでない場合は、サブクラスを作成して、「Stringとして見た」ときに似ているが実際には異なる2つの文字列を作成できます。


70
最終クラスと不変オブジェクトの間に私が見ない関係がない限り、私はあなたの答えが質問にどのように関係するかわかりません。
sepp2k 2010年

9
それが最終でない場合、StringChildをString paramとしていくつかのメソッドに渡すことができ、(子クラスの状態が変化するため)変更可能になる可能性があるためです。
helios

4
うわー!反対票?サブクラス化が不変性にどのように関係するか理解していませんか?何が問題なのか説明をいただければ幸いです。
Bruno Reis

7
@ Bruno、re:反対投票:私は反対投票しませんでしたが、サブクラスの防止が不変性を強制する方法に関する文を追加できます。今のところ、それは半分答えのようなものです。
Thilo

12
@BrunoReis-リンクできる素晴らしい記事を見つけました。JamesGosling(Javaの作成者)へのインタビューがあり、このトピックについてここで簡単に説明しています。ここに興味深いスニペットがあります。「文字列を不変にしたのはセキュリティでした。ファイルを開くメソッドがあります。それに文字列を渡します。それから、OSに取り掛かる前にあらゆる種類の認証チェックを行っています。文字列を効果的に変更する何かを行うことができた場合、セキュリティチェックの後、OS呼び出しの前に、ブームになりました...」
Anurag

60

これは、上記の回答ですでに述べた2つの理由を概説する素晴らしい記事です

  1. セキュリティ:システムは、読み取り専用情報の機密ビットが変更されることを心配せずに配布できます
  2. パフォーマンス:不変データは、スレッドセーフにするために非常に役立ちます。

そして、これはおそらくその記事で最も詳細なコメントです。これは、Javaの文字列プールとセキュリティの問題に関係しています。文字列プールに入れるものを決定する方法についてです。文字のシーケンスが同じである場合、両方の文字列が等しいと仮定すると、誰が最初にそこに到達するか、およびそれに伴ってセキュリティの問題に関する競合状態が発生します。そうでない場合、文字列プールには冗長な文字列が含まれるため、そもそもそれを使用する利点が失われます。自分で読んでくださいね。


Stringを拡張すると、equalsとinternで大混乱が発生します。JavaDocは次と等しいと言います:

この文字列を指定されたオブジェクトと比較します。引数がnullではなく、このオブジェクトと同じ文字シーケンスを表すStringオブジェクトである場合にのみ、結果はtrueになります。

java.lang.String最終ではないと仮定すると、a SafeStringはと等しい可能性がStringあり、その逆も同様です。同じ文字シーケンスを表すためです。

あなたが適用される場合、何が起こるかinternSafeString希望- SafeStringJVMの文字列プールに入りますか?ClassLoaderそして、すべてはオブジェクトSafeStringその後、JVMの寿命のために所定の位置にロックになるだろうに保持された参照を。あなたは文字の並びインターンへの最初の可能性が誰についての競合状態を取得したい-多分あなたはSafeString、多分勝つStringか、あるいはSafeString別のクラスローダ(したがって、異なるクラス)によってロードされました。

あなたがプールへの競争に勝った場合、これは真のシングルトンであり、人々はリフレクションとを通じてあなたの環境全体(サンドボックス)にアクセスできますsecretKey.intern().getClass().getClassLoader()

または、JVMは、具象文字列オブジェクトのみ(サブクラスなし)がプールに追加されたことを確認することにより、このホールをブロックできます。

equalsがSafeString!=のように実装されている場合StringSafeString.intern!= String.internでありSafeString、プールに追加する必要があります。プールは、その後のプールになる<Class, String>代わりに<String>、あなたがプールを入力する必要があると思いますすべてが新鮮クラスローダーになります。


2
もちろん、パフォーマンスの理由は誤りです。Stringがインターフェースであったとしても、アプリケーションでより良いパフォーマンスを実現する実装を提供できたでしょう。
Stephan Eggermont 2016

28

Stringが不変または最終的であるという最も重要な理由は、Stringがクラスローディングメカニズムによって使用されているため、根本的なセキュリティの深遠な側面があるためです。

Stringが変更可能であるか、最終的ではなかった場合、「java.io.Writer」をロードする要求が「mil.vogoon.DiskErasingWriter」をロードするように変更されている可能性があります

参照:なぜStringがJavaで不変か


15

String はJavaの非常に中心的なクラスです。多くのことは、たとえば不変であるなど、特定の方法で動作することに依存しています。

クラスfinalを作成することで、これらの仮定を破る可能性のあるサブクラスを防ぎます。

現在でも、リフレクションを使用している場合は、文字列を分割できる(値またはハッシュコードを変更できる)ことに注意してください。リフレクションはセキュリティマネージャで停止できます。場合Stringではなかったfinal、誰もがそれを行うことができます。

宣言されていない他のクラスをfinal使用すると、多少壊れたサブクラスを定義できます(Listたとえば、が間違った位置に追加される可能性があります)が、少なくともJVMはコアオペレーションに依存しません。


6
finalクラスでは、不変性は保証されません。これは、クラスの不変式(そのうちの1つは不変性である可能性がある)がサブクラスによって変更できないことを保証するだけです。
Kevin Brock、2010年

1
@ケビン:はい。クラスのFinalは、サブクラスがないことを保証します。不変性とは何の関係もありません。
Thilo

4
クラスをfinalにしても、それ自体は不変にはなりません。しかし、不変クラスをfinalにすることで、不変性を破壊するサブクラスを誰も作成しないことが保証されます。おそらく、不変性について主張する人々は、彼らが何を意味するのか正確に不明確でしたが、彼らの発言は、文脈で理解された場合は正しいです。
Jay

時々私はこの答えを読んで、それは大丈夫な答えだと思いました、そして私は「The Effective Java」からハッシュコードとイコールを読みました、そしてそれがとても良い答えだと気づきました。誰でも説明が必要です。同じ本のieam 8とiteam 9をお勧めします。
Abhishek Singh

6

ブルーノが言ったように、それは不変性についてです。これは文字列だけでなく、ラッパー(Double、Integer、Characterなど)にも関係します。これには多くの理由があります。

  • スレッドセーフ
  • 安全保障
  • Java自体によって管理されるヒープ(ガベージコレクションの方法が異なる通常のヒープとは異なります)
  • メモリ管理

基本的には、プログラマーとして、文字列が変更されないことを確認できます。同様に、それがどのように機能するかを知っている場合は、メモリ管理を改善できます。「hello」など、2つの同じ文字列を次々に作成してみてください。デバッグすると、IDが同じであることがわかります。つまり、これらはまったく同じオブジェクトです。これは、Javaで実現できるためです。文字列が変更可能である場合、これは不可能です。彼らは決して変わらないので、彼らは私と同じようにすることができます。したがって、1,000,000の文字列「hello」を作成することを決定した場合、実際に行うことは「hello」への1,000,000のポインタを作成することです。同様に、文字列の関数やその理由によるラッパーをすべてまとめると、別のオブジェクトが作成されます(ここでもオブジェクトIDを見てください-変更されます)。

さらにJavaのfinalは、オブジェクトが変更できないことを必ずしも意味しません(たとえば、C ++とは異なります)。つまり、それが指すアドレスは変更できませんが、プロパティや属性を変更することはできます。したがって、場合によっては不変性と最終の違いを理解することが非常に重要になる可能性があります。

HTH

参照:


1
文字列が別のヒープに移動したり、別のメモリ管理を使用したりすることはないと思います。彼らは確かにゴミ収集可能です。
Thilo

2
また、クラスの最終キーワードは、フィールドの最終キーワードとは完全に異なります。
Thilo

1
さて、SunのJVMでは、intern()された文字列は、ヒープの一部ではないperm-genに入る場合があります。しかし、それは間違いなくすべての文字列やすべてのJVMで発生するわけではありません。
Thilo

2
すべての文字列がその領域に移動するわけではなく、インターンされた文字列だけが移動します。リテラル文字列のインターンは自動的に行われます。(@Thilo、コメントの送信時に入力)。
Kevin Brock、2010年

この返信をありがとう、とても便利です。2つの事実があります。StringはFinalクラスであり、変更することはできませんが、別のオブジェクトから参照できるため、不変です。しかし、何について:-String a = new String( "test1"); 次に、s = "test2"; StringがFinalクラスオブジェクトの場合、どのように変更できますか?変更された最終オブジェクトをどのように使用できますか?何か間違って聞いたら教えてください。
Suresh Sharma

2

実装を簡素化するためだったかもしれません。クラスのユーザーが継承できるクラスを設計する場合は、まったく新しいユースケースのセットを考慮して設計する必要があります。彼らがXプロテクトされたフィールドでこれを行うとどうなりますか?最終的にすることで、彼らはパブリックインターフェースを正しく機能させることに集中でき、それが確実であることを確認できます。


3
「継承のための設計は難しい」の+1。ところで、それはBlochの「Effective Java」で非常にうまく説明されています。
sleske 2010

2

すでに多くの良い点が述べられていますが、もう1つ追加したいと思います。StringがJavaで不変である理由の1つは、Stringがそのハッシュコードをキャッシュできるようにし、Javaで不変なStringがそのハッシュコードをキャッシュし、すべてを計算しないことですStringのハッシュコードメソッドを呼び出すと、Javaのハッシュマップで使用されるハッシュマップキーとして非常に高速になります。

つまり、Stringは不変であるため、一度作成されたコンテンツを変更して、StringのhashCodeが複数の呼び出しで同じになることを保証することはできません。

Stringクラスが次のように宣言されているのを確認した場合

/** Cache the hash code for the string */
private int hash; // Default to 0

そしてhashcode()機能は次の通りです-

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

すでにコンピュータの場合は、値を返します。


2

より良い実装を得られないようにするためです。もちろんそれはインターフェースであったはずです。

[編集]ああ、もっと無知な反対票を得ること。答えは完全に深刻です。愚かな文字列の実装を何度もプログラムする必要があり、深刻なパフォーマンスと生産性の損失につながりました


2

他の回答で示唆されている明らかな理由は別として、Stringクラスをfinalにすることの1つの考えは、仮想メソッドのパフォーマンスオーバーヘッドにも関連している可能性があります。Stringは重いクラスであることを思い出してください。これは最終的なものであり、サブ実装が確実にないことを意味し、間接呼び出しのオーバーヘッドがないことを意味します。もちろん、今は仮想呼び出しなどのものがあり、常にこの種の最適化を行っています。


2

他の回答で述べた理由(セキュリティ、不変性、パフォーマンス)に加えて、String特別な言語サポートがあることに注意してください。あなたはStringリテラルを書くことができ、+演算子のサポートがあります。プログラマがサブクラス化できるようStringにすると、次のようなハックが促進されます。

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.

1

まあ、私は正しいかどうかはわかりませんが、JavaではStringがプリミティブデータ型として処理できる唯一のオブジェクトです。つまり、StringオブジェクトをString name = "javaとして作成できます。"参照によるコピーではなく値によるコピーである他のプリミティブデータ型と同様に、Stringがfinalである理由で、同じ動作が期待されます。それは私の考えたものです。完全に非論理的な場合は無視してください。


1
私の意見では、文字列はプリミティブ型のようには動作しません。"java"のような文字列リテラルは、実際にはStringクラスのオブジェクトです(終了演算子の直後のドット演算子を使用できます)。したがって、文字列変数にリテラルを割り当てることは、通常どおりオブジェクト参照を割り当てることだけです。違うのは、Stringクラスには言語レベルのサポートがコンパイラーに組み込まれているということです...二重引用符で囲まれたものをStringオブジェクトに変換し、上記の+演算子を使用します。
ジョージー14年

1

文字列の最終性も、標準としてそれらを守ります。C ++では文字列のサブクラスを作成できるため、すべてのプログラミングショップが独自のバージョンの文字列を持つことができます。これは強力な基準の欠如につながるでしょう。


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


0

JVMは不変のものを知っていますか?回答は「いいえ」です。定数プールにはすべての不変フィールドが含まれていますが、すべての不変フィールド/オブジェクトは定数プールにのみ格納されているわけではありません。不変性とその機能を実現する方法で実装するだけです。CustomStringは、MarkerInterfaceを使用して最終的に実装しなくても実装できます。MarkerInterfaceは、プーリングにJavaの特別な動作を提供します。この機能はまだ待ち望まれています!


0

答えのほとんどは不変性に関連しています-なぜString型のオブジェクトを適切に更新できないのですか?ここでは多くの良い議論があり、Javaコミュニティは不変性をプリンシパルとして採用するのに適しています。(息を止めていません。)

ただし、OPの問題は、なぜそれが最終的なのか、なぜ拡張できないのかということです。ここのいくつかはこれを引き受けました、しかし私はここに本当のギャップがあるというOPに同意します。他の言語では、開発者が型の新しい名義型を作成できます。たとえば、Haskellでは、実行時にテキストと同一である次の新しい型を作成できますが、コンパイル時にバインドの安全性を提供します。

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

したがって、Java言語の拡張機能として次の提案を提案します。

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(または、おそらくJavaから離れることができます)


-1

文字列を一度作成すると、それがオブジェクトであると見なし、それを変更したい場合、それは不可能です。新しいオブジェクトを作成します。


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