web-dev-qa-db-ja.com

同じオブジェクトへの2つの参照が必要になるのはいつですか?

Java具体的には、他の言語でも可能性があります:同じオブジェクトへの2つの参照があると便利なのはいつですか?

例:

Dog a = new Dog();
Dob b = a;

これが役立つ状況はありますか? aで表されるオブジェクトとやり取りしたいときはいつでも、これがaを使用する好ましい解決策となるのはなぜですか?

20
Bassinator

例は、2つの個別のリストで同じオブジェクトを使用する場合です。

Dog myDog = new Dog();
List dogsWithRabies = new ArrayList();
List dogsThatCanPlayPiano = new ArrayList();

dogsWithRabies.add(myDog);
dogsThatCanPlayPiano.add(myDog);
// Now each List has a reference to the same dog

別の用途は、同じオブジェクトを再生している場合いくつかの役割

Person p = new Person("Bruce Wayne");
Person batman = p;
Person ceoOfWayneIndustries = p;
45

それは実際には驚くほど深い質問です!最新のC++(およびRustなどの最新のC++を採用している言語)の経験から、たいていはそうしたくないと思われます。ほとんどのデータでは、singleまたはunique( "owning")が必要です) 参照。このアイデアは、 線形型システム の1つの主な理由でもあります。

ただし、その場合でも、メモリへの短時間のアクセスに使用され、データが存在する時間のかなりの部分が持続しない、一時的な「借用」参照が通常は必要です。最も一般的には、オブジェクトを引数として別の関数に渡す場合(パラメーターも変数です!):

void encounter(Dog a) {
  hissAt(a);
}

void hissAt(Dog b) {
  // ...
}

条件に応じて2つのオブジェクトのいずれかを使用する、あまり一般的ではないケースです。どちらを選択しても、基本的に同じことを行います。

Dog a, b;
Dog goodBoy = whoseAGoodBoy ? a : b;
feed(goodBoy);
walk(goodBoy);
pet(goodBoy);

より一般的な使用法に戻りますが、ローカル変数は残して、フィールドに目を向けます。たとえば、GUIフレームワークのウィジェットには親ウィジェットがあることが多いため、10個のボタンを含む大きなフレームには少なくとも10個の参照(さらにいくつかの参照)が含まれます。 its親、およびおそらくイベントリスナーなどから)。あらゆる種類のオブジェクトグラフ、およびいくつかの種類のオブジェクトツリー(親/兄弟参照を持つもの)では、複数のオブジェクトがそれぞれ同じオブジェクトを参照しています。そして事実上すべてのデータセットは実際にはグラフです;-)

16
user7043

一時変数:次の疑似コードを検討してください。

Object getMaximum(Collection objects) {
  Object max = null;
  for (Object candidate IN objects) {
    if ((max is null) OR (candidate > max)) {
      max = candidate;
    }
  }
  return max;
}

変数maxcandidateは同じオブジェクトを指す場合がありますが、変数の割り当ては、異なるルールを使用して、異なるタイミングで変更されます。

6
user22815

この方法は、コンテキストに関係なく使用できる別のオブジェクトをすべてコールバックする複数のオブジェクトがある場合に最適です。

たとえば、タブ付きのインターフェースがある場合、Tab1、Tab2、およびTab3を使用できます。また、ユーザーがどのタブにいるかに関係なく、共通の変数を使用してコードを簡略化し、ユーザーがオンになっているタブをその場で把握する必要を減らすこともできます。

Tab Tab1 = new Tab();
Tab Tab2 = new Tab();
Tab Tab3 = new Tab();
Tab CurrentTab = new Tab();

次に、番号付きの各タブonClickで、そのタブを参照するようにCurrentTabを変更できます。

CurrentTab = Tab3;

これで、コード内で、実際にどのタブにいるかを知る必要なく、「CurrentTab」をそのまま呼び出すことができます。 CurrentTabのプロパティを更新することもでき、それらは参照されるタブに自動的に流れます。

3
JMD

bmustが有用であるために不明な "a"への参照であるシナリオはたくさんあります。特に:

  • コンパイル時にbが何を指しているのかわからないときはいつでも。
  • コンパイル時にわかっているかどうかにかかわらず、コレクションを反復処理する必要があるときはいつでも
  • 範囲が限られているときはいつでも

例えば:

