Подтвердить что ты не робот

Как вы можете расширить Java, чтобы внедрить передачу по ссылке?

Java - это пропускная способность. Как вы можете изменить язык, чтобы ввести передачу по ссылке (или какое-то эквивалентное поведение)?

Возьмем, например, что-то вроде

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

который (без ref) компилируется в следующий 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

Код в 3: загружает ссылку в стек из переменной variable.

Одна из возможностей, которую я рассматриваю, заключается в том, чтобы определить, что метод передается по ссылке, возможно с помощью ref, и изменить метод для принятия объекта Holder, который хранит ту же ссылку, что и наша переменная. Когда метод завершается и, возможно, изменяет эту ссылку в держателе, переменная со значения стороны вызывающего абонента заменяется опорным значением держателя.

Он должен скомпилировать эквивалент этого

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 может быть что-то вроде

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

Где это может произойти или как вы можете его улучшить?

4b9b3361

Ответ 1

Чтобы ответить на ваш вопрос:

Где это может произойти?

  • Конечные переменные и константы перечисления
  • Специальные ссылки, такие как this
  • Ссылки, возвращаемые из вызовов методов, или построенные встроенные с помощью new
  • Литералы (строки, целые числа и т.д.)

... и, возможно, другие. В принципе, ваше ключевое слово ref должно использоваться только в том случае, если источник параметра является полем без конечных значений или локальной переменной. Любой другой источник должен генерировать ошибку компиляции при использовании с ref.

Пример (1):

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

Пример (2):

passByReference(ref this);  // Definitely impossible

Пример (3):

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

Пример (4):

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

И тогда есть выражения присваивания, которые кажутся мне чем-то вроде суждения:

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

Также немного странно, что для вашего синтаксиса требуется ключевое слово ref для определения метода и вызова метода. Я думаю, что определение метода было бы достаточно.

Ответ 2

Обычная идиома, которую я видел для передачи по ссылке в Java, - это передать одноэлементный массив, который сохранит безопасность во время выполнения (в отличие от генериков, которые подвергаются стиранию), и избегает необходимости вводить новый класс.

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

Ответ 3

Ваша попытка изменить язык игнорирует тот факт, что эта "функция" явно была исключена, чтобы предотвратить появление известных ошибок побочного эффекта. Java рекомендует делать то, что вы пытаетесь архивировать, используя классы хранения данных:

public class Holder<T> {
  protected T value;

  public T getValue() {
    return value;
  }

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

В потокобезопасной версии будет AtomicReference.

Теперь сохранение одной строки в классе кажется чрезмерным, и, скорее всего, это так, но обычно у вас есть класс хранения данных для нескольких связанных значений вместо одной строки.

Большим преимуществом такого подхода является то, что внутри метода очень явный. Поэтому, даже если вы программируете в понедельник утром после насыщенного выходного дня, и кофеварка просто сломалась, вы все равно можете легко сказать, что делает код (KISS), предотвращая появление нескольких ошибок даже в первую очередь, только потому, что вы забыли об этой одной из функций метода foo.

Если вы думаете о том, что ваш подход может сделать, что версия держателя данных не может, вы скоро поймете, что реализуете что-то только потому, что оно другое, но фактически оно не имеет реальной ценности.

Ответ 4

Использование класса AtomicReference в качестве объекта-владельца.

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

Ответ 5

Как ни странно, я недавно думал об этой проблеме. Я рассматривал вопрос о том, может ли быть интересно создать диалект VB, который работал на JVM - я решил, что этого не будет.

Во всяком случае, есть два основных случая, когда это может быть полезно и четко определено:

