java.lang.Stringが最終的なものではなかった場合、どうなりますか?


16

私は長い間Java開発者であり、最終的に、専攻した後、認定試験を受けるためにそれをきちんと勉強する時間があります。セキュリティの問題と関連するものについて読んだとき、私はそれを理解します...しかし、真剣に、誰もがその本当の例を持っていますか?

たとえば、Stringがファイナルでない場合はどうなりますか?Rubyにはないように。Rubyコミュニティからの苦情は聞いていません...そして、その動作(4行のコード)を実装するために自分で実装するか、Webで検索する必要があるStringUtilsと関連クラスを知っています。喜んで。


9
スタックオーバーフローでこの問題に興味がされることがあります。stackoverflow.com/questions/2068804/why-is-string-final-in-java
トーマス・オーウェンズ

同様に、そうでない場合java.io.Fileはどうなるでしょうfinal。ああ、そうではありません。バガー。
トムホーティン-タックライン

1
私たちが知っている西洋文明の終わり。
Tulainsコルドバ

回答:


22

主な理由は速度です。finalクラスを拡張できないため、文字列を処理するときにJITがあらゆる種類の最適化を実行できます-オーバーライドされたメソッドをチェックする必要はありません。

別の理由はスレッドセーフです:不変は常にスレッドセーフです。なぜなら、スレッドは、他の誰かに渡される前に完全にビルドする必要があるためです。

また、Javaランタイムの発明者は、常に安全性を重視したいと考えていました。String(非常に便利なのでGroovyでよく行うことです)を拡張できると、何をしているのかわからない場合、ワームの缶全体を開くことができます。


2
間違った答えです。JVM仕様で必要な検証以外に、HotSpotはfinal、コードのコンパイル時にメソッドがオーバーライドされていないことをクイックチェックとしてのみ使用します。
トムホーティン-タックライン

1
@ TomHawtin-tackline:リファレンス?
アーロンディグラ

1
あなたは、不変性と最終的であることは何らかの関係があることを暗示しているようです。「別の理由はスレッドセーフです:不変は常にスレッドセーフです...」オブジェクトが不変であるかどうかは、オブジェクトが最終的であるかどうかによって決まります。
Tulainsコルドバ

1
さらに、Stringクラスfinal、クラスのキーワードに関係なく不変です。含まれる文字配列最終的なものであり、不変です。クラス自体をfinalにすると、可変サブクラスを導入できないため、@ ablが言及しているようにループが閉じます。

1
@Snowman正確には、最終的な文字配列は、配列の内容ではなく、配列参照のみを不変にします。
ミチャウコスマルスキ

10

JavaのStringクラスをfinalにする必要があるもう1つの理由があります。それは、いくつかのシナリオではセキュリティにとって重要です。Stringクラスが最終クラスでない場合、次のコードは悪意のある呼び出し元による微妙な攻撃に対して脆弱です。

 public String makeSafeLink(String url, String text) {
     if (!url.startsWith("http:") && !url.startsWith("https:"))
         throw SecurityException("only http/https URLs are allowed");
     return "<a href=\"" + escape(url) + "\">" + escape(text) + "</a>";
 }

攻撃:悪意のある呼び出し元がStringのサブクラスを作成する可能性がありますEvilString。ここで、EvilString.startsWith()常にtrueを返しますが、の値EvilStringは悪(例:)javascript:alert('xss')です。サブクラス化により、これはセキュリティチェックを回避します。この攻撃は、チェックの時間(TOCTTOU)の脆弱性として知られています:チェックが行われる時間(URLがhttp / httpsで始まる)と値が使用される時間の間(htmlスニペットを作成するため)、有効な値は変更できます。String最終的なものでなければ、TOCTTOUのリスクはrisks延します。

そのStringため、最終的なものではない場合、発信者を信頼できない場合は、安全なコードを書くのが難しくなります。もちろん、それはまさにJavaライブラリの位置です。Javaライブラリは信頼できないアプレットによって呼び出される可能性があるため、呼び出し元を信頼することはありません。これは、String最終的なものでなければ、安全なライブラリコードを書くのは不合理に難しいことを意味します。


もちろん、リフレクションを使用してchar[]文字列の内部を変更することにより、そのTOCTTOU脆弱性を引き出すことは可能ですが、タイミングには多少の運が必要です。
ピーターテイラー

2
@ピーターテイラー、それはそれよりも複雑です。SecurityManagerから許可されている場合にのみ、リフレクションを使用してプライベート変数にアクセスできます。アプレットおよびその他の信頼できないコードにはこの許可が与えられていないため、リフレクションを使用してchar[]文字列内のプライベートを変更することはできません。したがって、TOCTTOUの脆弱性を利用することはできません。
DW

私は知っていますが、それでもアプリケーションと署名されたアプレットを残しています-そして、自己署名アプレットの警告ダイアログで実際に何人が怖がっていますか?
ピーターテイラー

