PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Java - Referenzenproblem


Monger
2005-09-26, 03:28:56
Moin,

momentan stehe ich wie der Ochs vorm Berg vor einem Anfängerproblem, aber ich komm einfach nicht auf die Lösung...

Vielleicht könnt ihr da ein bißchen Licht ins Dunkel bringen:

Ich mache sinngemäß folgenden Methodenaufruf


Dingsda bleh = null;

boolean isTrue = getThingy(bleh);


Hintergrund ist folgender: die Methode soll mir eigentlich ein bestimmtes Objekt suchen, und mir dieses Objekt liefern. Leider brauche ich noch einen booleeschen Rückgabewert. Ich wollte vermeiden, beides nochmal in einem eigenen Objekt zu kapseln, nur damit ich beides zurückgeben kann, also wollte ich ganz schlau sein...
Ich übergeb einfach mein "bleh", und weise innerhalb der Methode dieser Deklaration ein Objekt zu. Etwa so:


boolean getThingy(Dingsda bleh){
bleh = anderesBleh;
return true;
}


Sobald meine Methode abgearbeitet ist, verliert "bleh" aber wieder seine Referenz, und ist wieder null.


Wo ist mein Denkfehler? Die Objekte die ich haben will, sind übrigens definitiv noch da, und wurden nicht vom Garbage Collector aufgeräumt. Aber meine "bleh" Referenz scheint sich jedes mal aufzulösen...


Mir geht es übrigens wirklich vorallem ums Verständnis. Klar kann man's auch anders lösen, aber ich würde gerne wissen wo mein Denkfehler liegt.

mithrandir
2005-09-26, 07:55:09
Dere!

Vielleicht hilft dir das hier weiter:
http://userpage.fu-berlin.de/~ram/pub/pub_jf47hxhHHt/java_referenzvariablen_als_argument_de

bye, Peter

GloomY
2005-09-26, 07:59:45
Es ist dir hoffentlich klar, dass deine beiden "bleh"s zwei verschiedene "bleh"s sind, oder? Das erste "bleh" ist eine Variable, die in dem Kontext gilt, wo seine Deklaration steht (also irgendwo außerhalb der Methode "getThingy"). Und das zweite ist eine lokale Variable, die nur innerhalb der Methode getThingy exisitert.

Zweitens wird mit dem Aufruf von "getThingy" das "externe" "bleh" per Call-by-Value dem "internen" "bleh" zugewiesen. Call-by-Value heisst, dass eine Kopie des Inhalts gemacht wird, der aber sowieso NULL ist. (*) Du hast mit Call-by-Value also prinzipiell gar keine Chance, den Inhalt der externen Variablen "bleh" zu ändern.

Was du brauchst ist eine Referenz auf dein externes Objekt:
boolean getThingy(Dingsda &bleh) {
bleh = anderesBleh;
return true;
}So sollte es eigentlich funktionieren.



(*) An der Stelle ist mir nicht so ganz klar, was da genau passiert, wenn das Ding NULL ist.

Senior Sanchez
2005-09-26, 08:36:32
Es ist dir hoffentlich klar, dass deine beiden "bleh"s zwei verschiedene "bleh"s sind, oder? Das erste "bleh" ist eine Variable, die in dem Kontext gilt, wo seine Deklaration steht (also irgendwo außerhalb der Methode "getThingy"). Und das zweite ist eine lokale Variable, die nur innerhalb der Methode getThingy exisitert.

Zweitens wird mit dem Aufruf von "getThingy" das "externe" "bleh" per Call-by-Value dem "internen" "bleh" zugewiesen. Call-by-Value heisst, dass eine Kopie des Inhalts gemacht wird, der aber sowieso NULL ist. (*) Du hast mit Call-by-Value also prinzipiell gar keine Chance, den Inhalt der externen Variablen "bleh" zu ändern.

Was du brauchst ist eine Referenz auf dein externes Objekt:
boolean getThingy(Dingsda &bleh) {
bleh = anderesBleh;
return true;
}So sollte es eigentlich funktionieren.



(*) An der Stelle ist mir nicht so ganz klar, was da genau passiert, wenn das Ding NULL ist.


Das ist so nicht ganz richtig ;)
Standardgemäß wird in Java jedes Objekt per Call-by-Reference und die einfachen Datentypen per Call-by-Value übergeben. Den Adressoperator den du verwendest gibt es afaik in Java gar nicht.

