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


20

特にJavaでは、他の言語でも同様です。同じオブジェクトへの2つの参照がいつ役立つのでしょうか。

例:

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

これが役立つ状況はありますか?でa表されるオブジェクトとやり取りしたいときはいつでも、これが使用するのに好ましいソリューションになるのはなぜaですか?


それはフライウェイトパターンの背後にある本質です。

@MichaelT詳しく説明してもらえますか?
バシネーター14

7
場合ab 、常に同じを参照しDog、それにも意味がありません。場合によっては、それは非常に便利です。
ゲイブ14

回答:


45

例は、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;

4
申し訳ありませんが、ブルース・ウェインの例には設計上の欠陥があり、バットマンとCEOのポジションがあなたの役割になるはずだと思います。
シルビウブルシア14

44
-1バットマンの秘密のアイデンティティを明らかにした。
アンプト14

2
@Silviu Burcea-ブルース・ウェインの例が良い例ではないことに強く同意します。一方では、グローバルテキストの編集によって名前「ceoOfWayneIndustries」および「batman」が「p」に変更され(名前の衝突やスコープの変更がないと仮定)、プログラムのセマンティクスが変更された場合、それらは壊れています。参照されるオブジェクトは、プログラム内の変数名ではなく、実際の意味を表しています。異なるセマンティクスを持つために、それは異なるオブジェクトであるか、参照(透過的である必要があります)よりも多くの動作を持つ何かによって参照されるため、2 +参照の例ではありません。
gbulmer

2
書かれたブルース・ウェインの例はうまくいかないかもしれないが、私は述べられた意図が正しいと信じている。おそらく、より近い例としては、Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;複数の可能な値(または使用可能な値のリスト)と、現在アクティブ/選択されている値への参照がある場合があります。
ダンプゼイ14

1
@gbulmer:2つのオブジェクトへの参照を持つことはできません。参照currentPersonaは一方のオブジェクトまたは他方を指しますが、両方を指すことはありません。特に、簡単にその可能性がありますcurrentPersonaされて決してに設定されていないbruce、それは確かに二つのオブジェクトへの参照ではありません、その場合には、。私の例では、batmancurrentPersonaは両方とも同じインスタンスへの参照ですが、プログラム内で異なる意味上の意味を持っています。
ダンプゼイ14

16

それは実際、驚くほど深遠な質問です!最新のC ++(およびRustなどの最新のC ++から取った言語)の経験から、これは望ましくないことが非常に多いことがわかります。ほとんどのデータでは、単一または一意の(「所有」)参照が必要です。この考え方は、線形型システムの 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個の参照があります(さらにその親からそしておそらくイベントリスナーなどから)。あらゆる種類のオブジェクトグラフ、およびいくつかの種類のオブジェクトツリー(親/兄弟参照を持つもの)には、それぞれが同じオブジェクトを参照する複数のオブジェクトがあります。そして、事実上すべてのデータセットは実際にはグラフです;-)


3
「あまり一般的ではないケース」は非常に一般的です。オブジェクトをリストまたはマップに保存し、必要なオブジェクトを取得して、必要な操作を実行します。マップまたはリストからそれらの参照を消去しません。
SJuan76

1
@ SJuan76「あまり一般的ではない」とは、ローカル変数から取得することのみに関するものであり、データ構造を含むものはすべて最後のポイントに該当します。

参照カウントのメモリ管理のため、これは1つの参照のみの理想ですか?もしそうなら、それはモチベーションが異なるガベージコレクタと他の言語に関連していないことを言及(例えばC#やJava、Pythonの)価値があるだろう
MarkJ

@MarkJ Linear型は単なる理想ではなく、多くの既存のコードが無意識のうちにそれに準拠しています。これは、かなりの場合に意味的に正しいからです。これは、潜在的なパフォーマンス上の利点を持っていません(ただし、どちらもあなたが実際に参照カウントが足さおよびトレースの両方を省略し、その知識を活用することを別の戦略を使用するときに参照カウントやトレースのGCのために、それだけで助けていません)。さらに興味深いのは、単純で決定論的なリソース管理への応用です。RAIIとC ++ 11はセマンティクスを移動すると思いますが、より良い(より頻繁に適用され、コンパイラーがミスをキャッチします)。

6

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

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

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


3

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

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

Javaを書いてからしばらく経ちました。そのため、コードが正しくない、または賢明ではないかもしれません。擬似コードとしてより多くを取ります。


3

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

たとえば、タブ付きインターフェイスがある場合、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

有用であるためには未知の「」への参照でb なければならない多くのシナリオがあります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)への参照です。