2
@Peter、それはポイントではありません。要点は、信頼できるJavaライブラリを書くのははるかに難しく、String最終的なものでなければJavaライブラリに報告されている脆弱性がはるかに多いということです。この脅威モデルが適切である限り、防御することが重要になります。アプレットやその他の信頼できないコードにとって重要である限り、それはString信頼できるアプリケーションに必要ではない場合でも、おそらく最終決定を正当化するのに十分です。(いずれにせよ、信頼できるアプリケーションは、最終的なものであるかどうかにかかわらず、すべてのセキュリティ対策を既にバイパスできます。)
DW

「攻撃:悪意のある呼び出し元は、EvilString.startsWith()が常にtrueを返す文字列EvilStringのサブクラスを作成する可能性があります」-startsWith()が最終的な場合ではありません。
エリック

4

そうでない場合、マルチスレッドのJavaアプリは混乱します(実際よりもさらに悪い)。

私見最終的な文字列(不変)の主な利点は、本質的にスレッドセーフであることです。同期を必要としません(コードを書くのはかなり簡単ですが、それほど遠くはありません)。が可変である場合、マルチスレッドWindowsツールキットのようなものが非常に困難であることを保証し、それらは厳密に必要です。


3
finalクラスは、クラスの可変性とは関係ありません。

この回答は質問に対処するものではありません。-1
FUZxxl

3
Jarrod Roberson:クラスが最終クラスではない場合、継承を使用して可変文字列を作成できます。
ysdx

@JarrodRobersonは最終的に誰かがエラーに気づきました。受け入れられた回答は、final equals immutableも意味します。
Tulainsコルドバ

1

String最終であることのもう1つの利点は、不変性と同様に、予測可能な結果が保証されることです。 Stringは、クラスですが、値型のようないくつかのシナリオで扱われることを意図しています(コンパイラはを介してそれをサポートしますString blah = "test")。したがって、特定の値を持つ文字列を作成する場合、整数の場合と同様に、任意の文字列を継承する特定の動作が期待されます。

これについて考えてみてください:サブクラスを作成しjava.lang.Stringequalsor hashCodeメソッドをオーバーライドした場合はどうなりますか?突然、2つの文字列「テスト」が互いに等しくなることができなくなりました。

私がこれを行うことができたらカオスを想像してください:

public class Password extends String {
    public Password(String text) {
        super(text);
    }

    public boolean equals(Object o) {
        return true;
    }
}

他のクラス:

public boolean isRightPassword(String suppliedString) {
    return suppliedString != null && suppliedString.equals("secret");
}

public void login(String username, String password) {
    return isRightUsername(username) && isRightPassword(password);
}

public void loginTest() {
    boolean success = login("testuser", new Password("wrongpassword"));
    // success is true, despite the password being wrong
}

1

バックグラウンド

Javaでfinalを表示できる場所は3つあります。

クラスをfinalにすると、クラスのすべてのサブクラス化が防止されます。メソッドをfinalにすると、メソッドのサブクラスがメソッドをオーバーライドできなくなります。フィールドをfinalにすると、後で変更されることを防ぎます。

誤解

最終的なメソッドとフィールドの周りで起こる最適化があります。

最後の方法は、HotSpotがインライン化を介して簡単に最適化できるようにします。ただし、HotSpotは、他の方法で証明されるまでオーバーライドされないという前提で機能するため、メソッドが最終的でない場合でもこれを行います。SOの詳細

最終的な変数は積極的に最適化することができ、それについてはJLSセクション17.5.3で読むことができます。

ただし、その理解により、これらの最適化はいずれもクラスを最終的なものにすることではないことに注意する必要があります。クラスをファイナルにすることによるパフォーマンスの向上はありません。

クラスの最後の側面も不変性とは関係ありません。finalではない不変のクラス(BigIntegerなど)、または可変 finalのクラス(StringBuilderなど)を持つことができます。クラスがあればについての決定すべき最終的なものでは設計の問題です。

最終設計

文字列は、最もよく使用されるデータ型の1つです。それらはマップのキーとして検出され、ユーザー名とパスワードを保存し、キーボードまたはWebページのフィールドから読み取るものです。文字列はどこにでもあります。

地図

Stringをサブクラス化できる場合に何が起こるかを最初に検討するのは、誰かがStringのように見える可変Stringクラスを構築できることを認識することです。これはどこでもマップを台無しにします。

この架空のコードを考えてみましょう。

Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");

t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);

one.prepend("z");

これは一般にマップで可変キーを使用する場合の問題ですが、私がそこに到達しようとしているのは、突然マップのブレークに関する多くのことです。エントリは、マップの適切な場所にありません。HashMapでは、ハッシュ値が変更されている(変更されるはずである)ため、正しいエントリにありません。TreeMapでは、ノードの1つが間違った側にあるため、ツリーが壊れています。

これらのキーにStringを使用することは非常に一般的であるため、Stringをfinalにすることでこの動作を防ぐ必要があります。

Javaで文字列が不変である理由を読むことに興味があるかもしれません文字列の不変の性質についての詳細。

邪悪なひも

文字列にはさまざまな邪悪なオプションがあります。equalが呼び出されたときに常にtrueを返すStringを作成し、それをパスワードチェックに渡したかどうかを検討してください。または、MyStringへの割り当てにより、文字列のコピーが電子メールアドレスに送信されるようにしましたか?