HellHorse
2005-09-26, 09:06:51
Zurück zum Problem. Mögliche Lösungen:

Falls null kein gültiger Rückgabewerte von getThingy ist könnte man im Fall des Nichtvorhandenseins eines Elements einfach null zurückgeben, ansonsten das Element.
Falls null ein gültiger Rückgabewert ist kannst ev. eine spezielle Nullinstanz keieren. Ist etwas mühsam.
Einen Block übergeben, der mit dem Element als Argument ausgeführt wird, wenn es vorhanden ist. Sehr mühsam in Java.
Eine Exception werfen falls das Element nicht vorhanden ist. Nicht ideal, gehört aber noch zu den weniger hässlichen Sachen hier. :(
Zusätlich einen ValueHolder übergeben in den das Resultat gespeichert wird und einen boolean zurückgeben. Nicht besonders schön.

Das ist alles was mir so weit in den Sinn gekommen ist

Senior Sanchez
2005-09-26, 10:11:25
@HellHorse, nochmal zu der Call-by-Value/Call-by-Reference Sache

Gerade war ich schockiert, als ich inner Java Insel nachlas, dass es angeblich wirklich kein call-by-Reference gibt. Daraufhin habe ich folgendes Code-Snippet erstellt, was das Gegenteil beweist oder sehe ich das falsch?


import java.util.*;

public class Bla {
public static void main(String[] args) {
StringBuffer test = new StringBuffer();
test.append(1);
System.out.println(new Bla().test(test));
System.out.println(test.toString());
}

public boolean test(StringBuffer test) {
test.append(2);
return true;
}

}

Abe Ghiran
2005-09-26, 10:20:34
Was du brauchst ist eine Referenz auf dein externes Objekt:
boolean getThingy(Dingsda &bleh) {
bleh = anderesBleh;
return true;
}

Mal abgesehen davon, daß es keine expliziten Referenzen in java gibt (alle Objekte sind eh Referenzen), würde die Zuweisung wohl auch nicht funktionieren, denn Referenzen (in c++) sind ja immer konstant.
Also müsste man eher einen Pointer auf einen Pointer übergeben.
boolean getThingy(Dingsda** bleh) {
*bleh = anderesBleh; // wenn anderesBleh ein pointer, sonst &anderesBleh
return true;
}
Pointer auf Pointer gibt es in java natürlich auch nicht, da käme dann der von HellHorse erwähnte ValueHolder als Hilfsobjekt ins Spiel.

Das ist so nicht ganz richtig ;)
Standardgemäß wird in Java jedes Objekt per Call-by-Reference und die einfachen Datentypen per Call-by-Value übergeben. Den Adressoperator den du verwendest gibt es afaik in Java gar nicht.

Richtig, wobei man es noch anders umschreiben kann: In java wird grundsätzlich alles by value übergeben, auch Objekte. Da man jedoch in java Objekte nur in Form von Referenzen in die Finger bekommt, läuft es dann doch wieder auf call by reference hinaus. Anders als in c++, wo ich mein dickes fettes Objekt auch auf den stack legen und beim Methodenaufruf kopieren lassen kann.

Von HellHorse Methoden würde ich klar die erste bevorzugen, wobei man dafür ja noch ne schöne symbolische Konstante einführen kann
public static final Dingsda VALUE_NOT_FOUND = null;

Dann kann man das später auch noch ganz leicht in die Variante mit dem expliziten Nullelement ändern.

Grüße, Jan

Xmas
2005-09-26, 10:26:40
http://www.yoda.arachsys.com/java/passing.html

Man kann bei Java auch von Call-by-reference-value reden. Leider gibt es in Java kein "out" wie in C#.

Senior Sanchez
2005-09-26, 10:44:15
http://www.yoda.arachsys.com/java/passing.html

Man kann bei Java auch von Call-by-reference-value reden. Leider gibt es in Java kein "out" wie in C#.

Ist das nen Ausgabeparameter? Sprich nen Container für Änderungen an den Parametern, die da übergeben werden?

Ich kenns nur von Delphi und da hats net funktioniert :biggrin:

Monger
2005-09-26, 12:50:58
Huiuiui, was hab ich denn hier angerichtet?

Also, vielleicht erstmal was definitiv funktioniert:

