web-dev-qa-db-fra.com

Comment passer correctement une classe Integer par référence?

J'espère que quelqu'un pourra clarifier ce qui se passe ici pour moi. J'ai fouillé un peu dans la classe entière mais parce que l'entier est écrasant le + opérateur Je ne pouvais pas comprendre ce qui n'allait pas. Mon problème est avec cette ligne:

Integer i = 0;
i = i + 1;  // ← I think that this is somehow creating a new object!

Voici mon raisonnement: je sais que Java est passe par valeur ( ou passe par valeur de référence ), donc je pense que dans l'exemple suivant l'objet entier devrait être incrémenté à chaque fois.

public class PassByReference {

    public static Integer inc(Integer i) {
        i = i+1;    // I think that this must be **sneakally** creating a new integer...  
        System.out.println("Inc: "+i);
        return i;
    }

    public static void main(String[] args) {
        Integer integer = new Integer(0);
        for (int i =0; i<10; i++){
            inc(integer);
            System.out.println("main: "+integer);
        }
    }
}

Voici ma sortie attendue:

 Inc: 1 
 Principal: 1 
 Inc: 2 
 Principal: 2 
 Inc: 3 
 Principal: 3 
 Inc: 4 
 Principal: 4 
 Inc: 5 
 Principal: 5 
 Inc: 6 
 Principal: 6 
 ... 

Il s'agit de la sortie réelle.

 Inc: 1 
 Principal: 0 
 Inc: 1 
 Principal: 0 
 Inc: 1 
 Principal: 0 
 ... 

Pourquoi se comporte-t-il ainsi?

54
sixtyfootersdude

Il y a deux problèmes:

  1. L'entier est transmis par valeur et non par référence. La modification de la référence à l'intérieur d'une méthode ne sera pas reflétée dans la référence transmise dans la méthode appelante.
  2. L'entier est immuable. Il n'y a pas une telle méthode comme Integer#set(i). Sinon, vous pourriez simplement en faire usage.

Pour le faire fonctionner, vous devez réaffecter la valeur de retour de la méthode inc().

integer = inc(integer);

Pour en savoir un peu plus sur le passage par valeur, voici un autre exemple:

public static void main(String... args) {
    String[] strings = new String[] { "foo", "bar" };
    changeReference(strings);
    System.out.println(Arrays.toString(strings)); // still [foo, bar]
    changeValue(strings);
    System.out.println(Arrays.toString(strings)); // [foo, foo]
}
public static void changeReference(String[] strings) {
    strings = new String[] { "foo", "foo" };
}
public static void changeValue(String[] strings) {
    strings[1] = "foo";
}
55
BalusC

Le nombre entier est immuable. Vous pouvez envelopper int dans votre classe wrapper personnalisée.

class WrapInt{
    int value;
}

WrapInt theInt = new WrapInt();

inc(theInt);
System.out.println("main: "+theInt.value);
18
Markos

Il y a 2 façons de passer par référence

  1. Utilisez org.Apache.commons.lang.mutable.MutableInt de la bibliothèque Apache Commons.
  2. Créez une classe personnalisée comme indiqué ci-dessous

Voici un exemple de code pour le faire:

public class Test {
    public static void main(String args[]) {
        Integer a = new Integer(1);
        Integer b = a;
        Test.modify(a);
        System.out.println(a);
        System.out.println(b);

        IntegerObj ao = new IntegerObj(1);
        IntegerObj bo = ao;
        Test.modify(ao);
        System.out.println(ao.value);
        System.out.println(bo.value);
    }


    static void modify(Integer x) {
        x=7;
    }
    static void modify(IntegerObj x) {
        x.value=7;
    }   
}

class IntegerObj {
    int value;
    IntegerObj(int val) {
        this.value = val;
    }
}

Sortie:

1
1
7
7
15
Rishi Dua

Bonnes réponses ci-dessus expliquant la vraie question du PO.

Si quelqu'un doit faire circuler un numéro qui doit être mis à jour globalement, utilisez le AtomicInteger() au lieu de créer les différentes classes wrapper suggérées ou de s'appuyer sur des bibliothèques tierces.

Le AtomicInteger() est bien sûr principalement utilisé pour un accès sécurisé aux threads, mais si la performance n'est pas un problème, pourquoi ne pas utiliser cette classe intégrée. Le bonus supplémentaire est bien sûr la sécurité évidente du fil.

