Jak można rozszerzyć Javę, aby wprowadzić passing by reference?

Java to pass-by-value. Jak można zmodyfikować język, aby wprowadzić przechodzenie przez odniesienie (lub jakieś równoważne zachowanie)?

Weźmy na przykład coś w rodzaju

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

Który (bez ref) kompiluje się do następującego bajtowego kodu

  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

Kod w 3: ładuje referencję na stos ze zmiennej variable.

Jedną z możliwości, którą rozważam, jest to, aby kompilator określił metodę jako przekazaną przez odniesienie, być może za pomocą ref I zmienić metodę na accept obiekt Holder, który przechowuje to samo odniesienie co nasza zmienna. Gdy metoda zakończy się i ewentualnie zmieni to odniesienie w uchwycie, zmienna po stronie wywołującej zostanie zastąpiona wartością odniesienia posiadacza.

Powinno się skompilować do odpowiednika tego

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

Gdzie Holder może być coś w rodzaju

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

Gdzie to może się nie udać lub jak można to poprawić?

Author: Community, 2014-01-22

10 answers

Aby odpowiedzieć na twoje pytanie:

Gdzie to może się nie udać?

  1. zmienne końcowe i stałe enum
  2. "specjalne" odniesienia, takie jak this
  3. odwołania, które są zwracane z wywołań metod lub konstruowane inline za pomocą new
  4. literały (ciągi, liczby całkowite itp.)

...i ewentualnie inne. Zasadniczo, twoje słowo kluczowe ref może być użyteczne tylko wtedy, gdy źródłem parametru jest niekończące się pole lub zmienna lokalna. Inne źródło powinno generować błąd kompilacji, gdy jest używane z ref.

Przykład (1):

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

Przykład (2):

passByReference(ref this);  // Definitely impossible

Przykład (3):

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

Przykład (4):

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

A potem są wyrażenia przypisania, które wydają mi się czymś w rodzaju wezwania sądu:

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

To również trochę dziwne, że Twoja składnia wymaga słowa kluczowego ref zarówno dla definicji metody, jak i wywołania metody. Myślę, że metoda definicja byłaby wystarczająca.

 13
Author: Kevin K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-31 15:07:05

Typowym idiomem, który widziałem dla pass-by-reference w Javie, jest przekazanie tablicy jednoelementowej, która zarówno zachowa bezpieczeństwo typu run-time (w przeciwieństwie do generyków, które ulegają kasowaniu), jak i uniknie konieczności wprowadzania nowej klasy.

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'";
}
 22
Author: Jeffrey Hantin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-25 00:12:16

Twoja próba modyfikacji języka ignoruje fakt, że ta "funkcja" została wyraźnie pominięta, aby zapobiec wystąpieniu znanych błędów efektów ubocznych. Java zaleca, aby zrobić to, co próbujesz zarchiwizować, używając klas posiadaczy danych:

public class Holder<T> {
  protected T value;

  public T getValue() {
    return value;
  }

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

Wersją bezpieczną dla wątku będzie AtomicReference .

Teraz przechowywanie pojedynczego ciągu w klasie wydaje się zbyt zabójcze i najprawdopodobniej tak jest, jednak zwykle masz klasę data-holder dla kilka powiązanych wartości zamiast jednego ciągu znaków.

Dużą zaletą tego podejścia jest to, że to, co dzieje się wewnątrz metody jest bardzo wyraźne. Więc nawet jeśli programujesz w poniedziałek rano po pełnym wrażeń weekendzie i ekspres do kawy właśnie się zepsuł, nadal możesz łatwo powiedzieć, co robi kod (KISS ), zapobiegając nawet kilku błędom, tylko dlatego, że zapomniałeś o jednej funkcji metody foo.

Jeśli myślisz o tym, co twoje podejście może zrobić, że wersja posiadacza danych nie może, wkrótce zdasz sobie sprawę, że wdrażasz coś tylko dlatego, że jest inaczej, ale skutecznie nie ma prawdziwej wartości.

 10
Author: TwoThe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-25 17:04:16

Używanie klasy AtomicReference jako obiektu holder.

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");
}
 8
Author: Sinto K Itteera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-22 05:52:24

O dziwo, sam ostatnio myślałem o tym problemie. Zastanawiałem się, czy nie byłoby fajnie stworzyć dialekt VB, który działa na JVM - zdecydowałem, że nie będzie.

