Quelqu'un peut-il me expliquer ce que le raisonnement derrière en passant par la « valeur » et non par « référence » en Java est?

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

Question

Je suis assez nouveau à Java (été écrit d'autres choses pendant de nombreuses années) et à moins que je me manque quelque chose (et je suis heureux d'être mal ici) ce qui suit est une erreur fatale ...

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

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

Maintenant, je suis bien conscient du concept (assez mal formulée) qui en Java tout est passé par « valeur » et non « référence », mais la chaîne est un objet et a toutes sortes de cloches et de sifflets, de sorte que, on pourrait attendre à ce que la différence d'un int un utilisateur serait en mesure de fonctionner sur la chose qui est passé dans la méthode (et ne pas être coincé avec la valeur définie par les = surchargées).

Quelqu'un peut-il me expliquer ce que le raisonnement derrière ce choix de conception a été? Comme je l'ai dit, je ne cherche pas à être ici, et peut-être je manque quelque chose évidente?

Était-ce utile?

La solution

Cette diatribe explique mieux que je ne pourrais jamais même essayer de:

  

En Java, les primitives sont passés par valeur. Cependant, les objets ne sont pas   passé par référence. Une déclaration correcte serait des références d'objets   sont passés par valeur.

Autres conseils

Lorsque vous passez "foo", vous passez référence "foo" comme valeur à ThisDoesntWork (). Cela signifie que lorsque vous faites l'affectation à « foo » à l'intérieur de votre méthode, vous définissez simplement une variable locale (foo) de référence à une référence à votre nouvelle chaîne.

Une autre chose à garder à l'esprit lorsque vous pensez à la façon dont les chaînes se comportent en Java est que les chaînes sont immuables. Il fonctionne de la même manière en C #, et pour de bonnes raisons:

  • Sécurité : Personne ne peut bloquer des données dans votre chaîne et provoquer une erreur de dépassement de mémoire tampon si personne ne peut le modifier
  • Vitesse : Si vous pouvez être sûr que vos chaînes sont immuables, vous savez sa taille est toujours le même et vous n'avez jamais à faire un mouvement de la structure de données en mémoire lorsque vous manipuler. Vous (le concepteur du langage) aussi ne pas avoir à se soucier de la mise en œuvre de la chaîne comme une lente liste chaînée, que ce soit. Cela permet de réduire les deux sens, cependant. chaînes Annexer simplement en utilisant l'opérateur + peut être sage mémoire coûteuse, et vous devez utiliser un objet StringBuilder pour le faire dans une haute performance, façon économe en mémoire.

Maintenant sur votre grande question. Pourquoi les objets sont passés de cette façon? Eh bien, si Java passe votre chaîne comme ce que vous appelez traditionnellement « en valeur », il faudrait copier réellement toute la chaîne avant de passer à votre fonction. C'est assez lent. Si elle a adopté la chaîne par référence et laissez-vous le changer (comme C fait), vous auriez les problèmes que je viens d'énumérer.

Depuis ma première réponse était « Pourquoi est-il arrivé » et non « Pourquoi la langue conçu de telle sorte qu'il est arrivé, » Je vais vous donner cette autre fois.

Pour simplifier les choses, je vais me débarrasser de l'appel de méthode et de montrer ce qui se passe d'une autre manière.

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

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

Pour obtenir la dernière déclaration d'imprimer "bonjour", b doit pointer vers le même "trou" dans la mémoire que les points (un pointeur). Voici ce que vous voulez quand vous voulez passer par référence. Il y a deux raisons Java a décidé de ne pas aller dans cette direction:

  • Pointeurs confonds Les concepteurs de Java ont essayé de supprimer certaines des choses plus confuses au sujet d'autres langues. Les pointeurs sont l'une des constructions les plus mal compris et mal utilisées de C / C ++ ainsi que la surcharge de l'opérateur.

  • Les pointeurs sont des risques de sécurité Pointeurs causer de nombreux problèmes de sécurité est mal utilisé. Un programme malveillant affecte quelque chose à cette partie de la mémoire, alors ce que vous pensiez votre objet est en fait quelqu'un d'autre. (Java déjà débarrassé du plus gros problème de sécurité, les dépassements de tampon, avec des tableaux vérifiés)

  • Abstraction Fuites Lorsque vous commencez à traiter « Ce qui est en mémoire et où » exactement, votre abstraction devient moins d'une abstraction. Alors que les fuites d'abstraction s'insinue presque certainement dans une langue, les concepteurs ne voulaient pas faire cuire directement.

  • Les objets sont tous vous vous souciez En Java, tout est un objet, pas l'espace un objet occupe. Ajout de pointeurs rendrait l'espace un objet occupe importantant, bien .......

Vous pouvez imiter ce que vous voulez en créant un objet « trou ». Vous pouvez même utiliser les médicaments génériques pour le faire en toute sécurité de type. Par exemple:

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

Votre question a demandé n'a pas vraiment à voir avec le passage par valeur, en passant par référence, ou le fait que les chaînes sont immuables (comme d'autres l'ont dit).

Dans la méthode, vous créez en fait une variable locale (je vais appeler que l'on « localFoo ») qui pointe vers la même référence que votre variable originale ( « originalFoo »).

Lorsque vous attribuez "Howdy" à localFoo, vous ne changez pas où originalFoo pointe.

Si vous avez fait quelque chose comme:

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

Vous attendez-vous:

System.out.print(a)

pour imprimer "Howdy"? Il imprime "".

Vous ne pouvez pas modifier ce originalFoo points en changeant ce localFoo points. Vous pouvez modifier l'objet à la fois le point (si ce n'était pas immuable). Par exemple,

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);
}

