Kann mir jemand erklären, was die Argumentation von „Wert“ und nicht von „Referenz“ in Java hinter vorbei ist?

StackOverflow https://stackoverflow.com/questions/514478

Frage

ich auf Java ziemlich neu bin (geschrieben andere Sachen für viele Jahre) und es sei denn, ich bin etwas fehlt (und ich bin glücklich, falsch zu sein hier) die folgende ist ein fataler Fehler ...

String foo = new String();
thisDoesntWork(foo);
System.out.println(foo);//this prints nothing

public static void thisDoesntWork(String foo){
   foo = "howdy";
}

Nun, ich bin sehr wohl bewusst die (ziemlich schlecht formulierte) Konzept, dass alles in Java wird von „Wert“ übergeben und nicht die „Referenz“, aber String ist ein Objekt und hat alle Arten von Glocken und Pfeifen, so, würde man erwarten, dass im Gegensatz zu einem int eines Benutzer auf der Sache zu betreiben wäre in der Lage, die in das Verfahren übergeben wird (und nicht mit dem von der überlasteten = Sollwert geklebt werden).

Kann mir jemand erklären, was der Grund für diese Design-Wahl war? Wie gesagt, suche ich nicht hier zu sein, und vielleicht etwas offensichtlich fehle ich?

War es hilfreich?

Lösung

Diese Tirade es besser erklärt, als ich jemals auch nur versuchen könnte:

  

In Java Primitive wird von Wert übergeben. Allerdings Objekte sind nicht   Referenz übergeben. Eine korrekte Aussage würde Objektreferenzen sein   geben wird von Wert.

Andere Tipps

Wenn Sie "foo" passieren, Sie geben das Hinweis auf "foo" als Wert ThisDoesntWork (). Das bedeutet, dass, wenn Sie die Zuordnung zu „foo“ innerhalb Ihrer Methode zu tun, Sie sind lediglich eine lokale Variable Einstellung (foo) 's Referenz ein Verweis auf Ihre neue Zeichenfolge zu sein.

Eine andere Sache im Auge zu behalten, wenn darüber nachzudenken, wie Strings in Java verhalten ist, dass Strings unveränderlich sind. Es funktioniert auf die gleiche Art und Weise in C # und für einige gute Gründe:

  • Sicherheit : Niemand kann Daten in Ihr String verklemmen und einen Pufferüberlauf-Fehler verursachen, wenn niemand kann es ändern
  • Geschwindigkeit : Wenn Sie sicher sein können, dass Ihre Strings unveränderlich sind, wissen Sie seine Größe ist immer gleich und man nicht immer eine Bewegung der Datenstruktur im Speicher zu tun haben, wenn Sie manipulieren. Sie (die Sprache Designer) auch müssen sie keine Sorgen über den String als langsame verknüpfte Liste der Umsetzung, auch nicht. Diese schneidet in beiden Richtungen, though. Anfügen Strings nur den Operator + verwenden, kann teuren Speicher-weise sein, und Sie müssen ein Stringbuilder-Objekt verwenden, um dies zu tun, in einer leistungsfähigen, speichereffiziente Art und Weise.

Jetzt auf Ihre größere Frage. Warum werden die Objekte auf diese Weise weitergegeben? Nun, wenn Java Ihre String übergeben, was Sie traditionell würde „nach Wert“ nennen, müsste sie eigentlich die gesamte Zeichenfolge kopieren, bevor es an die Funktion übergeben. Das ist ziemlich langsam. Wenn es die Zeichenfolge als Verweis übergeben und lassen Sie es ändern (wie C der Fall ist), dann würden Sie die Probleme habe ich gerade aufgeführt.

Da meine ursprüngliche Antwort war: „Warum es passiert ist“ und nicht „Warum die Sprache entwickelt wurde, so geschah es:“ Ich werde diese einen anderen gehen geben.

Um die Dinge zu vereinfachen, ich loswerden der Methodenaufruf erhalten werden und zeigen, was in einer anderen Art und Weise geschieht.

String a = "hello";
String b = a;
String b = "howdy"

System.out.print(a) //prints hello