// ...
Dingsda bleh = new Dingsda();
// ...


boolean getThingy(Dingsda bleh){
bleh.doSth();
}


Sprich: Wenn ich ein Objekt an eine Methode übergebe und in dieser Methode ändere, ist das Objekt ÜBERALL geändert.

Aber mir schwant gerade mein Denkfehler...
Die zwei Blehs sind ja zwei völlig verschiedene Namen. Und wenn ich dem einen Namen eine andere Referenz zuweise, gilt das noch lange nicht für die zweite...

@HellHorse: Was ist denn eine Null Instanz?

Monger
2005-09-26, 12:55:01
Von HellHorse Methoden würde ich klar die erste bevorzugen, wobei man dafür ja noch ne schöne symbolische Konstante einführen kann
public static final Dingsda VALUE_NOT_FOUND = null;


Seit Java 5.0 ist dieses Pattern eigentlich böse! ;)
Eigentlich soll man inzwischen stattdessen Enumerationen für Konstanten verwenden. Aber wirklich verbreitet sind die wohl noch nicht.

Senior Sanchez
2005-09-26, 12:59:09
Huiuiui, was hab ich denn hier angerichtet?

Also, vielleicht erstmal was definitiv funktioniert:

// ...
Dingsda bleh = new Dingsda();
// ...


boolean getThingy(Dingsda bleh){
bleh.doSth();
}


Sprich: Wenn ich ein Objekt an eine Methode übergebe und in dieser Methode ändere, ist das Objekt ÜBERALL geändert.

Aber mir schwant gerade mein Denkfehler...
Die zwei Blehs sind ja zwei völlig verschiedene Namen. Und wenn ich dem einen Namen eine andere Referenz zuweise, gilt das noch lange nicht für die zweite...

@HellHorse: Was ist denn eine Null Instanz?


Das Problem, warum das bei dir nicht funktioniert ist, dass du eine Null-Referenz übergibst. Deine Variable vorm Aufruf verweist auf kein existierendes Objekt, sondern nur auf null. Und null wird somit auch der Methode übergeben ohne anscheinend einen Verweis auf den Speicherort der Variable zu übergeben. Initialisierst du jetzt in der Methode das Argument, so erzeugst du quasi eine neue Referenz ohne einen Verweis auf die Variable der aufrufenden Methode, was zur Folge hat, dass sich die ursprüngliche Variablen eben nicht ändert.

Schau dir mal mein Beispiel oben an und jetzt dieses dazu:


import java.util.*;

public class Bla {
private StringBuffer foo = new StringBuffer();

public static void main(String[] args) {
StringBuffer test = null;
System.out.println(new Bla().test(test));
System.out.println(test.toString());
}

public boolean test(StringBuffer test) {
test = foo;
test.append("2");
return true;
}

}


Das wirft ne schöne Nullpointer-Exception, weil eben auf einer Null-Referenz versucht wird, toString() aufzurufen.

Senior Sanchez
2005-09-26, 13:00:19
Seit Java 5.0 ist dieses Pattern eigentlich böse! ;)
Eigentlich soll man inzwischen stattdessen Enumerationen für Konstanten verwenden. Aber wirklich verbreitet sind die wohl noch nicht.

Enums stellen aber im Grunde nur vereinfachte Klassen dar, deren Elemente über Ganzzahlen identifiziert werden und über einen Namen verfügen. Enums mit Elementen eines anderen Typs zu bauen ist dagegen meines Wissens nach nicht möglich ;)

Xmas
2005-09-26, 13:02:02
Ist das nen Ausgabeparameter? Sprich nen Container für Änderungen an den Parametern, die da übergeben werden?

Ich kenns nur von Delphi und da hats net funktioniert :biggrin:
Ich habe damit auch in Delphi keine Probleme gehabt.


Sprich: Wenn ich ein Objekt an eine Methode übergebe und in dieser Methode ändere, ist das Objekt ÜBERALL geändert.

Aber mir schwant gerade mein Denkfehler...
Die zwei Blehs sind ja zwei völlig verschiedene Namen. Und wenn ich dem einen Namen eine andere Referenz zuweise, gilt das noch lange nicht für die zweite...

@HellHorse: Was ist denn eine Null Instanz?
Java übergibt keine Objekte, sondern Referenzen auf Objekte by-value. Deswegen kannst du zwar das Objekt manipulieren, aber nicht die Referenzvariable, die als Parameter verwendet wurde.

