it-swarm.com.de

Wann möchten Sie zwei Verweise auf dasselbe Objekt?

In Java speziell, aber wahrscheinlich auch in anderen Sprachen: Wann wäre es nützlich, zwei Verweise auf dasselbe Objekt zu haben?

Beispiel:

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

Gibt es eine Situation, in der dies nützlich wäre? Warum ist dies eine bevorzugte Lösung für die Verwendung von a, wenn Sie mit dem durch a dargestellten Objekt interagieren möchten?

20
Bassinator

Ein Beispiel ist, wenn Sie dasselbe Objekt in zwei separate Listen haben möchten:

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

Eine andere Verwendung ist, wenn Sie dasselbe Objekt spielen mehrere Rollen:

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

Das ist eigentlich eine überraschend tiefe Frage! Erfahrungen aus modernem C++ (und Sprachen aus modernem C++ wie Rust) legen nahe, dass Sie das sehr oft nicht wollen! Für die meisten Daten möchten Sie eine einzelne oder eindeutige ("besitzen") ) Referenz. Diese Idee ist auch der Hauptgrund für lineare Typsysteme .

Selbst dann möchten Sie in der Regel einige kurzlebige "geliehene" Referenzen, die verwendet werden, um kurz auf den Speicher zuzugreifen, aber nicht für einen signifikanten Bruchteil der Zeit, in der die Daten vorhanden sind. Am häufigsten, wenn Sie ein Objekt als Argument an eine andere Funktion übergeben (Parameter sind auch Variablen!):

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

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

Ein weniger häufiger Fall, wenn Sie abhängig von einer Bedingung eines von zwei Objekten verwenden und im Wesentlichen dasselbe tun, unabhängig davon, für welches Sie sich entscheiden:

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

Wir kehren zu häufigeren Verwendungen zurück, lassen jedoch lokale Variablen hinter uns und wenden uns den Feldern zu: Beispielsweise haben Widgets in GUI-Frameworks häufig übergeordnete Widgets, sodass auf Ihren großen Frame mit zehn Schaltflächen mindestens zehn Verweise verweisen (plus einige weitere) von seinem Elternteil und vielleicht von Ereignis-Listenern und so weiter). Bei jeder Art von Objektdiagramm und einigen Arten von Objektbäumen (solche mit übergeordneten/Geschwisterreferenzen) beziehen sich mehrere Objekte auf dasselbe Objekt. Und praktisch jeder Datensatz ist tatsächlich eine Grafik ;-)

16
user7043

Temporäre Variablen: Betrachten Sie den folgenden Pseudocode.

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

Die Variablen max und candidate können auf dasselbe Objekt verweisen, aber die Variablenzuweisung ändert sich nach unterschiedlichen Regeln und zu unterschiedlichen Zeiten.

6
user22815

Diese Methode eignet sich hervorragend, wenn Sie mehrere Objekte haben, die alle auf ein anderes Objekt zurückrufen, das nicht kontextuell verwendet werden kann.

Wenn Sie beispielsweise eine Oberfläche mit Registerkarten haben, haben Sie möglicherweise Tab1, Tab2 und Tab3. Möglicherweise möchten Sie auch eine gemeinsame Variable verwenden können, unabhängig davon, auf welcher Registerkarte sich der Benutzer befindet, um Ihren Code zu vereinfachen und zu vermeiden, dass Sie im laufenden Betrieb immer wieder herausfinden müssen, auf welcher Registerkarte sich Ihr Benutzer befindet.

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

Anschließend können Sie in jeder der nummerierten Registerkarten auf Click CurrentTab so ändern, dass auf diese Registerkarte verwiesen wird.

CurrentTab = Tab3;

Jetzt können Sie in Ihrem Code ungestraft "CurrentTab" aufrufen, ohne wissen zu müssen, auf welchem ​​Tab Sie sich tatsächlich befinden. Sie können auch die Eigenschaften von CurrentTab aktualisieren. Diese werden automatisch auf die Registerkarte weitergeleitet, auf die verwiesen wird.

3
JMD

Es gibt viele Szenarien, in denen b ein Verweis auf ein unbekanntes "a" sein muss, um nützlich zu sein . Bestimmtes:

  • Immer wenn Sie nicht wissen, worauf b zur Kompilierungszeit zeigt.
  • Jedes Mal, wenn Sie eine Sammlung durchlaufen müssen, unabhängig davon, ob sie zur Kompilierungszeit bekannt ist oder nicht
  • Jederzeit haben Sie begrenzten Umfang

Zum Beispiel:

Parameter

public void DoSomething(Thing &t) {
}

t ist eine Referenz auf eine Variable aus einem externen Bereich.

Rückgabewerte und andere bedingte Werte

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

biggerThing und z beziehen sich jeweils entweder auf a oder b. Wir wissen nicht, welche zur Kompilierungszeit.

Lambdas und ihre Rückgabewerte

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

