1. 序章
この記事では、正確なメモリを文字通り共有することと、必要に応じてコピーを作成することの両方によって、コードの異なる領域間で同じデータを安全に共有する方法のいくつかを見ていきます。
2. 参照と値
Javaなどの多くの言語では、ほとんどの変数は実際の値を格納せず、代わりに値への参照またはポインターを格納します:
このように作業することには、いくつかの重要な利点があります。 たとえば、変数を渡す場合、大きい値ではなく小さい参照のみを渡します。
これにより、多くの異なる変数がメモリ内のまったく同じ値を指すようにすることもできます。
これは、両方の変数がまったく同じデータを参照することを意味するため、便利です。 ただし、一方が変更された場合、もう一方も同時に同じ変更を自動的に確認することも意味します。2つの変数は常に同じです。
これは、オブジェクトを使用する場合にのみ適用されることに注意してください。 intやbyteなどのプリミティブは常に保存され、値への参照ではなく正確な値として渡されます。 最大のプリミティブ– long –は通常、参照と同じ量のメモリであり、プリミティブは常に不変であるため、とにかく変更できないため、これは問題ありません。
3. 浅いコピーとは何ですか?
場合によっては、値のコピーを作成して、2つの異なるコードが同じ値の異なるコピーを参照できるようにすることができます。これにより、たとえば、一方を他方とは異なる方法で操作できます。
これを行う最も簡単な方法は、オブジェクトの浅いコピーを作成することです。 これは、元のフィールドと同じフィールドをすべて含み、同じ値のコピーを持つ新しいオブジェクトを作成することを意味します。
比較的単純なオブジェクトの場合、これは正常に機能します。 ただし、オブジェクトに他のオブジェクトが含まれている場合は、これらへの参照のみがコピーされます。つまり、2つのコピーには、メモリ内の同じ値への参照が含まれ、長所と短所があります。これには以下が含まれます。
この例では、オリジナルとコピーの両方に、同じ番号のリストを指すフィールド“ def”があります。 それらの1つがリストを変更すると、もう1つにも同じ変更が表示されます。 ただし、元のデータのコピーを作成したため、基になるデータがまだそれらの間で共有されていることに驚くかもしれません。これにより、コードに予期しないバグが発生する可能性があります。
4. ディープコピーとは何ですか?
これに代わる方法は、オブジェクトのディープコピーを実行することです。 ここで、各フィールドを元のフィールドからコピーにコピーしますが、その際、参照をコピーするだけでなく、それらのディープコピーを実行します:
これは、新しいコピーが元のコピーの正確なコピーであることを意味しますが、一方への変更が他方に反映されないように接続されていません。
5. 不変性とコピー
データのコピーを作成する主な利点は、2つの異なるコードが干渉なしにデータに作用できることです。それぞれがまったく同じリストを与えられた2つのコードがあり、1つがアイテムを削除する場合それから、他の人もその変化を見るでしょう。 リストのコピーを作成するということは、一方への変更が他方に表示されないことを意味します。
ただし、オブジェクトのコピーにはコストがかかる場合があります。 オブジェクト構造が複雑になるほど、コストが高くなる可能性があります。 また、場合によっては、コピーが不可能な場合があります。たとえば、オブジェクトがコンピュータメモリだけでなく、ネットワークソケットやファイルハンドルなどの物理リソースを表す場合です。
ただし、別の方法があります。 オブジェクトが不変である場合、つまり値を変更できない場合は、異なるコード間でまったく同じ値を共有するリスクがはるかに少なくなります。リストを異なるコードに渡す場合コードですが、変更されないことを保証できます。そうすれば、これが安全であることがわかります。
ただし、特にネストされた構造では、不変のコードを書くのは必ずしも簡単ではありません。 たとえば、ゲッターのみがあり、セッターがないオブジェクトがある場合、そのフィールドを変更することはできません。 このオブジェクト自体は不変ですが、これらのフィールドのいずれか自体が不変である場合、同じ問題が発生する可能性があります。
class Immutable {
private final List<String> names = new ArrayList<>();
public List<String> getNames() {
return names;
}
}
この例では、オブジェクトのnamesフィールドを変更することはできません。 常に同じリストを指します。 しかし、ここではどうなりますか?
var immutable = new Immutable();
var immutable2 = immutable;
immutable.getNames().add("Baeldung");
names フィールドを変更することはできませんが、新しいエントリを挿入することができました。 また、このエントリは、immutableとimmutable2の両方が同じメモリを指しているため、同時に表示されます。
6. コピーオンライト
場合によっては、変更可能な値が必要ですが、必要がなければコピーのコストを払いたくない場合があります。この場合、コピーと呼ばれるパターンを使用できます。 -オンライト。 この場合、元のオブジェクトを指すオブジェクトのコピーを作成します。 ただし、変更を加えたい場合は、すぐにオリジナルのコピーを作成します。
class Original {
private String value;
public String getValue() {}
public String setValue(String value) {}
}
class CopyOnWrite {
private Original value;
private boolean copied;
public String getValue() {
return this.value.getValue();
}
public String setValue(String newValue) {
if (!copied) {
this.value = deepCopy(this.value);
copied = true;
}
this.value.setValue(newValue);
}
}
ここで、 CopyOnWrite クラスは、Originalクラスのインスタンスをラップします。 これは、まったく同じ値を安価に共有できることを意味します。 ただし、ラッパーで初めて setValue()を呼び出すと、すぐに停止して元のローカルコピーを作成します。
この時点で、ディープコピーを実行するためのコストを支払っていますが、これは、変更がこのインスタンスに対してのみローカルであり、他のインスタンスでは表示されないことを意味します。
7. 概要
ここでは、コードのさまざまな領域間でデータを共有できるいくつかの方法を確認し、一方の領域が他の領域に不注意に影響を与えないようにするためのいくつかの方法を検討しました。