これらは、文字列をサブクラス化する能力がある場合に非常に現実的な可能性です。

Java.lang文字列の最適化

前に述べたように、finalはStringを高速化しません。ただし、Stringクラス(および内の他のクラスjava.lang)は、フィールドおよびメソッドのパッケージレベルの保護を頻繁に使用して、他のjava.langクラスが常にStringのパブリックAPIを通過するのではなく、内部を調整できるようにします。StringBufferによって使用される範囲チェックなしのgetCharslastIndexOfなどの関数、または基になる配列を共有するコンストラクター(メモリの問題によりJava 6が変更されたことに注意してください)。

誰かがStringのサブクラスを作った場合、(それはの一部であった場合を除き、これらの最適化を共有することができないjava.lang、あまりにも、それはだ密封されたパッケージ)。

拡張のために何かを設計するのは難しい

拡張可能なものを設計するのは困難です。それは、何か他のものが変更できるように内部の一部を公開しなければならないことを意味します。

拡張可能な文字列では、メモリリークを修正できませんでした。それらの部分はサブクラスに公開されている必要があり、そのコードを変更すると、サブクラスが壊れることになります。

Javaは下位互換性を誇り、コアクラスを拡張することで、サードパーティのサブクラスとの計算可能性を維持しながら、物事を修正する機能の一部を失います。

Checkstyleには、すべてのクラスが次のいずれかであることを強制する「DesignForExtension」と呼ばれる、強制するルール(内部コードを書くときに本当にイライラさせられます)があります。

  • 概要
  • 最後の
  • 空の実装

合理的な理由は次のとおりです。

このAPIデザインスタイルは、サブクラスによる破損からスーパークラスを保護します。欠点は、サブクラスの柔軟性が制限されていることです。特に、スーパークラスでのコードの実行を防ぐことはできませんが、これは、サブクラスがスーパーメソッドの呼び出しを忘れてスーパークラスの状態を破壊できないことも意味します

実装クラスの拡張を許可すると、サブクラスが基になるクラスの状態を破壊し、スーパークラスが与えるさまざまな保証が無効になる可能性があることを意味します。Stringのような複雑なものの場合、その一部を変更する何か壊れることはほぼ確実です。

開発者hurbis

開発者であることのその部分。各開発者が独自のutilsコレクションを使用して独自のStringサブクラスを作成する可能性を考慮してください。ただし、これらのサブクラスを相互に自由に割り当てることはできません。

WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.

この方法は狂気につながります。あらゆる場所でStringにキャストし、StringがStringクラスのインスタンスであるかどうかを確認します。そうでない場合は、そのクラスに基づいて新しいStringを作成します。しないでください。

私は確かにあなたが良いのStringクラスを書くことができますよ...しかし、C ++を書くとに対処する必要があり、それらの狂気の人々に複数の文字列の実装を書いたままstd::stringchar*し、ブーストやから何かSString、およびすべての残りの部分

Java String Magic

Javaが文字列を使用して行う魔法の機能がいくつかあります。これらにより、プログラマーは対処しやすくなりますが、言語にいくつかの矛盾が生じます。Stringでサブクラスを許可すると、これらの魔法のようなものをどのように扱うかについて非常に重要な考えが必要になります。

  • 文字列リテラル(JLS 3.10.5

    できるようにするコードを持つ:

    String foo = "foo";

    これは、整数などの数値型のボクシングと混同しないでください。あなたはできないが1.toString()、あなたはできる"foo".concat(bar)

  • +オペレータ(JLS 15.18.1

    Javaの他の参照型では、演算子を使用できません。文字列は特別です。文字列連結演算子はコンパイラレベルでも機能するため、実行時ではなくコンパイル時に"foo" + "bar"なり"foobar"ます。

  • 文字列変換(JLS 5.1.11

    すべてのオブジェクトは、文字列コンテキストで使用するだけで文字列に変換できます。

  • 文字列インターン(JavaDoc

    文字列クラスは、文字列のプールにアクセスして、コンパイルタイプで文字列リテラルを入力したオブジェクトの標準的な表現を持つことができます。

Stringのサブクラスを許可すると、プログラミングを容易にするStringを含むこれらのビットは、他のString型が可能な場合に非常に困難または不可能になることを意味します。


0

Stringが最終的なものではない場合、すべてのプログラマーツールチェストには、独自の「いくつかの素晴らしいヘルパーメソッドを備えたString」が含まれます。これらはそれぞれ、他のすべてのツールチェストと互換性がありません。

私はそれを愚かな制限だと考えました。今日、これは非常に健全な決定であると確信しています。文字列を文字列として保持します。


finalJavaのfinalC#とは異なります。このコンテキストでは、C# 'sと同じものreadonlyです。参照してくださいstackoverflow.com/questions/1327544/...
Arseni Mourzenko

9
@MainMa:実際、このコンテキストでは、C#の封印と同じように見えpublic final class Stringます。
コンフィギュレー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.