x ist ein Parameter (Beispiel 1 oben) und t ist ein Rückgabewert (Beispiel 2 oben)

Sammlungen

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

index["some name"] ist zu einem großen Teil ein anspruchsvolleres b. Es ist ein Alias ​​für ein Objekt, das erstellt und in die Sammlung eingefügt wurde.

Schleifen

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

t ist eine Referenz auf ein Element, das von einem Iterator zurückgegeben wurde (Beispiel 2) (vorheriges Beispiel).

3
svidgen

Um die anderen Antworten zu ergänzen, möchten Sie möglicherweise auch eine Datenstruktur ab derselben Stelle unterschiedlich durchlaufen. Wenn Sie beispielsweise eine BinaryTree a = new BinaryTree(...); BinaryTree b = a hätten, könnten Sie den Pfad ganz links des Baums mit a und den Pfad ganz rechts mit b durchlaufen, indem Sie Folgendes verwenden:

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

Es ist schon eine Weile her, dass ich Java geschrieben habe, sodass der Code möglicherweise nicht korrekt oder sinnvoll ist. Nehmen Sie es eher als Pseudocode.

3
Mike

Es ist ein entscheidender Punkt, aber IMHO ist es wert, verstanden zu werden.

Alle OO Sprachen erstellen immer Kopien von Referenzen und kopieren niemals ein Objekt 'unsichtbar'. Es wäre viel schwieriger, Programme zu schreiben, wenn OO Sprachen funktionierten auf andere Weise. Beispielsweise konnten Funktionen und Methoden ein Objekt niemals aktualisieren. Java und die meisten OO Sprachen wären ohne erhebliche zusätzliche Komplexität fast unmöglich zu verwenden.

Ein Objekt in einem Programm soll eine Bedeutung haben. Zum Beispiel repräsentiert es etwas Spezifisches in der realen physischen Welt. Es ist normalerweise sinnvoll, viele Verweise auf dasselbe zu haben. Beispielsweise kann meine Privatadresse vielen Personen und Organisationen zugewiesen werden, und diese Adresse bezieht sich immer auf denselben physischen Standort. Der erste Punkt ist also, dass Objekte oft etwas darstellen, das spezifisch, real oder konkret ist. Daher ist es äußerst nützlich, viele Verweise auf dasselbe zu haben. Andernfalls wäre es schwieriger, Programme zu schreiben.

Jedes Mal, wenn Sie a als Argument/Parameter an eine andere Funktion übergeben, z. Berufung
foo(Dog aDoggy);
oder wenden Sie eine Methode auf a an. Der zugrunde liegende Programmcode erstellt eine Kopie der Referenz, um eine zweite Referenz auf dasselbe Objekt zu erstellen.

Befindet sich Code mit einer kopierten Referenz in einem anderen Thread, können beide gleichzeitig verwendet werden, um auf dasselbe Objekt zuzugreifen.

In den meisten nützlichen Programmen gibt es also mehrere Verweise auf dasselbe Objekt, da dies die Semantik der meisten OO Programmiersprachen) ist.

Wenn wir darüber nachdenken, weil das Übergeben als Referenz der nur Mechanismus ist, der in vielen OO Sprachen (C++ unterstützt beide)) verfügbar ist, können wir erwarten, dass dies der ist 'richtig' Standard Verhalten.

IMHO ist die Verwendung von Referenzen aus mehreren Gründen richtig Standard:

  1. Es garantiert, dass der Wert eines Objekts, das an zwei verschiedenen Orten verwendet wird, gleich ist. Stellen Sie sich vor, Sie fügen ein Objekt in zwei verschiedene Datenstrukturen (Arrays, Listen usw.) ein und führen einige Operationen an einem Objekt aus, das es ändert. Das könnte ein Albtraum sein. Noch wichtiger ist, dass is dasselbe Objekt in beiden Datenstrukturen ist oder das Programm einen Fehler aufweist.
  2. Sie können Code problemlos in mehrere Funktionen umgestalten oder den Code aus mehreren Funktionen zu einer zusammenführen, und die Semantik ändert sich nicht. Wenn die Sprache keine Referenzsemantik bereitstellen würde, wäre es noch komplexer, Code zu ändern.

Es gibt auch ein Effizienzargument; Das Erstellen von Kopien ganzer Objekte ist weniger effizient als das Kopieren einer Referenz. Ich denke jedoch, dass das den Punkt verfehlt. Mehrere Verweise auf dasselbe Objekt sind sinnvoller und einfacher zu verwenden, da sie der Semantik der realen physischen Welt entsprechen.

Also, IMHO, es normalerweise macht Sinn, mehrere Verweise auf dasselbe Objekt zu haben. In den ungewöhnlichen Fällen, in denen dies im Kontext eines Algorithmus keinen Sinn ergibt, bieten die meisten Sprachen die Möglichkeit, einen "Klon" oder eine tiefe Kopie zu erstellen. Dies ist jedoch nicht die Standardeinstellung.