パラメータ

public void DoSomething(Thing &t) {
}

tは、外部スコープからの変数への参照です。

戻り値とその他の条件値

Thing a = Thing.Get("a");
Thing b = Thing.Get("b");
Thing biggerThing = Thing.max(a, b);
Thing z = a.IsMoreZThan(b) ? a : b;

biggerThingおよびzは、それぞれaまたはbへの参照です。コンパイル時にどちらになるかわかりません。

ラムダとその戻り値

Thing t = someCollection.FirstOrDefault(x => x.whatever > 123);

xはパラメーター(上記の例1)であり、tは戻り値(上記の例2)です

コレクション

indexByName.add(t.name, t);
process(indexByName["some name"]);

index["some name"]は、大部分はより洗練された外観bです。これは、作成されてコレクションに詰め込まれたオブジェクトのエイリアスです。

ループ

foreach (Thing t in things) {
 /* `t` is a reference to a thing in a collection */
}

tは、イテレータ(前の例)によって返されたアイテム(例2)への参照です。

3
svidgen

他の回答を補足するために、同じ場所から始めて、データ構造を別の方法で走査することもできます。たとえば、BinaryTree a = new BinaryTree(...); BinaryTree b = aがある場合、次のように使用して、ツリーの左端のパスをaで、右端のパスをbでトラバースできます。

while (!a.equals(null) && !b.equals(null)) {
    a = a.left();
    b = b.right();
}

私がJavaを作成してからしばらく経っているので、そのコードは正しくないか、賢明ではありません。疑似コードとしてそれをもっと取りなさい。

3
Mike

それは重要なポイントですが、私見は理解する価値があります。

すべてOO言語は常に参照のコピーを作成し、オブジェクトを「見えないように」コピーすることは決してありません。はるかに困難になりますOO言語が他の方法で機能した場合にプログラムを作成します。たとえば、関数やメソッドはオブジェクトを更新できません。JavaとほとんどのOO言語は大幅に複雑にしないと、ほとんど使用できません。

プログラム内のオブジェクトには何らかの意味があるはずです。たとえば、実際の物理的な世界で特定の何かを表します。通常、同じものを多く参照することは理にかなっています。たとえば、私の自宅の住所は多くの人や組織に与えることができ、その住所は常に同じ物理的な場所を参照しています。つまり、最初のポイントは、オブジェクトは多くの場合、特定の、実際の、または具体的なものを表すということです。そして、同じものへの多くの参照を持つことができることは非常に便利です。そうしないと、プログラムを書くのが難しくなります。

aを引数/パラメータとして別の関数に渡すたびに、たとえば呼び出す
foo(Dog aDoggy);
またはaにメソッドを適用すると、基礎となるプログラムコードが参照のコピーを作成して、同じオブジェクトへの2番目の参照を生成します。

さらに、コピーされた参照を持つコードが別のスレッドにある場合、両方を同時に使用して同じオブジェクトにアクセスできます。

したがって、ほとんどの有用なプログラムでは、同じオブジェクトへの複数の参照が存在します。これは、ほとんどのOOプログラミング言語のセマンティクスであるためです。

今、それについて考えると、参照渡しはonlyメカニズムなので、多くのOO言語(C++両方をサポートします)、それは「正しい」defaultの動作であると期待できます。

私見、参照の使用は正しいdefaultですが、いくつかの理由があります。

  1. 2つの異なる場所で使用されるオブジェクトの値が同じであることを保証します。オブジェクトを2つの異なるデータ構造(配列、リストなど)に入れ、それを変更するオブジェクトに対していくつかの操作を行うことを想像してください。デバッグするのは悪夢かもしれません。さらに重要なのは、両方のデータ構造で同じオブジェクトがであるか、プログラムにバグがあることです。
  2. コードをいくつかの関数にリファクタリングしたり、いくつかの関数のコードを1つにマージしたりしても、セマンティクスは変わりません。言語が参照セマンティクスを提供しない場合、コードを変更することはさらに複雑になります。

効率の議論もあります。オブジェクト全体のコピーを作成することは、参照をコピーするよりも効率的ではありません。しかし、それは要点を逃していると思います。同じオブジェクトへの複数の参照は、実際の物理世界のセマンティクスと一致するため、より意味があり、使いやすくなっています。