W każdym razie istnieją dwa główne przypadki, w których może to być przydatne i dobrze zdefiniowane:]}
  • zmienne lokalne
  • atrybuty obiektu

Zakładam, że piszesz nowy kompilator (lub adaptujesz istniejący) dla Twojego nowego dialektu Java.

Zmienne lokalne są zwykle obsługiwane przez kod podobny do tego, co proponujesz. Najbardziej znam Scalę, która nie obsługuje pass-by-reference, ale obsługuje zamknięcia, które mają te same problemy. W Scali istnieje klasa scala.runtime.ObjectRef, która przypomina Twoją klasę Holder. Istnieją również podobne klasy {...}Ref dla prymitywów, zmiennych lotnych i podobnych.

Jeśli kompilator musi utworzyć zamknięcie aktualizujące zmienną lokalną," aktualizuje" zmienna do final ObjectRef (która może być przekazana do zamknięcia w jej konstruktorze) i zastępuje użycie tej zmiennej przez get s, a aktualizacje przez set s, na ObjectRef. W kompilatorze można uaktualnić zmienne lokalne za każdym razem, gdy są przekazywane przez odniesienie.

Możesz użyć podobnej sztuczki z atrybutami obiektu. Załóżmy, że Holder implementuje interfejs ByRef. Gdy kompilator widzi, że atrybut obiektu jest przekazywany przez odniesienie, może utworzyć anonimową podklasę ByRef odczytuje i aktualizuje atrybut obiektu w swoich metodach get i set. Ponownie, Scala robi coś podobnego dla leniwie ocenianych parametrów (jak referencje, ale tylko do odczytu).

Aby uzyskać dodatkowe punkty brownie, możesz rozszerzyć techique o właściwości JavaBean, a nawet Map, List i Array elementów.

Jednym z efektów ubocznych jest to, że na poziomie JVM, Twoje metody mają nieoczekiwane sygnatury. Jeśli skompilujesz metodę z podpisem void doIt(ref String), na poziomie kodu bajtowego, skończysz z podpisem void doIt(ByRef) (możesz się spodziewać, że będzie to coś w rodzaju void doIt(ByRef<String>), Ale oczywiście generyki używają usuwania typu). Może to spowodować problemy z przeciążeniem metody, ponieważ wszystkie parametry by-ref kompilują się do tej samej sygnatury.

It may be possible to do this with bytecode manipulation, but there are pułapki, like the fact that the JVM allows applications to re-use local variables-so at the bytecode level, it may not be clear whether a parameter is being re-assigned, lub jego gniazdo ponownie wykorzystane, jeśli aplikacja została skompilowana bez debugowania symboli. Ponadto kompilator może pomijać instrukcje aload, Jeśli nie ma możliwości zmiany wartości w zewnętrznej metodzie - jeśli nie podejmiesz kroków, aby tego uniknąć, zmiany w zmiennej referencyjnej mogą nie być odzwierciedlone w zewnętrznej metodzie.

 3
Author: James_pic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-04 13:02:11

Myślę, że możesz osiągnąć większość tego, co chcesz, budując agenta i używając cglib.

Wiele z podanych tutaj przykładów może zadziałać. Polecam użycie zaproponowanego przez Ciebie szablonu, ponieważ będzie on kompilowany z normalnym kompilatorem.

public void doSomething(@Ref String var)

Potem za kulisami używasz cglib, aby przepisać metody z adnotacjami, co jest łatwe. Będziesz musiał również przepisać rozmówcę, co myślę, że będzie znacznie bardziej skomplikowane w cglib. javassist używa bardziej "kodu źródłowego" podejście zorientowane i może być lepiej dostosowane do przepisywania rozmówców.

 1
Author: Ted Bigham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-30 07:41:59

Pomyśl o tym, jak można to zaimplementować prymitywnym typem, powiedzmy int. Java-JVM, a nie tylko język - nie posiada typu "wskaźnik" do zmiennej lokalnej, na ramce (stosie metod) lub stosie operandu. Bez tego nie można naprawdę przejść przez odniesienie.

Inne języki, które obsługują pass-by-reference używają wskaźników(wierzę, choć nie widzę innej możliwości). Odniesienia do C++ (jak int&) są wskaźnikami w ukryciu.

I ' ve thought tworzenia nowego zestawu klas rozszerzających Number, zawierających int, long, itd. ale Nie niezmienne. Może to dać pewien efekt przechodzenia prymitywów przez odniesienie - ale nie będą one automatycznie Pudełkowane, a niektóre inne funkcje mogą nie działać.