Um die letzte Anweisung zu bekommen "Hallo" zu drucken, b hat das gleiche "Loch" im Speicher zu zeigen, dass a auf (ein Zeiger). Dies ist, was Sie wollen, wenn Sie als Verweis übergeben werden sollen. Es gibt ein paar Gründe, Java entschieden, nicht in diese Richtung zu gehen:

  • Pointers verwechselst Die Designer von Java versucht, einige der eher verwirrend Dinge über andere Sprachen zu entfernen. Zeiger sind eine der unverstandenen und unsachgemäß benutzt Konstrukte C / C ++ zusammen mit Bedienungs Überlastung.

  • Pointers sind Sicherheitsrisiken Pointers viele Sicherheitsprobleme verursachen, wenn sie missbraucht werden. Ein bösartiges Programm ordnet etwas in diesem Teil des Speichers, dann, was Sie dachten, war das Objekt tatsächlich jemand ist anderes. (Java wurde bereits Loswerden des größten Sicherheitsproblems, Pufferüberläufe mit kariertem Arrays)

  • Abstraction Leakage Wenn Sie beginnen mit zu tun "Was in Erinnerung ist und wo" genau, Ihre Abstraktion wird weniger von einer Abstraktion. Während Abstraktion Leckage es an Sicherheit grenzender Wahrscheinlichkeit in eine Sprache kriecht, wollten die Designer nicht in direkt backen.

  • Objekte sind alle, die Sie interessieren In Java, alles ist ein Objekt, nicht der Raum ein Objekt einnimmt. Hinzufügen Zeiger würde den Raum macht ein Objekt importantant einnimmt, obwohl .......

Sie könnten emulieren, was Sie wollen durch ein „Loch“ Objekt erstellen. Sie könnten sogar Generika verwenden, um es sicher geben zu machen. Zum Beispiel:

public class Hole<T> {
   private T objectInHole;

   public void putInHole(T object) {
      this.objectInHole = object;
   }
   public T getOutOfHole() {
      return objectInHole;
   }

   public String toString() {
      return objectInHole.toString();
   }
   .....equals, hashCode, etc.
}


Hole<String> foo = new Hole<String)();
foo.putInHole(new String());
System.out.println(foo); //this prints nothing
thisWorks(foo);
System.out.println(foo);//this prints howdy

public static void thisWorks(Hole<String> foo){
   foo.putInHole("howdy");
}

Ihre Frage, fragte nicht wirklich von Wert mit Vergehen zu tun hat, als Verweis oder die Tatsache, dass Strings unveränderlich ist (wie andere gesagt haben).

Innerhalb der Methode, die Sie tatsächlich eine lokale Variable erstellen (Ich werde, dass man „localFoo“ nennen), die als Originalgröße auf den gleichen Referenzpunkte ( „originalFoo“).

Wenn Sie „grüß dich“ zu localFoo zuweisen, die Sie nicht ändern, wo originalFoo zeigt.

Wenn Sie tut so etwas wie:

String a = "";
String b = a;
String b = "howdy"?

Möchten Sie erwarten:

System.out.print(a)

auszudrucken „grüß dich“? Es druckt "".

Sie können nicht, was originalFoo Punkte ändern, indem Sie ändern, was localFoo Punkte. Sie können das Objekt, das sowohl Punkt ändern (wenn es nicht unveränderlich). Zum Beispiel:

List foo = new ArrayList();
System.out.println(foo.size());//this prints 0

thisDoesntWork(foo);
System.out.println(foo.size());//this prints 1

public static void thisDoesntWork(List foo){   
    foo.add(new Object);
}

In Java alle übergebenen Variablen tatsächlich von wert- sogar Objekte herumgereicht. Alle Variablen auf eine Methode übergeben sind tatsächlich Kopien des ursprünglichen Wertes. Im Falle der Zeichenfolge Beispiel der ursprüngliche Zeiger (es ist eigentlich eine Referenz - aber Verwirrung krank Verwendung ein anderes Wort zu vermeiden) in eine neue Variable kopiert, die die Parameter der Methode wird

.

Es wäre ein Schmerz, wenn alles unter Bezugnahme ist. Man müßte Privatkopien ganz über den Platz machen, die auf jeden Fall ein echten Schmerzen würden. Jeder weiß, dass für Werttypen mit Unveränderlichkeit etc macht Ihre Programme unendlich einfacher und skalierbarer.