HellHorse meint eine speziell geformte Instanz des Rückgabetyps, die null repräsentiert.

Senior Sanchez
2005-09-26, 13:03:50
Ich habe damit auch in Delphi keine Probleme gehabt.


Zumindest unter Delphi 5 haben die Teile net funktioniert.

Monger
2005-09-26, 13:14:07
Enums stellen aber im Grunde nur vereinfachte Klassen dar, deren Elemente über Ganzzahlen identifiziert werden und über einen Namen verfügen. Enums mit Elementen eines anderen Typs zu bauen ist dagegen meines Wissens nach nicht möglich ;)

Hö?

Bin jetzt nicht ganz sicher was du meinst.

Aber ein Enum kann z.B. so aussehen:


enum Konstanten{

valueNotFound{
public Object get(){
return null;
}
};

public abstract Object get();

}

Der Gebrauch wäre entsprechend:


if(doSth() == Konstanten.valueNotFound.get()){
// ...
}


Das sieht erstmal relativ hässlich aus. Aber wenn man z.B. den Enumerationstyp als statischen Import reinnimmt, kann man sich den Namen sparen:


if(doSth() == valueNotFound.get()){}


Du kannst einer Enumeration völlig beliebiges Verhalten aufprägen!

Senior Sanchez
2005-09-26, 13:21:05
Hö?

Bin jetzt nicht ganz sicher was du meinst.

Aber ein Enum kann z.B. so aussehen:


enum Konstanten{

valueNotFound{
public Object get(){
return null;
}
};

public abstract Object get();

}

Der Gebrauch wäre entsprechend:


if(doSth() == Konstanten.valueNotFound.get()){
// ...
}


Das sieht erstmal relativ hässlich aus. Aber wenn man z.B. den Enumerationstyp als statischen Import reinnimmt, kann man sich den Namen sparen:


if(doSth() == valueNotFound.get()){}


Du kannst einer Enumeration völlig beliebiges Verhalten aufprägen!


ahh, Fehler meinerseits. Habe ich noch net gewusst, aber merci für die Erklärung :)

HellHorse
2005-09-26, 16:01:27
Seit Java 5.0 ist dieses Pattern eigentlich böse! ;)
Eigentlich soll man inzwischen stattdessen Enumerationen für Konstanten verwenden.
Uhmmm nee. Enumerationen lösen ein anderes Problem. Und zwar repräsentieren sie alle möglichen gültigen Werte eines Typs, z.B. alle Schwierigkeitsstufen.
Hier haben wir aber ein anderes Problem. Und zwar haben wir eine Instanz eines Typs der eigentlich null repräsentiert aber nicht null ist. Sentinel heisst so ein Ding glaube ich auch.
Möchtest du das wirklich als Enumeration modelieren müsstes du dort auch alle möglichen Instanzen von Dingsda reinpacken.

Abnaxos
2005-09-26, 16:47:56
Um es nochmal ganz klar zu machen: In Java werden Werte *immer* by-value übergeben. Dieser "Value" kann verschiedene Typen haben, einer der primitiven Typen (int, boolean, etc) oder es ist eine Referenz auf das Objekt. Daher kommt auch die Verwirrung vieler Anfänger, was Call-by-Reference und Call-by-Value angeht, das mit "Objekte werden by-reference und primitive Typen by-value übergeben" zu umschreiben ist aber falsch! Die Objekt-Referenz wird by-value übergeben, genau wie ein int auch.

Zu HellHorses Problem: In diesem Fall wäre es natürlich schön, wenn man es wie mit Python machen könnte:

def myFunction():
return 1, 2

a, b = myFunction()

Die Syntax versteckt hier ein wenig, was tatsächlich passiert. Tatsächlich wird hier nämlich ein neues Tupel (1, 2) erzeugt und danach wieder auseinandergenommen und auf a und b verteilt. Anders geschrieben:

def myFunction():
return tuple(1, 2)

t = myFunction()
a=t[0]
b=t[1]
del t