Ich denke, Leute, die argumentieren, dass dies nicht die Standardeinstellung sein sollte, verwenden eine Sprache, die keine automatische Speicherbereinigung bietet. Zum Beispiel altmodisches C++. Das Problem dort ist, dass sie einen Weg finden müssen, um "tote" Objekte zu sammeln und keine Objekte zurückzufordern, die möglicherweise noch benötigt werden. Wenn Sie mehrere Verweise auf dasselbe Objekt haben, ist dies schwierig.

Ich denke, wenn C++ eine ausreichend kostengünstige Garbage Collection hatte, so dass alle referenzierten Objekte Garbage Collection sind, dann verschwindet ein Großteil der Einwände. Es wird immer noch Fälle geben, in denen die Referenzsemantik nicht ist, was benötigt wird. Nach meiner Erfahrung sind die Personen, die diese Situationen identifizieren können, normalerweise auch in der Lage, die geeignete Semantik zu wählen.

Ich glaube, es gibt Hinweise darauf, dass ein großer Teil des Codes in einem C++ - Programm dazu dient, die Speicherbereinigung zu handhaben oder zu verringern. Das Schreiben und Verwalten dieser Art von "infrastrukturellem" Code erhöht jedoch die Kosten. Es soll die Sprache benutzerfreundlicher oder robuster machen. So ist beispielsweise die Go-Sprache so konzipiert, dass einige der Schwächen von C++ behoben werden, und sie hat keine andere Wahl als die Speicherbereinigung.

Dies ist im Kontext von Java natürlich irrelevant. Es wurde auch so konzipiert, dass es einfach zu bedienen ist, ebenso wie die Speicherbereinigung. Daher ist das Vorhandensein mehrerer Referenzen die Standardsemantik und relativ sicher in dem Sinne, dass Objekte nicht zurückgefordert werden, solange auf sie verwiesen wird. Natürlich können sie von einer Datenstruktur festgehalten werden, da das Programm nicht richtig aufräumt, wenn es wirklich mit einem Objekt fertig ist.

Wenn Sie also zu Ihrer Frage zurückkehren (mit ein wenig Verallgemeinerung), wann möchten Sie mehr als einen Verweis auf dasselbe Objekt? So ziemlich in jeder Situation, die ich mir vorstellen kann. Sie sind die Standardsemantik der meisten Sprachen, die Parameter übergeben. Ich schlage vor, das liegt daran, dass die Standardsemantik des Umgangs mit Objekten, die in der realen Welt existiert, so ziemlich als Referenz dienen muss (weil die tatsächlichen Objekte da draußen sind).

Jede andere Semantik wäre schwieriger zu handhaben.

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

Ich schlage vor, dass der "Rover" in dl derjenige sein sollte, der von setOwner bewirkt wird, da Programme sonst schwer zu schreiben, zu verstehen, zu debuggen oder zu modifizieren sind. Ich denke, die meisten Programmierer wären sonst verwirrt oder bestürzt.

später wird der Hund verkauft:

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

Diese Art der Verarbeitung ist üblich und normal. Referenzsemantik ist daher die Standardeinstellung, da sie normalerweise am sinnvollsten ist.

Zusammenfassung: Es ist immer sinnvoll, mehrere Verweise auf dasselbe Objekt zu haben.

2
gbulmer

Natürlich ein anderes Szenario, in dem Sie möglicherweise enden:

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

wenn Sie Code pflegen und b früher ein anderer Hund oder eine andere Klasse war, wird er jetzt von a bedient.

Im Allgemeinen sollten Sie mittelfristig den gesamten Code überarbeiten, um direkt auf a zu verweisen. Dies kann jedoch nicht sofort geschehen.

1
Mark Hurd

Sie möchten dies immer dann, wenn Ihr Programm die Möglichkeit hat, eine Entität an mehr als einer Stelle in den Speicher zu ziehen, möglicherweise weil verschiedene Komponenten sie verwenden.

Identity Maps lieferte einen endgültigen lokalen Speicher von Entitäten, sodass wir vermeiden können, zwei oder mehr separate Darstellungen zu haben. Wenn wir dasselbe Objekt zweimal darstellen, läuft unser Client Gefahr, ein Parallelitätsproblem zu verursachen, wenn eine Referenz des Objekts seine Statusänderungen beibehält, bevor die andere Instanz dies tut. Die Idee ist, dass wir sicherstellen möchten, dass unser Kunde immer den endgültigen Verweis auf unsere Entität/unser Objekt hat.

1
Mario T. Lanza

Ich habe dies beim Schreiben eines Sudoku-Lösers verwendet. Wenn ich die Nummer einer Zelle bei der Verarbeitung von Zeilen kenne, möchte ich, dass die enthaltende Spalte auch die Nummer dieser Zelle bei der Verarbeitung von Spalten kennt. Daher sind sowohl Spalten als auch Zeilen Arrays von Zellenobjekten, die sich überlappen. Genau wie die akzeptierte Antwort zeigte.

0
winkbrace