28 votes

Comment pouvez-vous étendre Java pour introduire le passage par référence?

Java est passé par valeur. Comment pourriez-vous modifier la langue d'introduire le passage par référence (ou l'équivalent comportement)?

Prenez par exemple quelque chose comme

public static void main(String[] args) {
    String variable = "'previous String reference'";
    passByReference(ref variable);
    System.out.println(variable); // I want this to print 'new String reference'
}

public static void passByReference(ref String someString) {
    someString = "'new String reference'";
}

qui (sans le ref) compile à la suite de bytecode

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String 'previous String reference'
       2: astore_1
       3: aload_1
       4: invokestatic  #3                  // Method passByReference:(Ljava/lang/String;)V
       7: return

  public static void passByReference(java.lang.String);
    Code:
       0: ldc           #4                  // String 'new String reference'
       2: astore_0
       3: return

Le code lors de l' 3: des charges de la référence dans la pile de la variable variable.

Une possibilité que j'envisage est d'avoir le compilateur de déterminer une méthode est de passer par référence, éventuellement avec ref, et de modifier la méthode d'accepter un Détenteur de l'objet qui stocke la même référence que notre variable. Lorsque la méthode est terminée, et, éventuellement, des changements qui font référence dans le support, la variable sur l'appelant côté de la valeur est remplacée par le titulaire de référence de la valeur.

Il doit compiler pour un équivalent de ce

public static void main(String[] args) {
    String variable = "'previous String reference'";
    Holder holder = Holder.referenceOf(variable);
    passByReference2(holder);
    variable = (String) holder.getReference(); // I don't think this cast is necessary in bytecode
    System.out.println(variable);
}

public static void passByReference(Holder someString) {
    someString.setReference("'new String reference'");
}

Holder pourrait être quelque chose comme

public class Holder {
    Object reference;
    private Holder (Object reference) {
        this.reference = reference;
    }
    public Object getReference() {
        return this.reference;
    }
    public void setReference(Object reference) {
        this.reference = reference;
    }
    public static Holder referenceOf(Object reference) {
        return new Holder(reference);
    }
}

Où cela peut-il échouer ou comment pourriez-vous améliorer?

21voto

Jeffrey Hantin Points 19272

L'idiome habituel que j'ai vu pour le passage par référence en Java est de passer un tableau à un seul élément, qui préservera à la fois la sécurité de type au moment de l'exécution (contrairement aux génériques qui subissent l'effacement) et évitera la nécessité d'introduire une nouvelle classe .

 public static void main(String[] args) {
    String[] holder = new String[1];

    // variable optimized away as holder[0]
    holder[0] = "'previous String reference'";

    passByReference(holder);
    System.out.println(holder[0]);
}

public static void passByReference(String[] someString) {
    someString[0] = "'new String reference'";
}
 

14voto

Kevin K Points 4279

Pour répondre à votre question:

Où cela peut-il échouer?

  1. Les variables Final et enum constantes
  2. "Spécial" des références telles que this
  3. Les références qui sont renvoyés par les appels de méthode, ou de la construction inline l'aide d' new
  4. Les littéraux (Chaînes de caractères, entiers, etc.)

...et peut-être d'autres. Fondamentalement, votre ref mot-clé ne doit être disponible que si le paramètre source est un non définitif champ ou une variable locale. Une autre source doit générer une erreur de compilation lorsqu'il est utilisé avec ref.

Un exemple de (1):

final String s = "final";
passByReference(ref s);  // Should not be possible

Un exemple de (2):

passByReference(ref this);  // Definitely impossible

Un exemple de (3):

passByReference(ref toString());  // Definitely impossible
passByReference(ref new String("foo"));  // Definitely impossible

Un exemple de (4):

passByReference(ref "literal");  // Definitely impossible

Et puis il y a cession des expressions qui semblent à moi comme quelque chose d'un jugement d'appel:

String s;
passByReference(ref (s="initial"));  // Possible, but does it make sense?

C'est aussi un peu étrange que votre syntaxe nécessite l' ref mot-clé pour la définition de la méthode et de l'invocation de la méthode. Je pense que la définition de la méthode serait suffisant.

9voto

TwoThe Points 4053

Votre tentative de modifier la langue ignore le fait que cette "fonctionnalité" a été explicitement à gauche afin d'éviter que le bien-connu des effets secondaires des bugs d'être en mesure de se produire en premier lieu. Java recommande de faire ce que vous essayez d'archiver par l'utilisation de données de titulaire de classes:

public class Holder<T> {
  protected T value;