  • локальные переменные
  • атрибуты объекта

Я предполагаю, что вы пишете новый компилятор (или адаптируете существующий) для вашего нового диалекта Java.

Локальные переменные обычно обрабатываются кодом, аналогичным тому, что вы предлагаете. Я больше всего знаком с Scala, который не поддерживает pass-by-reference, но поддерживает закрытие, которое имеет те же проблемы. В Scala существует класс scala.runtime.ObjectRef, который напоминает ваш класс Holder. Существуют также аналогичные классы {...}Ref для примитивов, переменных переменных и т.д.

Если компилятору необходимо создать закрытие, которое обновляет локальную переменную, оно "обновляет" переменную до final ObjectRef (которая может быть передана закрытию в ее конструкторе) и заменяет использование этой переменной на get и обновления set s, на ObjectRef. В вашем компиляторе вы можете обновлять локальные переменные всякий раз, когда они передаются по ссылке.

Вы можете использовать подобный трюк с атрибутами объекта. Предположим, что Holder реализует интерфейс ByRef. Когда ваш компилятор видит атрибут объекта, передаваемый по ссылке, он может создать анонимный подкласс ByRef, который считывает и обновляет атрибут объекта в его методах get и set. Опять же, Scala делает что-то похожее на это для лениво оцениваемых параметров (например, ссылок, но только для чтения).

Для дополнительных точек коричневого цвета вы можете расширить технику до свойств JavaBean и даже элементов Map, List и Array.

Один из побочных эффектов этого заключается в том, что на уровне JVM ваши методы имеют неожиданные подписи. Если вы скомпилируете метод с сигнатурой void doIt(ref String), на уровне байт-кода вы получите подпись void doIt(ByRef) (вы можете ожидать, что это будет что-то вроде void doIt(ByRef<String>), но, разумеется, дженерики используют стирание типа). Это может вызвать проблемы с перегрузкой метода, так как все параметры by-ref скомпилируются с одной и той же сигнатурой.

Возможно, это можно сделать с помощью манипуляции с байт-кодом, но есть ошибки, например, тот факт, что JVM разрешает приложениям повторно использовать локальные переменные - поэтому на уровне байт-кода может быть неясно, является ли параметр повторно назначено или его слот повторно используется, если приложение было скомпилировано без отладки символов. Кроме того, компилятор может выполнить команды aload, если нет возможности изменить значение во внешнем методе - если вы не предпримете шаги, чтобы избежать этого, изменения в ссылочной переменной могут не отразиться на внешнем методе.

Ответ 6

Подумайте, как это можно реализовать с помощью примитивного типа, скажем int. Java - JVM, а не только язык - не имеет никакого типа "указатель" для локальной переменной, для фрейма (стека методов) или стека операндов. Без этого невозможно действительно пройти по ссылке.

Другие языки, поддерживающие указатели использования пошаговой ссылки (я считаю, хотя я не вижу других возможностей). Ссылки на С++ (например, int&) являются скрытыми указателями.

Я думал о создании нового набора классов, расширяющих Number, содержащих int, long и т.д., но не неизменяемых. Это может привести к некоторому эффекту передачи примитивов по ссылке - но они не будут автоматически загружаться, а некоторые другие функции могут не работать.

Без поддержки в JVM у вас не может быть реальной передачи по ссылке. Извините, но это мое понимание.

Кстати, уже есть несколько классов типа Reference (например, для Holder). ThreadLocal<> (который имеет get() и set()) или расширения Reference, такие как WeakReference (который, как я думаю, имеет только get()).

Изменить: Прочитав некоторые другие ответы, я предлагаю, чтобы ref был формой автоматического бокса. Таким образом:

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*
    }
}

Но посмотрите, где есть потенциал для размещения неправильного типа типа в ReferenceHolder? Если генератор правильно генерируется, компилятор может иногда иногда предупреждать, но, как вы, скорее всего, хотите, чтобы новый код был как можно больше похож на обычный код, есть возможность CCEx с каждым вызовом автообновления.

Ответ 7

Я думаю, что вы можете выполнить большую часть того, чего хотите, создав агента и используя cglib.

Многие из приведенных здесь примеров могут работать. Я бы рекомендовал использовать предложенный вами шаблон, потому что он будет компилироваться с обычным компилятором.

public void doSomething(@Ref String var)

Затем за кулисами вы используете cglib для перезаписи аннотированных методов, что легко. Вам также придется переписать вызывающего, который, по моему мнению, будет намного сложнее в cglib. javassist использует больше ориентированного на "исходный код" подход и может быть лучше подходит для перезаписи вызывающих абонентов.

Ответ 8

Отвечая на вопрос о том, как расширить язык, мой выбор: - Используя различные держатели, как несколько других ответов, - Использовать аннотации для прикрепления метаданных о том, какие аргументы должны быть переданы по ссылке, а затем начать жонглирование библиотекой манипулирования байтом, например cglib, чтобы для выполнения ваших идей в самом байтовом коде.

Хотя вся эта идея кажется странной.

Ответ 9

Существует несколько способов написания кода Java как эффективно передаваемого по ссылке, даже в стандартных условных условных соглашениях.

Один из подходов состоит в том, чтобы использовать экземпляры или статические переменные, область видимости которых включает конкретный метод, вместо явных параметров. Переменные, которые изменяются, могут быть включены в комментарии, если вы действительно хотите указать их имена в начале метода.

Недостатком такого подхода является то, что область этих переменных должна охватывать весь рассматриваемый класс, а не только метод. Если вы хотите более точно ограничить области переменных, вы всегда можете изменить их с помощью методов getter и setter, а не как параметров.

Сработав как с Java, так и с C/С++, я не думаю, что Java-предполагаемая негибкость в передаче по значению - это просто грандиозное решение - для любых программистов, которые знают, что происходит с переменными, существуют разумные обходные пути которые могут выполнять те же функции функционально.

Ответ 10

Java (фактически) проходит по ссылке. Когда метод вызывается, ссылка (указатель) на объект передается, и когда вы изменяете объект, вы можете увидеть модификацию при возврате из метода. Проблема с вашим примером заключается в том, что java.lang.String является неизменным.

И то, что вы на самом деле достигаете с вашим примером, - это выходные параметры.

Вот немного другая версия Джеффри Хантина:

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