私は長い間ですJava=開発者であり、最後に、専攻の後、資格試験を受けるためにきちんと勉強する時間があります...常に私を悩ませてきた1つのことは、文字列です。 "final"。私はセキュリティの問題と関連するものについて読んだときにそれを理解しています...しかし、真剣に、誰かがその真の例を持っていますか?
たとえば、Stringがfinalでなければどうなりますか? Rubyにはないように。 Rubyコミュニティ...からの苦情は聞いていません...そして実装するために自分で実装するかWebで検索する必要があるStringUtilsと関連クラスを知っていますその動作(コードの4行)は、喜んで行います。
主な理由は速度です。final
クラスを拡張できないため、JITは文字列を処理するときにあらゆる種類の最適化を行うことができます。オーバーライドされたメソッドを確認する必要はありません。
もう1つの理由はスレッドセーフです。不変オブジェクトは、他のユーザーに渡す前に完全にビルドする必要があるため、常にスレッドセーフです。ビルド後は変更できなくなります。
また、Javaランタイムの発明者は、常に安全の面で誤りを犯したいと思っていました。Stringを拡張できること(Groovyで非常に便利なため、私がよく行うこと)は、何をしているかわからない場合はワーム。
JavaのString
クラスをfinalにする必要があるもう1つの理由があります。これは、一部のシナリオではセキュリティにとって重要です。 String
クラスがfinalでない場合、次のコードは悪意のある呼び出し元による微妙な攻撃に対して脆弱になります。
_ 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>";
}
_
攻撃:悪意のある呼び出し元が文字列のサブクラスEvilString
を作成する可能性があります。ここで、EvilString.startsWith()
は常にtrueを返しますが、EvilString
の値は何か悪です(たとえば、javascript:alert('xss')
)。サブクラス化により、これはセキュリティチェックを回避します。この攻撃は、チェック・タイムから使用時間(TOCTTOU)の脆弱性として知られています。チェックが行われたとき(URLがhttp/httpsで始まるとき)と値が使用されるときの間(htmlスニペットを構築するため)、実効値は変更される可能性があります。 String
が最終的なものでない場合、TOCTTOUのリスクが蔓延することになります。
したがって、String
がfinalでない場合、呼び出し元を信頼できない場合に安全なコードを書くのは難しくなります。もちろん、これはまさにJavaライブラリが置かれている位置です。これらは信頼できないアプレットによって呼び出される可能性があるため、呼び出し元を信頼しません。これは、不当に扱いにくいことを意味します。 String
がfinalでない場合は、安全なライブラリコードを記述します。
そうでない場合、マルチスレッド化されたJavaアプリは混乱します(実際のものよりもさらに悪い)。
IMHO最終的な文字列(不変)の主な利点は、それらが本質的にスレッドセーフであることです。同期が不要です(そのコードの記述はかなり簡単ですが、それほど簡単ではありません)。変更可能である場合は、マルチスレッド化されたWindowsツールキットのようなものが非常に困難であり、それらが厳密に必要であることを保証します。
FinalがJavaで表示される場所は3つあります。
クラスをfinalにすると、クラスのすべてのサブクラス化が防止されます。メソッドをfinalにすることで、メソッドのサブクラスがそれをオーバーライドするのを防ぎます。フィールドをfinalにすると、後で変更できなくなります。
最終的なメソッドとフィールドの周りで行われる最適化があります。
最後の方法は、HotSpotがインライン化によって最適化するのを容易にします。ただし、HotSpotは、メソッドがfinalでない場合でも、別の方法で証明されるまでオーバーライドされていないという前提で機能するため、これを行います。 SOについての詳細
最終的な変数は積極的に最適化することができ、その詳細についてはJLSセクション 17.5. で読むことができます。
ただし、その理解により、これらの最適化のどちらでもないはclass 最後の。クラスをfinalにすることによるパフォーマンスの向上はありません。
クラスの最後の側面も、不変性とは関係ありません。 finalではない不変のクラス( BigInteger など)、または変更可能なクラスand final( StringBuilder など)を持つことができます。 。クラスすべきが最終であるかどうかの決定は、設計の問題です。
文字列は、最もよく使用されるデータ型の1つです。それらはマップへのキーとして見つかり、ユーザー名とパスワードを格納します。これらはキーボードまたはWebページのフィールドから読み取ったものです。文字列はeverywhereです。
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");
_
これは一般にMapで可変キーを使用する場合の問題ですが、私がここで達成しようとしていることは、突然Mapブレークに関する多くのことが起きることです。エントリはマップの正しい場所にありません。 HashMapでは、ハッシュ値が変更されている(変更されているはずです)ため、適切なエントリにはありません。 TreeMapでは、ノードの1つが間違った側にあるため、ツリーが壊れています。
これらのキーに文字列を使用することは非常に一般的であるため、文字列をfinalにすることでこの動作を防ぐ必要があります。
文字列の不変な性質の詳細については、 Javaで文字列が不変なのはなぜですか? を読んでみてください。
文字列には、さまざまな悪質なオプションがいくつかあります。 equalが呼び出されたときに常にtrueを返す文字列を作成し、それをパスワードチェックに渡したかどうかを検討してください。または、MyStringへの割り当てが文字列のコピーを電子メールアドレスに送信するように作成しましたか?
これらは、Stringをサブクラス化する能力がある場合に非常に現実的な可能性です。
前述のように、finalはStringを高速化しません。ただし、Stringクラス(および_Java.lang
_の他のクラス)は、フィールドおよびメソッドのパッケージレベルの保護を頻繁に使用して、他の_Java.lang
_クラスがパブリックを介さずに内部をいじくれるようにします常にStringのAPI。範囲チェックなしの getChars のような関数、またはStringBufferによって使用される lastIndexOf 、または基になる配列を共有する constructor (Java 6メモリの問題のために変更されたもの)。
誰かがStringのサブクラスを作成した場合、それらの最適化を共有することはできません(それが_Java.lang
_の一部である場合を除きますが、それは 密封されたパッケージ です)。
拡張可能なものを設計することはhardです。それは、何かを変更できるように内部の一部を公開する必要があることを意味します。
拡張可能な文字列は メモリリーク を修正できませんでした。それらの部分はサブクラスに公開されている必要があり、そのコードを変更するとサブクラスが壊れることになります。
Javaは下位互換性を誇り、コアクラスを拡張に開放することで、サードパーティのサブクラスとの計算可能性を維持しながら、修正機能をいくらか失います。
Checkstyle には、 "DesignForExtension"と呼ばれるルールがあり(内部コードを書くときに本当にイライラします)、すべてのクラスが次のいずれかであることを強制します。
合理的なものは次のとおりです。
このAPI設計スタイルは、サブクラスによる破損からスーパークラスを保護します。欠点は、サブクラスの柔軟性に制限があることです。特に、スーパークラスでのコードの実行を妨げることはできませんが、サブクラスがスーパーメソッドの呼び出しを忘れてスーパークラスの状態を破壊することはできません。
実装クラスの拡張を許可するということは、サブクラスがベースとなるクラスの状態を破壊し、スーパークラスが提供するさまざまな保証が無効になるような状態になる可能性があることを意味します。文字列のように複雑なものの場合、その一部を変更することはほぼ確実ですwill何かが壊れます。
開発者であることの一部。各開発者が tils の独自のコレクションを含む独自のStringサブクラスを作成する可能性を考慮してください。ただし、これらのサブクラスを自由に相互に割り当てることはできなくなりました。
_WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
_
この方法は狂気につながります。あらゆる場所で文字列にキャストし、文字列がyour文字列クラスのインスタンスであるかどうかを確認します。そうでない場合は、その文字列に基づいて新しい文字列を作成します。しないでください。
きっと良いStringクラスを書くことができると思います...しかし、C++を書き、_std::string
_と_char*
_とboostからの何かを扱う必要があるクレイジーな人々に複数の文字列実装を書くことを残してください- SString 、および 残りのすべて 。
Javaが文字列に対して行う魔法のようなことがいくつかあります。これらにより、プログラマーは扱いやすくなりますが、言語に不整合が生じます。 Stringでサブクラスを許可するには、これらの不思議なことに対処する方法について非常に重要な考慮が必要です。
文字列リテラル(JLS .10.5 )
実行を可能にするコードを持っている:
_String foo = "foo";
_
これは、Integerのような数値型のボックス化と混同しないでください。 1.toString()
はできませんが、"foo".concat(bar)
はできます。
_+
_演算子(JLS 15.18.1 )
Javaの他の参照タイプでは、演算子を使用できません。文字列は特別です。文字列連結演算子はコンパイラレベルでも機能するため、_"foo" + "bar"
_は、実行時ではなくコンパイル時に_"foobar"
_になります。
文字列変換(JLS 5.1.11 )
すべてのオブジェクトは、文字列コンテキストで使用するだけで文字列に変換できます。
文字列インターニング( JavaDoc )
Stringクラスは、コンパイルタイプで文字列リテラルが入力されたオブジェクトの正規表現を持つことを可能にする文字列のプールにアクセスできます。
Stringのサブクラスを許可すると、他のString型が可能である場合、プログラミングを容易にするStringのこれらのビットを実行することが非常に困難または不可能になります。
最終的なString
のもう1つの利点は、不変性と同様に、予測可能な結果が保証されることです。 String
はクラスですが、値型などのいくつかのシナリオで扱われることを意図しています(コンパイラはString blah = "test"
)。したがって、特定の値を持つ文字列を作成した場合、整数の場合と同様に、任意の文字列に継承される特定の動作が期待されます。
これについて考えてみてください。もしあなたがサブクラスJava.lang.String
およびequals
またはhashCode
メソッドを上書きしましたか?突然、2つの文字列「test」が互いに等しくなくなりました。
これができたら混乱を想像してみてください。
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
}
Stringがfinalでない場合、すべてのプログラマツールチェストには、独自の「少数のNiceヘルパーメソッドを含むString」が含まれます。これらのそれぞれは、他のすべてのツールチェストと互換性がありません。
それはばかげた制限だと思っていました。今日、これは非常に健全な決定であると確信しています。文字列を文字列のままにします。