  public T getValue() {
    return value;
  }

  public void setValue(T value) {
    this.value = value;
  }
}

Une version thread-safe serait le AtomicReference.

Maintenant, ne conservant qu'une seule Chaîne de caractères dans une classe semble plus de kill et plus il est probable, cependant ils ont généralement une des données de titulaire de classe pour plusieurs valeurs au lieu d'une seule Chaîne.

Le grand avantage de cette approche est que ce qui se passe à l'intérieur de la méthode est très explicite. Donc, même si vous programmez un lundi matin après un week-end riche en événements et la machine à café vient de se casser vers le bas, vous pouvez toujours dire facilement ce que fait le code (KISS), la prévention de plusieurs bugs de même en premier lieu, juste parce que vous avez oublié que l'un des éléments de la méthode foo.

Si vous pensez à ce que votre approche peut faire que les données de titulaire de version ne peut pas, vous vous rendrez vite compte que vous êtes la mise en œuvre de quelque chose juste parce que c'est différent, mais effectivement il n'a pas de valeur réelle.

7voto

Sinto K Itteera Points 552

Utilisation de la classe AtomicReference comme objet support.

 public static void main(String[] args) {
    String variable="old";
    AtomicReference<String> at=new AtomicReference<String>(variable);
    passByReference(at);
    variable=at.get();
    System.out.println(variable);
}

public static void passByReference(AtomicReference<String> at) {
  at.set("new");
}
 

4voto

James_pic Points 1684

Curieusement, j'ai réfléchi à ce problème moi-même récemment. J'ai été d'examiner s'il peut être amusant de créer un dialecte de VB qui a couru sur la JVM - j'ai décidé qu'il ne serait pas.

De toute façon, il y a deux cas où cela est susceptible d'être utile et bien définies:

  • les variables locales
  • les attributs de l'objet

Je suis en supposant que vous êtes en train de rédiger un nouveau compilateur (ou l'adaptation d'un produit existant) pour votre nouveau dialecte de Java.

Les variables locales sont généralement traitées par un code similaire à ce que vous proposez. Je suis plus familier avec Scala, qui ne supporte pas passé par référence, mais prend en charge les fermetures, qui ont les mêmes problèmes. En Scala, il y a une classe scala.runtime.ObjectRef, qui ressemble à votre Holder classe. Il y a également similaire {...}Ref classes de primitives, la volatilité des variables, et similaires.

Si le compilateur a besoin de créer une fermeture qui met à jour une variable locale, il "mises à jour" de la variable final ObjectRef (ce qui peut être transmis à la fermeture de son constructeur), et remplace les utilisations de cette variable en gets et mises à jour en sets, sur l' ObjectRef. Dans votre compilateur, vous pouvez mettre à niveau les variables locales chaque fois qu'ils sont passés par référence.

Vous pouvez utiliser une astuce similaire avec les attributs de l'objet. Supposons qu' Holder implémente une interface ByRef. Lors de votre compilateur voit un attribut de l'objet étant passé par référence, il pourrait créer un anonyme sous-classe de la ByRef que les lectures et les mises à jour de l'attribut de l'objet dans ses get et set méthodes. Encore une fois, Scala fait quelque chose de similaire à ce pour paresseusement paramètres évalués (comme des références, mais en lecture seule).

Pour plus de bons points, vous pouvez étendre la techique JavaBean propriétés et même Map, List et Array - éléments.

Un effet secondaire de ceci est que, au niveau de la JVM, vos méthodes inattendues signatures. Si vous compilez une méthode avec signature void doIt(ref String), au niveau du bytecode, vous vous retrouverez avec la signature void doIt(ByRef) (vous pourrait s'attendre à quelque chose comme void doIt(ByRef<String>), mais bien sûr, les génériques du type d'utilisation de l'effacement). Cela peut causer des problèmes avec la surcharge de méthode, comme toutes les par-ref paramètres de compilation à la même signature.

Il peut être possible de le faire avec le bytecode de la manipulation, mais il ya des pièges, comme le fait que la JVM des demandes de permis de ré-utiliser des variables locales - donc au niveau du bytecode, il peut ne pas être clair si un paramètre est en cours de ré-attribuée, ou sa fente re-utilisé, si la demande a été compilé sans les symboles de débogage. En outre, le compilateur peut éluder aload instructions si il n'y a pas de possibilité d'une valeur d'avoir changé de l'intérieur que l'extérieur de la méthode - si vous ne prenez pas des mesures pour éviter cela, des changements à votre variable de référence peut ne pas être reflétée dans la méthode extérieure.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X