import Java.util.concurrent.atomic.AtomicInteger
14
Hans

Ce que vous voyez ici n'est pas un opérateur + Surchargé, mais un comportement de mise en boîte automatique. La classe Integer est immuable et votre code:

Integer i = 0;
i = i + 1;  

est vu par le compilateur (après l'autoboxing) comme:

Integer i = Integer.valueOf(0);
i = Integer.valueOf(i.intValue() + 1);  

vous avez donc raison de conclure que l'instance de Integer est modifiée, mais pas sournoisement - elle est cohérente avec la définition du langage Java :-)

12
rsp

Vous avez raison ici:

Integer i = 0;
i = i + 1;  // <- I think that this is somehow creating a new object!

Premièrement: l'entier est immuable.

Deuxièmement: la classe Integer ne remplace pas le + opérateur, il y a la mise en boîte automatique et la mise en boîte automatique impliquées sur cette ligne (dans les anciennes versions de Java vous obtiendriez une erreur sur la ligne ci-dessus).
Lorsque vous écrivez i + 1 le compilateur convertit d'abord l'entier en un (primitif) int pour effectuer l'ajout: autounboxing. Ensuite, faire i = <some int> le compilateur convertit de int en un (nouveau) Integer: autoboxing.
Alors + est actuellement appliqué aux ints primitifs.

6
Carlos Heuberger

Je pense que c'est l'autoboxing qui vous déstabilise.

Cette partie de votre code:

   public static Integer inc(Integer i) {
        i = i+1;    // I think that this must be **sneakally** creating a new integer...  
        System.out.println("Inc: "+i);
        return i;
    }

Se résume vraiment à un code qui ressemble à:

  public static Integer inc(Integer i) {
        i = new Integer(i) + new Integer(1);      
        System.out.println("Inc: "+i);
        return i;
    }

Ce qui bien sûr .. ne changera pas la référence transmise.

Vous pouvez le réparer avec quelque chose comme ça

  public static void main(String[] args) {
        Integer integer = new Integer(0);
        for (int i =0; i<10; i++){
            integer = inc(integer);
            System.out.println("main: "+integer);
        }
    }
2
bwawok

Si vous changez votre fonction inc () en ceci

 public static Integer inc(Integer i) {
      Integer iParam = i;
      i = i+1;    // I think that this must be **sneakally** creating a new integer...  
      System.out.println(i == iParam);
      return i;
  }

alors vous verrez qu'il affiche toujours "faux". Cela signifie que l'addition crée une nouvelle instance de Integer et la stocke dans la variable local i ("local", car i est en fait une copie de la référence transmise), laissant la variable de la méthode d'appel intacte.

L'entier est une classe immuable, ce qui signifie que vous ne pouvez pas modifier sa valeur mais devez obtenir une nouvelle instance. Dans ce cas, vous n'avez pas à le faire manuellement comme ceci:

i = new Integer(i+1); //actually, you would use Integer.valueOf(i.intValue()+1);

au lieu de cela, cela se fait par autoboxing.

2
f1sh

1) Seule la copie de référence est envoyée en tant que valeur au paramètre formel. Lorsque la variable de paramètre formelle reçoit une autre valeur, la référence du paramètre formel change mais la référence du paramètre réel reste la même dans le cas de cet objet entier.

classe publique UnderstandingObjects {

public static void main(String[] args) {

    Integer actualParam = new Integer(10);

    changeValue(actualParam);

    System.out.println("Output " + actualParam); // o/p =10

    IntObj obj = new IntObj();

    obj.setVal(20);

    changeValue(obj);

    System.out.println(obj.a); // o/p =200

}

private static void changeValue(Integer formalParam) {

    formalParam = 100;

    // Only the copy of reference is set to the formal parameter
    // this is something like => Integer formalParam =new Integer(100);
    // Here we are changing the reference of formalParam itself not just the
    // reference value

}

private static void changeValue(IntObj obj) {
    obj.setVal(200);

    /*
     * obj = new IntObj(); obj.setVal(200);
     */
    // Here we are not changing the reference of obj. we are just changing the
    // reference obj's value

    // we are not doing obj = new IntObj() ; obj.setValue(200); which has happend
    // with the Integer

}

}

class IntObj {Entier a;

public void setVal(int a) {
    this.a = a;
}

}

0
awinas kannan