だから、私見、それは通常同じオブジェクトへの複数の参照を持つことは理にかなっています。アルゴリズムのコンテキストでそれが意味をなさない珍しいケースでは、ほとんどの言語が「クローン」または深いコピーを作成する機能を提供します。ただし、これはデフォルトではありません。

これがデフォルトではないべきだと主張する人は、自動ガベージコレクションを提供しない言語を使用していると思います。たとえば、昔ながらのC++。問題は、「不要な」オブジェクトを収集する方法を見つける必要があり、まだ必要なオブジェクトを再利用しないことです。同じオブジェクトへの複数の参照があると、それが難しくなります。

C++に十分に低コストのガベージコレクションがあり、参照されているすべてのオブジェクトがガベージコレクションされている場合、反対意見の多くは解消されると思います。参照セマンティクスが必要なではない場合もいくつかあります。しかし、私の経験では、それらの状況を識別できる人は、通常、とにかく適切なセマンティクスを選択することもできます。

C++プログラムの大量のコードがガベージコレクションを処理または軽減するために存在するという証拠がいくつかあると思います。ただし、そのような「インフラストラクチャ」コードを記述して維持すると、コストが増加します。これは、言語をより使いやすく、またはより堅牢にするためにあります。したがって、たとえば、Go言語はC++の弱点の一部を修正することに重点を置いて設計されており、ガベージコレクション以外に選択肢はありません。

もちろん、これはJavaのコンテキストでは無関係です。これも使いやすいように設計されており、ガベージコレクションも行われています。したがって、複数の参照を持つことはデフォルトのセマンティクスであり、オブジェクトへの参照がある間はオブジェクトは回収されないという意味で比較的安全です。もちろん、オブジェクトが実際に終了したときにプログラムが適切に整理されていないため、データ構造によって保持されている可能性があります。

それで、質問に戻って(少し一般化して)、同じオブジェクトへの複数の参照が必要になるのはいつですか?私が考えることができるあらゆる状況でほとんど。これらは、ほとんどの言語のパラメーター受け渡しメカニズムのデフォルトのセマンティクスです。これは、現実の世界に存在するオブジェクトを処理するデフォルトのセマンティクスが参照によるものでなければならないためです(実際のオブジェクトはそこにあります)。

他のセマンティクスは処理が難しくなります。

Dog a = new Dog("rover");  // initialise with name 
DogList dl = new DogList()
dl.add(a)
...
a.setOwner("Mr Been")

dlの「ローバー」はsetOwnerの影響を受けるものである必要があります。そうしないと、プログラムの作成、理解、デバッグ、変更が難しくなります。そうでなければ、ほとんどのプログラマーは困惑したり失望したりすると思います。

その後、犬は販売されます:

soldDog = dl.lookupOwner("rover", "Mr Been")
soldDog.setOwner("Mr Mcgoo")

この種の処理は一般的で正常です。したがって、参照セマンティクスは、通常、最も意味があるため、デフォルトです。

概要:同じオブジェクトへの複数の参照があることは常に意味があります。

2
gbulmer

もちろん、次のようなシナリオがもう1つあります。

Dog a = new Dog();
Dog b = a;

コードを管理しているときにbは以前は別の犬または別のクラスでしたが、現在はaによってサービスされています。

一般に、中期的には、aを直接参照するようにすべてのコードを書き直す必要がありますが、すぐには発生しない可能性があります。

1
Mark Hurd

おそらく異なるコンポーネントがエンティティを使用しているために、プログラムがエンティティを複数の場所でメモリにプルする可能性があるときはいつでもこれが必要になります。

アイデンティティマップ エンティティの明確なローカルストアが提供されたため、2つ以上の個別の表現を避けることができます。同じオブジェクトを2回表すと、オブジェクトの1つの参照が他のインスタンスの状態の変化よりも前にその状態の変化を持続すると、クライアントは同時実行性の問題を引き起こすリスクを負います。アイデアは、クライアントが常にエンティティ/オブジェクトへの明確な参照を処理するようにすることです。

1
Mario T. Lanza

私は数独ソルバーを書くときにこれを使いました。行を処理しているときにセルの数がわかっている場合、列を処理しているときに、それを含む列にもそのセルの数を知らせたい。したがって、列と行は両方とも、重なり合うCellオブジェクトの配列です。正確に受け入れられた答えが示したように。

0
winkbrace