En java toutes les variables passées sont en fait passés autour de même des objets à valeur. Toutes les variables passées à une méthode sont en fait des copies de la valeur d'origine. Dans le cas de votre exemple de chaîne le pointeur d'origine (sa fait une référence - mais pour éviter toute confusion mauvais usage, un autre mot) est copié dans une nouvelle variable qui devient le paramètre à la méthode

.

Il serait une douleur si tout était par référence. Il faudrait faire des copies privées dans tous les sens qui serait certainement une vraie douleur. Tout le monde sait que l'utilisation immuabilité pour les types de valeur etc rend vos programmes infiniment plus simple et plus évolutive.

Certains avantages sont les suivants: - Pas besoin de faire des copies de défense. - ThreadSafe -. Pas besoin de se soucier de verrouillage juste au cas où quelqu'un d'autre veut changer l'objet

Le problème est que vous instancier un type de référence Java. Ensuite, vous passez ce type de référence à une méthode statique, et le réattribuer à une variable scope localement.

Il n'a rien à voir avec immuabilité. Exactement la même chose se serait passé pour un type de référence mutable.

Si nous faire une analogie C et assembleur approximatif:

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

}

L'une des raisons pourquoi tout est passé par valeur en Java, ses gens de concepteur de langue veulent simplifier le langage et tout se fait de manière POO.

Ils préféreraient que vous concevez un entier swapper en utilisant des objets que leur fournissent un soutien de première classe par référence passant, le même pour délégué (Gosling se sent dégueu avec pointeur de fonction, il préférerait entasser cette fonctionnalité à des objets) et ENUM.

Ils trop simplifier (tout est objet) la langue au détriment de ne pas avoir soutien de première classe pour la plupart des constructions de langage, par exemple en passant par référence, les délégués, ENUM, propriétés vient à l'esprit.

Êtes-vous sûr qu'il imprime null? Je pense que ce sera juste vide comme lors de l'initialisation de la variable foo vous avez fourni chaîne vide.

L'attribution de foo dans la méthode thisDoesntWork ne change pas la référence de la variable foo défini dans la classe de sorte que le foo dans System.out.println (foo) sera toujours pointer sur l'ancien objet chaîne vide.

Dave, tu dois me pardonner (eh bien, je suppose que vous ne « faut », mais je préfère vous a fait), mais cette explication est pas trop convaincant. Les gains de sécurité sont assez minimes, car ceux qui ont besoin de changer la valeur de la chaîne va trouver un moyen de le faire avec une solution de contournement laid. Et la vitesse ?! Vous vous (tout à fait correctement) affirment que toute l'affaire avec le + est extrêmement coûteux.

Le reste de vous les gars, s'il vous plaît comprendre que je reçois comment cela fonctionne, je demande pourquoi il fonctionne de cette façon ... s'il vous plaît arrêter d'expliquer la différence entre les méthodes.

(et honnêtement, je ne suis pas à la recherche d'une sorte de combat ici, d'ailleurs, je ne vois pas comment cela a été une décision rationnelle).

@Axelle

Maté savez-vous vraiment la différence entre le passage par valeur et par référence?

En java même des références sont passés par valeur. Lorsque vous passez une référence à un objet que vous obtenez une copie du pointeur de référence dans la deuxième variable. Tahts pourquoi peut être changé la seconde variable sans affecter la première.

Il est dû au fait, il crée une variable locale dans la méthode. ce serait un moyen facile (que je suis assez sûr travaillerait) serait:

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";    
}

Si vous pensez à un objet comme seulement les champs de l'objet puis les objets sont passés par référence en Java, car une méthode peut modifier les champs d'un paramètre et un appelant peut observer la modification. Toutefois, si vous pensez aussi d'un objet comme son identité alors des objets sont passés par valeur, car une méthode ne peut pas changer l'identité d'un paramètre d'une manière que l'appelant peut observer. Je dirais donc que Java est passe par valeur.

En effet, à l'intérieur « thisDoesntWork », vous êtes en train de détruire efficacement la valeur locale de foo. Si vous voulez passer par référence de cette manière, peut toujours encapsuler la chaîne dans un autre objet, par exemple dans un tableau.

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]);
    }
}

Les résultats de la sortie suivante:

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

arguments dactylographié de référence sont passés comme des références à des objets eux-mêmes (et non des références à d'autres Variables qui font référence à des objets). Vous pouvez appeler des méthodes sur l'objet qui a été transmis. Cependant, dans votre exemple de code:

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

vous ne stockez une référence à la chaîne dans une variable "howdy" qui est locale à la méthode. Cette variable locale (foo) a été initialisé à la valeur de l'appelant de String lorsque la méthode a été appelée, mais n'a pas de référence à lui-même la variable de l'appelant. Après l'initialisation:

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

Après l'affectation dans votre méthode:

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

Vous avez encore des questions là: les cas sont immuables <=> (par la conception, de la sécurité) de sorte que vous ne pouvez pas modifier sa valeur

.

Si vous voulez vraiment que votre méthode pour fournir une valeur initiale de votre chaîne (ou any temps dans sa vie, d'ailleurs), alors votre méthode retour une valeur que vous attribuez <=> à la variable de l'appelant au moment de l'appel. Quelque chose comme ça, par exemple:

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

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

Aller faire le tutoriel vraiment grand sur le site Web de soleils.

Vous semblez ne pas comprendre la différence des étendues variables peuvent être. « Foo » est locale à votre méthode. Rien en dehors de cette méthode peut changer ce « foo », souligne aussi. Le « foo » étant appelée méthode est un champ complètement différent - est un champ statique sur votre classe englobante.

est particulièrement important établissement de la portée que vous ne voulez pas que tout soit visible pour tout le reste dans votre système.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top