クラスJava.lang.String
がJavaでfinalとして宣言されていることを知ったときから、なぜそうなのかと思っていました。当時は答えが見つかりませんでしたが、この投稿: JavaでStringクラスのレプリカを作成する方法? クエリを思い出しました。
確かに、Stringは私が必要とするすべての機能を提供し、Stringクラスの拡張を必要とする操作を考えたことはありませんが、それでも誰かが必要とするものがわからないでしょう!
では、デザイナーが最終決定を下したとき、デザイナーの意図が何であるかを誰もが知っていますか?
文字列を不変オブジェクトとして実装すると非常に便利です。 immutabilityについて読んで、それについてさらに理解してください。
不変オブジェクトの利点の1つは、
単一のインスタンスを指すことで、重複を共有できます。
(from here )。
文字列が最終的なものではない場合、サブクラスを作成し、「文字列として見た」ときに似たような2つの文字列を作成できますが、実際には異なります。
これは素敵な記事です 上記の回答ですでに言及された2つの理由を概説しています:
そして、これはおそらくその記事の中で最も詳細なコメントです。 Javaの文字列プールとセキュリティの問題に関係しています。何が文字列プールに入るかを決定する方法について。文字のシーケンスが同じである場合、両方の文字列が等しいと仮定すると、最初に誰がそこに着くのか、それに伴ってセキュリティの問題が発生するという競合状態になります。そうでない場合、文字列プールには冗長文字列が含まれるため、そもそもそれを使用する利点が失われます。自分で読んでみてください。
Stringを拡張すると、equalsとinternで大混乱が発生します。 JavaDocは等しいと言います:
この文字列を指定されたオブジェクトと比較します。引数がnullではなく、このオブジェクトと同じ文字シーケンスを表すStringオブジェクトである場合にのみ、結果はtrueです。
Java.lang.String
が最終ではないと仮定すると、SafeString
はString
と等しくなり、逆もまた同様です。同じ文字列を表すためです。
intern
をSafeString
に適用するとどうなりますか-SafeString
はJVMの文字列プールに入りますか? ClassLoader
およびSafeString
が参照を保持しているすべてのオブジェクトは、JVMの存続期間中、所定の位置にロックされます。誰が最初に一連のキャラクターをインターンすることができるかについての競合状態を取得します-多分あなたのSafeString
が勝つ、多分String
、または多分SafeString
異なるクラスローダー(したがって異なるクラス)。
プールでのレースに勝った場合、これは真のシングルトンとなり、人々はリフレクションとsecretKey.intern().getClass().getClassLoader()
を介して環境全体(サンドボックス)にアクセスできます。
または、JVMは、具体的なStringオブジェクトのみ(サブクラスは含まない)がプールに追加されるようにすることで、このホールをブロックできます。
SafeString
!= String
のようにequalsが実装された場合、SafeString.intern
!= String.intern
、およびSafeString
をプールに追加する必要があります。プールは<Class, String>
ではなく<String>
のプールになり、プールに入るのに必要なのは新しいクラスローダーだけになります。
Stringが不変またはfinalである絶対に最も重要な理由は、それがクラスローディングメカニズムによって使用されるため、深層で基本的なセキュリティの側面があることです。
Stringが可変または最終ではなかった場合、「Java.io.Writer」をロードする要求が「mil.vogoon.DiskErasingWriter」をロードするように変更された可能性があります
String
はJavaの非常にコアなクラスであり、不変など、特定の方法で動作することに多くのことが依存しています。
クラスfinal
を作成すると、これらの仮定を破るサブクラスができなくなります。
現在でも、リフレクションを使用する場合は、 文字列を分割できます (値またはハッシュコードを変更する)に注意してください。リフレクションはセキュリティマネージャーで停止できます。 String
がfinal
でない場合、誰でもできます。
final
として宣言されていない他のクラスでは、多少壊れたサブクラスを定義できます(たとえば、間違った位置に追加されるList
があります)が、少なくともJVMはそのサブクラスに依存しませんコア操作。
ブルーノが言ったように、それは不変性に関するものです。文字列だけでなく、ラッパーなども同様です。ダブル、整数、文字など。これには多くの理由があります。
基本的には、プログラマーとして、あなたの文字列が決して変更されないことを確信できるようにするためです。また、それがどのように機能するかを知っていれば、メモリ管理を改善できます。 「hello」など、2つの同一の文字列を次々に作成してみてください。デバッグすると、それらが同一のIDを持っていることに気付くでしょう。つまり、それらはまったく同じオブジェクトです。これは、Javaで実行できるためです。文字列がミュータブルである場合、これは不可能です。彼らは決して変わらないので、彼らは私と同じようにすることができます。したがって、1,000,000個の文字列「hello」を作成することにした場合、実際に行うのは「hello」への1,000,000個のポインタを作成することです。同様に、文字列の関数またはその理由のためのラッパーをすべて割り当てると、別のオブジェクトが作成されます(オブジェクトIDをもう一度見てください-変更されます)。
さらにJavaのfinalは、必ずしも必要ではありませんオブジェクトが変更できないことを意味しません(たとえばC++とは異なります)。つまり、それが指すアドレスは変更できませんが、プロパティや属性を変更することはできます。そのため、場合によっては不変性と最終の違いを理解することが非常に重要です。
HTH
参照:
既に多くの良い点が述べられているので、別の点を追加したいと思います-StringがJavaで不変である理由の1つはStringがハッシュコードをキャッシュする Javaの不変の文字列はハッシュコードをキャッシュし、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;
}
既にコンピューターの場合は、値を返します。
他の回答(セキュリティ、不変性、パフォーマンス)で言及されている理由に加えて、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.
実装を単純化するためだったかもしれません。クラスのユーザーが継承できるクラスを設計する場合、設計で考慮すべきまったく新しいユースケースセットがあります。 X保護フィールドでこれまたはこれを行うとどうなりますか?最終的には、パブリックインターフェイスを正常に動作させることに集中し、確実に機能させることができます。
他の回答で示唆されている明白な理由とは別に、Stringクラスをfinalにすることの1つの考えは、仮想メソッドのパフォーマンスオーバーヘッドに関連している可能性もあります。 Stringは重いクラスであり、この最終クラスを作成することは、サブ実装が確実に行われず、間接呼び出しのオーバーヘッドがまったくないことを意味します。もちろん、今では仮想呼び出しなどの機能があり、これらは常にこのような最適化を行います。
まあ、私は正しいかどうかわからないいくつかの異なる考えを持っていますが、Java Stringではプリミティブデータ型として扱うことができる唯一のオブジェクトであり、Stringオブジェクトを作成できることを意味しますas String name = "Java"。 値によるコピー not 参照によるコピーである他のプリミティブデータ型と同様に、Stringは同じ動作をすることが期待されるため、Stringは最終的なものです。それが私の考えだ。完全に非論理的な場合は無視してください。
より良い実装が得られないようにするため。もちろん、インターフェースである必要があります。
[編集]ああ、もっと無知な下票を得る。答えは完全に深刻です。愚かな文字列の実装を何回かプログラムしなければならず、深刻なパフォーマンスと生産性の低下につながりました。
メソッド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!
と言うことを期待してgreetメソッドを呼び出しますが、取得するのは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!"
}
}
この状況は、avoidedになります。Employee
クラスがfinal
になった場合。 String
クラスがfinal
として宣言されていない場合、生意気なプログラマーが引き起こす可能性のある混amountの量は、あなたの想像次第です。
文字列の最終性も標準としてそれらを守ります。 C++では、文字列のサブクラスを作成できるため、すべてのプログラミングショップが独自の文字列バージョンを持つことができます。これは強力な基準の欠如につながります。
JVMは不変のものを知っていますか?答えは「いいえ」です。定数プールにはすべての不変フィールドが含まれていますが、すべての不変フィールド/オブジェクトは定数プールのみに格納されていません。不変性とその機能を実現する方法で実装するだけです。 CustomStringは、プーリングにJavaの特別な動作を提供するMarkerInterfaceを使用して最終的なものにすることなく実装できますが、この機能はまだ待っています!
ほとんどの答えは不変性に関連しています-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から離れることもできます)