あなたの例に従うことは困難です。誤解しないでください、私はそれらを乗り越えましたが、私はそれに取り組む必要がありました。将来的には、いくつかの無関係な例を1つのブロックに入れずに、コード例を個々のブロックに分割することをお勧めします。
バシネーター14

1
@HCBPshenanigans選択した回答を変更することは期待していません。しかし、読みやすくするために、また選択した回答にないユースケースのいくつかを埋めるために、私のものを更新しました。
svidgen 14

2

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

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

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

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

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

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

考えてみると、参照渡しは多くのOO言語で利用可能な唯一のメカニズムであるため(C ++は両方をサポートします)、「正しい」デフォルトの動作であると予想されるかもしれません。

私見、参照を使用することは、いくつかの理由から正しいデフォルトです

  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")

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

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


ポイント良いが、これは、より良いコメントとして適している
Bassinator

@HCBPshenanigans-要点はあまりにも簡潔で、あまりにも多くのことを言わなかったと思う。だから私は推論を拡大しました。あなたの質問に対する「メタ」な答えだと思います。要約すると、プログラム内の多くのオブジェクトは実世界の特定のまたは一意のインスタンスを表すため、プログラムを簡単に作成するには、同じオブジェクトへの複数の参照が不可欠です。
-gbulmer

1

もちろん、次のようなシナリオになります:

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

コードを管理bしていて、かつて別の犬または別のクラスであったが、現在はによってサービスされている場合aです。

一般に、中期的には、a直接参照するすべてのコードを修正する必要がありますが、すぐには発生しない場合があります。


1

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

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


0

数独ソルバーを作成するときにこれを使用しました。行を処理するときにセルの番号を知っているとき、列を処理するときにそのセルの番号を含む列にも知ってほしい。そのため、列と行は両方とも重なり合うCellオブジェクトの配列です。受け入れられた答えが示したとおりです。


-2

Webアプリケーションでは、オブジェクトリレーショナルマッパーは遅延読み込みを使用して、同じデータベースオブジェクト(少なくとも同じスレッド内)へのすべての参照が同じものを指すようにすることができます。

たとえば、2つのテーブルがある場合:

犬:

  • id | owner_id | 名
  • 1 | 1 | バーク・ケント

所有者:

  • id | 名
  • 1 | 私
  • 2 | 君は

以下の呼び出しが行われた場合、ORMが実行できるいくつかのアプローチがあります。

dog = Dog.find(1)  // fetch1
owner = Owner.find(1) // fetch2
superdog = owner.dogs.first() // fetch3
superdog.name = "Superdog"
superdog.save! // write1
owner = dog.owner // fetch4
owner.name = "Mark"
owner.save! // write2
dog.owner = Owner.find(2)
dog.save! // write3

単純な戦略では、モデルと関連する参照へのすべての呼び出しが個別のオブジェクトを取得します。Dog.find()Owner.find()owner.dogs、およびdog.ownerデータベースになり、それらがメモリに保存された後、最初の頃にヒット。など:

  • データベースは少なくとも4回取得されます
  • dog.ownerはsuperdog.ownerとは異なります(個別に取得)
  • dog.nameはsuperdog.nameと同じではありません
  • dogとsuperdogは両方とも同じ行に書き込みを試み、互いの結果を上書きします。write3は、write1の名前の変更を取り消します。

参照がなければ、より多くのフェッチがあり、より多くのメモリを使用し、以前の更新を上書きする可能性があります。

ORMがdogsテーブルの行1へのすべての参照が同じものを指している必要があることを知っているとします。次に:

  • fetch.4は、Owner.find(1)に対応するメモリ内にオブジェクトがあるため、削除できます。fetch3は、所有者が所有している他の犬がいる可能性があるため、少なくともインデックススキャンが行われますが、行の取得はトリガーしません。
  • dogとsuperdogは同じオブジェクトを指します。
  • dog.nameとsuperdog.nameは同じオブジェクトを指します。
  • dog.ownerとsuperdog.ownerは同じオブジェクトを指します。
  • write3は、write1の変更を上書きしません。

つまり、参照を使用すると、データベース内の行である単一の真実(少なくともそのスレッド内)の原則を体系化するのに役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.