オブジェクト参照がメソッドに渡された場合、オブジェクトをメソッドに対して「読み取り専用」にすることは可能ですか?
厳密に言えば。つまり、オブジェクトを変更できる参照を、オブジェクトを変更できない参照に変換することはできません。また、規則を使用する以外に、型が不変または可変であることを表現する方法はありません。
何らかの形の不変性を保証する唯一の機能は、final
フィールドです。一度書き込まれると、変更できません。
とはいえ、不要な突然変異が防止されるようにクラスを設計する方法があります。ここにいくつかのテクニックがあります:
防御的コピー。オブジェクトのコピーを渡して、変更された場合に内部不変条件を壊さないようにします。
アクセス修飾子および/またはインターフェースを使用して、読み取り専用メソッドのみを公開します。アクセス修飾子(public
/private
/protected
)を使用して、場合によってはインターフェイスと組み合わせて、特定のメソッドのみが他のオブジェクトに表示されるようにすることができます。公開されているメソッドが本質的に読み取り専用である場合は、安全です。
オブジェクトをデフォルトで不変にします。オブジェクトに対する操作は、実際にはオブジェクトのコピーを返します。
また、SDKのAPIには、オブジェクトの不変バージョンを返すメソッドがある場合があることに注意してください。 Collections.unmodifiableList
。不変リストを変更しようとすると、例外がスローされます。これは、静的に(静的型システムを使用したコンパイル時に)不変性を強制しませんが、動的に(実行時に)強制するための安価で効果的な方法です。
エイリアシングやアクセシビリティをより適切に制御するためのJava拡張機能)に関する多くの研究提案があります。たとえば、readonly
キーワードの追加です。 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);
}
}
}
_
出力:
_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メソッドのみの公開は、可変メンバー変数がない場合にのみ機能します。
クラスからの脱出を望まない参照を持つメンバー変数としてコレクションがある場合は、ewernliの回答で指摘されているようにCollections.unmodifiableList()
を使用できます。
これにより、外部コードで基になるコレクションを変更することはできず、データは完全に読み取り専用になります。
しかし、同じことを行うためのカスタムオブジェクトに関しては、誤って変更を防ぐことができるInterfaceメソッドのみを認識していますが、参照エスケープを回避するための絶対確実な方法についてはわかりません。
Ewernliの answer ..を拡張します。
クラスを所有している場合は、読み取り専用インターフェイスを使用して、オブジェクトの読み取り専用参照を使用するメソッドが子の読み取り専用コピーのみを取得できるようにすることができます。メインクラスは書き込み可能なバージョンを返します。
例
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をスローし、個別のセッターを使用できます。これにより、基本クラスが読み取り専用クラスを効果的に参照できるようになりますが、混乱を招きやすく、バグを理解しにくいため、十分に文書化されていることを確認してください。
ルールを適用する場所によって異なります。プロジェクトで共同作業をしている場合は、final
を使用して、次の人にこの値を変更するつもりはないことを伝えるコメントを付けます。それ以外の場合は、単にメソッドを記述してnotオブジェクトにタッチしませんか?
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
}
したがって、特にこのメソッドでは、値が書き込まれることはなく、コンパイル時に適用されるため、ソリューションは非常に堅牢になります。このメソッドの範囲外では、オブジェクトは、ラッパーを作成しなくても変更されません。