Einige Vorteile sind: - Keine Notwendigkeit, defensive Kopien zu machen. - Thread -. Keine Notwendigkeit, sich Sorgen zu machen über Sperren nur für den Fall jemand anderes will, das Objekt ändern

Das Problem ist, dass Sie einen Java-Referenztyp sind Instanziierung. Dann übergeben Sie diesen Referenztyp auf eine statische Methode, und weisen Sie es an eine lokal scoped Variable.

Es hat nichts mit Unveränderlichkeit zu tun. Genau dasselbe für eine veränderbare Referenztyp passiert wäre.

Wenn wir eine grobe C und Assembler Analogie machen würden:

void Main()
{ 
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByValue(message); // pass the pointer pointed to of message.  0x0001, not 0x8001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0001.  still the same

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}

void PassStringByValue(string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the memory pointed to of message, 0x0001
    printf("%d", foo);  // 0x0001
    // World is in memory address 0x0002
    foo = "World";  // on foo's memory address (0x4001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x4001] = 0x0002

    // print the new memory pointed by foo
    printf("%d", foo); // 0x0002

    // Conclusion: Not in any way 0x8001 was involved in this function.  Hence you cannot change the Main's message value.
    // foo = "World"  is same as [0x4001] = 0x0002

}

void Main()
{
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByRef(ref message); // pass the stack memory address of message.  0x8001, not 0x0001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}


void PassStringByRef(ref string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the address of message(0x8001)
    printf("%d", foo);  // 0x8001
    // World is in memory address 0x0002
    foo = "World"; // on message's memory address (0x8001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x8001] = 0x0002;


    // print the new memory pointed to of message
    printf("%d", foo); // 0x0002

    // Conclusion: 0x8001 was involved in this function.  Hence you can change the Main's message value.
    // foo = "World"  is same as [0x8001] = 0x0002

}

Ein möglicher Grund, warum alles von Wert in Java übergeben wird, wollen seine Sprache Designer Leute, die Sprache vereinfachen und alles in OOP Weise getan zu machen.

Sie würden eher Sie eine ganze Zahl entwerfen haben Swapper Objekte mit als sie eine erstklassige Unterstützung für vorbei Verweis liefern, die gleiche für die Delegierten (Gosling fühlt sich eklig mit Zeiger auf Funktion, er würde lieber diese Funktionalität auf Objekte stopfen) und enum.

Sie über vereinfachen (alles Objekt ist) die Sprache, zum Nachteil der nicht erstklassigen Support für die meisten Sprachkonstrukte, z.B. Bezug genommen wird, Delegierte, Enum vorbei, Eigenschaften in den Sinn kommt.

Sind Sie sicher, dass es druckt null? Ich denke, es wird einfach leer, wie wenn Sie die foo Variable initialisiert Sie leeren String zur Verfügung gestellt.

Die Zuordnung von foo in thisDoesntWork Verfahren ändert nicht die Referenz der foo Variable in der Klasse definiert so die foo in System.out.println (foo) wird noch auf den alten leeren String-Objekt verweisen.

Dave, müssen Sie mir verzeihen (na ja, ich denke, Sie nicht „haben“, aber ich würde lieber haben Sie), aber diese Erklärung ist nicht allzu überzeugend. Die Sicherheitsgewinne sind ziemlich gering, da jeder, der den Wert der Zeichenfolge einen Weg finden muss, ändern Sie es mit einigen hässlichen Abhilfe zu tun. Und Geschwindigkeit ?! Sie selbst (ganz richtig) behauptet, dass das ganze Geschäft mit der + extrem teuer ist.

Der Rest von euch, bitte verstehen, dass ich bekommen, wie es funktioniert, ich gefragt bin, WARUM es so funktioniert ... stoppen Sie bitte den Unterschied zwischen den Methoden zu erklären.

(und ich bin ehrlich gesagt nicht für jede Art von Kampf suche hier, btw, ich sehe einfach nicht, wie dies eine rationale Entscheidung war).

@Axelle

Kamerad wissen Sie wirklich den Unterschied zwischen vorbei Wert und durch Bezugnahme?

In Java sogar Referenzen werden von Wert übergeben. Wenn Sie einen Verweis auf ein Objekt übergeben Sie eine Kopie des Referenzzeigers in den zweiten Variablen zu bekommen. Tahts, warum die zweite Variable kann, ohne dass die erste geändert werden.

Es ist, weil es eine lokale Variable in der Methode erstellt. was eine einfache Möglichkeit wäre (was ich arbeiten würde ziemlich sicher bin) wäre:

String foo = new String();    

thisDoesntWork(foo);    
System.out.println(foo); //this prints nothing

public static void thisDoesntWork(String foo) {    
   this.foo = foo; //this makes the local variable go to the main variable    
   foo = "howdy";    
}

Wenn Sie ein Objekt denken, als nur die Felder im Objekt dann Objekte durch Verweis in Java übergeben werden, weil eine Methode die Felder eines Parameters ändern kann, und ein Anrufer kann die Änderung beobachten. Allerdings, wenn Sie denken auch an einem Objekt wie es ist Identität dann Objekte von Wert übergeben werden, da eine Methode kann nicht die Identität eines Parameters in einer Art und Weise ändern, dass der Anrufer beobachten kann. Also ich würde sagen Java ist Pass-by-Wert.

Das ist, weil im Inneren „thisDoesntWork“, werden Sie effektiv den lokalen Wert foo zu zerstören. Wenn Sie mit dem Verweis auf diese Weise übergeben wollen, können immer den String in einem anderen Objekt kapseln, sagen wir in einem Array.

class Test {

    public static void main(String[] args) {
        String [] fooArray = new String[1];
        fooArray[0] = new String("foo");

        System.out.println("main: " + fooArray[0]);
        thisWorks(fooArray);
        System.out.println("main: " + fooArray[0]);
    }

    public static void thisWorks(String [] foo){
        System.out.println("thisWorks: " + foo[0]);
        foo[0] = "howdy";
        System.out.println("thisWorks: " + foo[0]);
    }
}

Ergebnisse in der folgenden Ausgabe:

main: foo
thisWorks: foo
thisWorks: howdy
main: howdy

Referenz Argumente eingegeben werden, als Verweise auf die Objekte selbst übergeben (keine Verweise auf andere Variablen , die beziehen sich auf Objekte). Sie können Methoden für das Objekt aufrufen, übergeben wurde. Jedoch in Ihrem Codebeispiel:

public static void thisDoesntWork(String foo){
    foo = "howdy";
}

Sie speichern nur einen Verweis auf die Zeichenfolge "howdy" in einer Variablen, die auf das Verfahren lokal ist. Das lokale Variable (foo) wurde auf den Wert des foo des Anrufers initialisiert, wenn die Methode aufgerufen wurde, hat jedoch keinen Hinweis auf die Anrufer-Variable selbst. Nach der Initialisierung:

caller     data     method
------    ------    ------
(foo) -->   ""   <-- (foo)

Nach der Zuordnung in Ihrer Methode:

caller     data     method
------    ------    ------
(foo) -->   ""
          "hello" <-- (foo)

Sie haben eine andere Probleme gibt: String Instanzen sind unveränderlich (von Design, für die Sicherheit), so dass Sie kann ihren Wert nicht ändern

.

Wenn Sie wirklich wollen, um Ihre Methode einen Anfangswert für die Zeichenfolge zur Verfügung zu stellen (oder bei jeder Zeit in ihrem Leben, was das betrifft), dann Ihre Methode hat zurück ein String Wert, den Sie an der Stelle des Anrufs des Anrufers variablen zuweisen. So etwas wie dies, zum Beispiel:

String foo = thisWorks();
System.out.println(foo);//this prints the value assigned to foo in initialization 

public static String thisWorks(){
    return "howdy";
}

Sehen Sie sich die wirklich großen Tutorial tun auf Sonnen-Website.

Sie scheinen nicht die Differenz Tive Variablen sein kann, zu verstehen. „Foo“ ist Ihre Methode lokal. Nichts außerhalb dieser Methode kann ändern, was „foo“ zu hindeutet. Die „foo“ bezeichnet wird, um Ihre Methode ein ganz anderes Feld ist - es ist ein statisches Feld auf dem umgebenden Klasse.

Scoping ist besonders wichtig, da man alles nicht wollen, alles andere im System sichtbar sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top