Bez wsparcia w JVM, nie możesz mieć prawdziwego odniesienia. Przykro mi, ale tak Rozumiem.

BTW, jest już kilka klas typu referencyjnego (jak byś chciał dla hold ' a). ThreadLocal<> (który ma get() i set()), lub Reference extendery, jak WeakReference (które chyba mają tylko get()).

Edytuj: Po przeczytaniu kilku innych odpowiedzi, sugeruję, że ref być formą auto-Boks. Tak więc:

class ReferenceHolder<T> {
    T referrent;
    static <T> ReferenceHolder<T> valueOf(T object) {
        return new ReferenceHolder<T>(object);
    }
    ReferenceHolder(T object) { referrent = object; }
    T get()            { return referrent; }
    void set(T value)  { referrent = value; }
}

class RefTest {
    static void main() {
        String s = "Hello";
        // This is how it is written...
        change(s);
        // but the compiler converts it to...
        ReferenceHolder<String> $tmp = ReferenceHolder.valueOf(s);
        change($tmp);
        s = $tmp.get();
    }
    // This is how it is written...
    static void change(ref Object s) {
        s = "Goodbye";              // won't work
        s = 17;             // *Potential ClassCastException, but not here*
    }
    // but the compiler converts it tothe compiler treats it as:
    static <T> void change(ReferenceHolder<T> obj) {
        obj.set((T) "Goodbye");     // this works
        obj.set((T) 17);    // *Compiler can't really catch this*
    }
}

Ale zobacz, gdzie jest potencjał, aby umieścić niewłaściwy typ w ReferenceHolder? Jeśli poprawnie wygenerowany, kompilator może być w stanie czasami ostrzec, ale ponieważ prawdopodobnie chcesz, aby nowy kod przypominał normalny kod jak najbardziej, istnieje możliwość ccex z każdym auto-ref sprawdzam.

 1
Author: Menachem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-30 18:33:35

Odpowiadając na pytanie, jak rozszerzyć język mój wybór będzie: - Korzystanie z różnych technik uchwytów, jak opisuje kilka innych odpowiedzi - Użyj adnotacji, aby dołączyć metadane dotyczące argumentów, które powinny być przekazywane przez odniesienie, a następnie zacznij żonglować z biblioteką manipulacji kodem bajtowym, jak cglib, aby zrealizować swoje pomysły w samym kodzie bajtowym.

Choć cały ten pomysł wydaje się dziwny.

 0
Author: aljipa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-26 16:22:10

Istnieje kilka sposobów na pisanie kodu Java jako skutecznego odniesienia, nawet w ramach standardowych konwencji pass-by-value.

Jednym z podejść jest użycie instancji lub zmiennych statycznych, których zakres obejmuje określoną metodę, zamiast jawnych parametrów. Zmienne, które są modyfikowane, mogą być zawarte w komentarzach, jeśli naprawdę chcesz wymienić ich nazwy na początku metody.

Wadą takiego podejścia jest to, że zakres tych zmienne muszą obejmować całą daną klasę, A nie tylko metodę. Jeśli chcesz dokładniej ograniczyć zakres zmiennych, zawsze możesz je zmodyfikować za pomocą metod getter i setter, a nie jako parametrów.

Pracując zarówno z Javą, jak i C / C++, nie sądzę, aby przypuszczalna nieelastyczność Javy była tylko wartością przekazywaną - dla wszystkich programistów, którzy wiedzą, co się dzieje ze zmiennymi, istnieją rozsądne obejścia, które mogą wykonaj te same rzeczy funkcjonalnie.

 0
Author: La-comadreja,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-27 16:47:31

Java jest (w rzeczywistości) przekazywana przez odniesienie. Podczas wywoływania metody przekazywane jest odniesienie (wskaźnik) do obiektu, a podczas modyfikowania obiektu można zobaczyć modyfikację po powrocie z metody. Problem z twoim przykładem polega na tym, że java.lang.Ciąg jest niezmienny.

I to, co osiągasz w swoim przykładzie, to parametry wyjściowe.

Oto nieco inna wersja Jeffreya Hantina:

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

public static void passByReference(StringBuilder someString) {
  String nr = "'new String reference'";
  someString.replace(0, nr.length() - 1, nr);
}
 -1
Author: TomWolk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-30 05:59:29