オブジェクト参照がメソッドに渡された場合、オブジェクトをメソッドに対して「読み取り専用」にすることは可能ですか?
11 に答える
厳密に言えば。つまり、オブジェクトを変更できる参照を、オブジェクトを変更できない参照に変換することはできません。また、規則を使用する以外に、型が不変または可変であることを表現する方法はありません。
何らかの形の不変性を保証する唯一の機能はfinal
フィールドです。一度書き込まれると、変更することはできません。
とはいえ、不要なミューテーションが防止されるようにクラスを設計する方法があります。ここにいくつかのテクニックがあります:
防御的なコピー。オブジェクトのコピーを渡して、変更された場合に内部の不変条件を壊さないようにします。
アクセス修飾子やインターフェースを使用して、読み取り専用メソッドのみを公開します。アクセス修飾子(
public
/private
/protected
)を使用して、場合によってはインターフェイスと組み合わせて、特定のメソッドのみが他のオブジェクトに表示されるようにすることができます。公開されているメソッドが本質的に読み取り専用である場合は、安全です。デフォルトでオブジェクトを不変にします。オブジェクトに対する操作は、実際にはオブジェクトのコピーを返します。
また、SDKのAPIには、オブジェクトの不変バージョンを返すメソッドがある場合があることに注意してくださいCollections.unmodifiableList
。不変リストを変更しようとすると、例外がスローされます。これは、静的に(静的型システムを使用したコンパイル時に)不変性を強制しませんが、動的に(実行時に)強制するための安価で効果的な方法です。
エイリアシングとアクセシビリティのより良い制御のためのJava拡張の多くの研究提案がありました。たとえば、readonly
キーワードの追加。私の知る限り、Javaの将来のバージョンに含める予定はありません。興味がある場合は、これらのポインタを確認できます。
- Javaに「読み取り専用」を追加すべきではない理由(まだ) -ほとんどの提案をリストして比較します
- Checker Framework:Java用のカスタムプラグ可能型-特に不変型を使用して、型システムを拡張するための邪魔にならない方法。
チェッカーフレームワークは非常に興味深いものです。チェッカーフレームワークで、Generic Universe Typesチェッカー、IGJ不変性チェッカー、およびJavari不変性チェッカーを確認します。フレームワークは注釈を使用して機能するため、邪魔になりません。
いいえ、装飾、合成、クローン作成などが必要です。
そのための一般的なメカニズムはありません。それを実現するには、不変のラッパーを作成するなど、特殊なケースのコードを作成する必要があります(を参照Collections.unmodifiableList
)。
Object
ほとんどの場合、このようなメソッドの最初のステートメントとしてを複製することで、同様のことを実現できます...
public void readOnlyMethod(Object test){
test = test.clone();
// other code here
}
したがってreadOnlyMethod()
、いずれかを呼び出して渡すとObject
、のクローンObject
が取得されます。クローンはメソッドのパラメーターと同じ名前を使用するため、元のを誤って変更するリスクはありませんObject
。
いいえ。ただし、オブジェクトを渡す前に複製を試みることができるため、メソッドによって行われた変更は元のオブジェクトに影響しません。
読み取り専用メソッドのみ(セッターメソッドなし)のインターフェイスを実装することで、オブジェクトのコピー(道路のみのコピー)が提供され、オブジェクト自体のインスタンスを返す代わりに、インターフェイスの読み取り専用インスタンスが返されます。
オブジェクトのすべてのパラメータを次のように定義できますfinal
が、これにより、オブジェクトはすべての人に読み取り専用になります。
あなたの本当の質問は、エスケープ参照を回避することだと思います。
クラスからインターフェイスを抽出し、getメソッドのみを公開するためのいくつかの回答で指摘されているように。誤って変更することはありませんが、上記の問題を回避するための絶対確実な解決策ではありません。
以下の例を検討してください。
Customer.java:
public class Customer implements CustomerReadOnly {
private String name;
private ArrayList<String> list;
public Customer(String name) {
this.name=name;
this.list = new ArrayList<>();
this.list.add("First");
this.list.add("Second");
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ArrayList<String> getList() {
return list;
}
public void setList(ArrayList<String> list) {
this.list = list;
}
}
CustomerReadOnly.java:
public interface CustomerReadOnly {
String getName();
ArrayList<String> getList();
}
Main.java:
public class Test {
public static void main(String[] args) {
CustomerReadOnly c1 = new Customer("John");
System.out.println("printing list of class before modification");
for(String s : c1.getList()) {
System.out.println(s);
}
ArrayList<String> list = c1.getList();
list.set(0, "Not first");
System.out.println("printing list created here");
for(String s : list) {
System.out.println(s);
}
System.out.println("printing list of class after modification");
for(String s : c1.getList()) {
System.out.println(s);
}
}
}
Ouput:
printing list of class before modification
First
Second
printing list created here
Not first
Second
printing list of class after modification
Not first
Second
したがって、ご覧のとおり、インターフェイスの抽出とgetメソッドのみの公開は、可変メンバー変数がない場合にのみ機能します。
クラスからエスケープしたくない参照を持つメンバー変数としてコレクションがある場合はCollections.unmodifiableList()
、ewernliの回答で指摘されているように使用できます。
これにより、外部コードで基になるコレクションを変更することはできず、データは完全に読み取り専用になります。
しかし、同じことを行うためのカスタムオブジェクトに関しては、誤って変更を防ぐことができるInterfaceメソッドのみを認識していますが、参照エスケープを回避するための絶対確実な方法についてはわかりません。
ルールを適用する場所によって異なります。プロジェクトで共同作業している場合は、final
この値を変更することを意図していないことを次の人に伝えるコメントとともに使用します。それ以外の場合は、オブジェクトに触れないようにメソッドを作成するだけではありませんか?
public static void main(String[] args) {
cantTouchThis("Cant touch this");
}
/**
*
* @param value - break it down
*/
public static void cantTouchThis(final String value) {
System.out.println("Value: " + value);
value = "Nah nah nah nah"; //Compile time error
}
したがって、特にこのメソッドでは、値が書き込まれることはなく、コンパイル時に適用されるため、ソリューションは非常に堅牢になります。このメソッドの範囲外では、オブジェクトは、ラッパーを作成しなくても変更されません。
private boolean isExecuteWriteQueue = false;
public boolean isWriting(){
final boolean b = isExecuteWriteQueue;
return b;
}
ewernliの答えを拡張して...
クラスを所有している場合は、読み取り専用インターフェイスを使用して、オブジェクトの読み取り専用参照を使用するメソッドが子の読み取り専用コピーのみを取得できるようにすることができます。メインクラスは書き込み可能なバージョンを返します。
例
public interface ReadOnlyA {
public ReadOnlyA getA();
}
public class A implements ReadOnlyA {
@Override
public A getA() {
return this;
}
public static void main(String[] cheese) {
ReadOnlyA test= new A();
ReadOnlyA b1 = test.getA();
A b2 = test.getA(); //compile error
}
}
クラスを所有していない場合は、クラスを拡張して、セッターをオーバーライドしてエラーまたはno-opをスローし、別のセッターを使用できます。これにより、基本クラスが読み取り専用クラスを効果的に参照できるようになりますが、混乱を招きやすく、バグを理解しにくいため、十分に文書化されていることを確認してください。