Man sieht, auch in Sprachen, die mehr syntaktischen Zucker bieten, werden unter der Motorhaube temporäre Objekte erzeugt, die Syntax versteckt diese Tatsache einfach ein wenig besser. Es gibt also keinen Grund, es in Java nicht zu tun (insbesondere, da die VM von Java extrem optimiert ist, eben gerade auch auf temporäre Objekte). Das heisst nicht, dass man jetzt mit temporären Objekten nur so um sich schmeissen soll, aber wo sie benötigt werden, werden sie nun mal benötigt. :biggrin:

Lieber mal ein temporäres Objekt zu viel als diese grausame Stolperfalle namens Call-by-Reference zu verwenden, zumal es Letzeres in Java gar nicht gibt. :biggrin:

Monger
2005-09-26, 18:49:40
Uhmmm nee. Enumerationen lösen ein anderes Problem. Und zwar repräsentieren sie alle möglichen gültigen Werte eines Typs, z.B. alle Schwierigkeitsstufen.

Ich wollte ja eigentlich auch nur darauf hinaus, dass das public static int Pattern eigentlich nicht so schick ist, und man lieber Enumerationen verwenden soll, weil man da wenigstens auch bei Rückgabewerten einen eindeutigen Parameter kriegt, und nicht irgendeine Zahl.


Hier haben wir aber ein anderes Problem. Und zwar haben wir eine Instanz eines Typs der eigentlich null repräsentiert aber nicht null ist. Sentinel heisst so ein Ding glaube ich auch.
Möchtest du das wirklich als Enumeration modelieren müsstes du dort auch alle möglichen Instanzen von Dingsda reinpacken.
Thx. :)
Ich werd nicht ernsthaft eine Enumeration verwenden. Lohnt sich in dem Fall einfach nicht. Ich habs inzwischen übrigens so gelöst, dass ich beide Parameter in einem kleinen Objekt kapsele, und dieses Objekt zurückgebe.

Xmas
2005-09-26, 21:00:54
Lieber mal ein temporäres Objekt zu viel als diese grausame Stolperfalle namens Call-by-Reference zu verwenden, zumal es Letzeres in Java gar nicht gibt. :biggrin:
Inwiefern ist Call-by-Reference eine grausame Stolperfalle?

Senior Sanchez
2005-09-26, 21:08:02
Inwiefern ist Call-by-Reference eine grausame Stolperfalle?

Ich hatte nur mal neulich nen Fall und zwar ist ne Stolperfalle, dass du etwas am übergebenen Objekt veränderst ohne darauf zu achten, dass das auch das Objekt in der aufrufenden Methode betrifft. Hatte ich neulich bei einer rekursiven Funktion, sehr ärgerlich das ganze ;) Da hilft es einfach ein temporäres Objekt zu verwenden.

HellHorse
2005-09-26, 21:49:53
Zu HellHorses Problem: In diesem Fall wäre es natürlich schön, wenn man es wie mit Python machen könnte:
Nein. Python macht das z.B bei Dictionaries nicht sondern wirft einen KeyError. Aus guten Grund. Ideal wäre natürlich das Verhalten von Smalltalk. D.h. wie Python bloss dass man für den Fall des Nichtvorhandenseins zustätzlich einen Block übergeben kann, der statt der Exception ausgeführt wird (eigentlich ist die Exception der default Block).

Edit:
Wenn es dich glücklich macht, kannst du in Java problemlos eine Tupelklasse machen ohne die Syntax oder der Compiler kompilzierter zu machen. Automatisches entpacken fällt aber natürlich weg.

Ich wollte ja eigentlich auch nur darauf hinaus, dass das public static int Pattern eigentlich nicht so schick ist, und man lieber Enumerationen verwenden soll, weil man da wenigstens auch bei Rückgabewerten einen eindeutigen Parameter kriegt, und nicht irgendeine Zahl.
Genau das wäre ja bei Nullinstanz/Sentinel auch der Fall.

Xmas
2005-09-26, 21:54:27
Ich hatte nur mal neulich nen Fall und zwar ist ne Stolperfalle, dass du etwas am übergebenen Objekt veränderst ohne darauf zu achten, dass das auch das Objekt in der aufrufenden Methode betrifft. Hatte ich neulich bei einer rekursiven Funktion, sehr ärgerlich das ganze ;) Da hilft es einfach ein temporäres Objekt zu verwenden.
Das ist bei "Call-by-Reference-Value" wie es Java tut ja auch so. In dem Fall hilft es, eine Objektkopie anzulegen.