valor través referencia por paso pasar pasaje parámetros parametros parametro objetos funciones enviar ejercicios datos como arreglo java pass-by-reference language-design bytecode bytecode-manipulation

java - referencia - paso de parámetros a través de la red



¿Cómo se puede extender Java para introducir pasar por referencia? (10)

Creo que puedes lograr la mayor parte de lo que quieres al crear un agente y usar cglib.

Muchos de los ejemplos dados aquí pueden funcionar. Recomiendo usar la plantilla que propusiste porque se compilará con el compilador normal.

public void doSomething(@Ref String var)

Luego, detrás de escena, utiliza cglib para reescribir los métodos anotados, lo cual es fácil. También tendrás que volver a escribir a la persona que llama, lo que creo que será mucho más complicado en cglib. javassist utiliza un enfoque más orientado al "código fuente", y podría ser más adecuado para reescribir a las personas que llaman.

Java es el paso por valor. ¿Cómo podría modificar el lenguaje para introducir el paso por referencia (o algún comportamiento equivalente)?

Tomemos por ejemplo algo como

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

el cual (sin la ref ) compila al siguiente 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

El código en 3: carga la referencia en la pila desde la variable variable .

Una posibilidad que estoy considerando es hacer que el compilador determine que un método se pase por referencia, posiblemente con ref , y cambie el método para aceptar un objeto Holder que almacena la misma referencia que nuestra variable. Cuando el método se completa, y posiblemente cambia esa referencia en el titular, la variable en el valor del lado que llama se reemplaza con el valor de referencia del titular.

Se debe compilar a un equivalente de este

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

donde Holder podría ser algo así como

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

¿Dónde puede fallar esto o cómo podría mejorarlo?


Curiosamente, he estado pensando en este problema recientemente. Estaba considerando si sería divertido crear un dialecto de VB que se ejecutara en la JVM; decidí que no lo sería.

De todos modos, hay dos casos principales en los que es probable que esto sea útil y bien definido:

  • variables locales
  • atributos de objeto

Supongo que está escribiendo un nuevo compilador (o adaptando uno existente) para su nuevo dialecto de Java.

Las variables locales normalmente se manejan mediante un código similar al que está proponiendo. Estoy más familiarizado con Scala, que no admite el paso por referencia, pero sí admite cierres, que tienen los mismos problemas. En Scala, hay una clase scala.runtime.ObjectRef , que se parece a su clase de Holder . También hay clases similares de {...}Ref para primitivas, variables volátiles y similares.

Si el compilador necesita crear un cierre que actualice una variable local, "actualiza" la variable a un final ObjectRef (que se puede pasar al cierre en su constructor), y reemplaza los usos de esa variable por get sy actualizaciones por set s, en el ObjectRef . En su compilador, podría actualizar las variables locales siempre que se pasen por referencia.

Podrías usar un truco similar con atributos de objeto . Supongamos que Holder implementa una interfaz ByRef . Cuando su compilador ve que se pasa un atributo de objeto por referencia, podría crear una subclase anónima de ByRef que lea y actualice el atributo de objeto en sus métodos de get y set . Una vez más, Scala hace algo similar a esto para parámetros evaluados perezosamente (como referencias, pero de solo lectura).

Para puntos extra de brownie, puede extender la tecnología a las propiedades de JavaBean e incluso a los elementos Map , List y Array .

Un efecto secundario de esto es que, a nivel de JVM, sus métodos tienen firmas inesperadas. Si compila un método con la firma void doIt(ref String) , en el nivel de bytecode, terminará con la firma void doIt(ByRef) (puede esperar que esto sea algo así como void doIt(ByRef<String>) , pero, por supuesto, los genéricos usan borrado de tipo). Esto puede causar problemas con la sobrecarga del método, ya que todos los parámetros de referencia se compilan en la misma firma.

Es posible hacer esto con la manipulación del código de bytes, pero existen inconvenientes, como el hecho de que la JVM permite que las aplicaciones reutilicen las variables locales, por lo que, a nivel del código de bytes, puede no estar claro si se está reasignando un parámetro. , o su ranura reutilizada, si la aplicación fue compilada sin símbolos de depuración. Además, el compilador puede ignorar aload instrucciones si no hay posibilidad de que un valor haya cambiado dentro del método externo; si no toma medidas para evitar esto, los cambios en su variable de referencia pueden no reflejarse en el método externo.


El lenguaje habitual que he visto paso por referencia en Java es pasar una matriz de un solo elemento, que preservará la seguridad de tipos en tiempo de ejecución (a diferencia de los genéricos que se borran) y evitará la necesidad de introducir una nueva clase .

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


Hay varias formas de escribir código Java como paso por referencia, incluso dentro de las convenciones estándar de paso por valor.

Un enfoque es utilizar variables de instancia o estáticas cuyo alcance incluye un método particular, en lugar de parámetros explícitos. Las variables que se están modificando podrían incluirse en los comentarios, si realmente desea mencionar sus nombres al comienzo de un método.

La desventaja de este enfoque es que el alcance de estas variables debería abarcar toda la clase en cuestión, en lugar de solo el método. Si desea restringir los ámbitos de las variables de manera más precisa, siempre puede modificarlos utilizando métodos de obtención y establecimiento en lugar de parámetros.

Después de haber trabajado tanto con Java como con C / C ++, no creo que la supuesta inflexibilidad de Java al ser pasado por valor solo sea importante, para los programadores que saben lo que sucede con las variables, existen soluciones razonables que pueden lograr. Las mismas cosas funcionalmente.


Java es (de hecho) pasar por referencia. Cuando se llama al método, se pasa la referencia (puntero) al objeto y cuando modifica el objeto, puede ver la modificación cuando regresa del método. El problema con tu ejemplo es que java.lang.String es inmutable.

Y lo que realmente está logrando con su ejemplo son los parámetros de salida.

Aquí hay una versión ligeramente diferente de Jeffrey Hantin:

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


Para responder tu pregunta:

¿Dónde puede fallar esto?

  1. Variables finales y constantes de enumeración
  2. Referencias ''especiales'' como this
  3. Referencias que se devuelven a partir de llamadas a métodos o se construyen en línea utilizando new
  4. Literales (cuerdas, enteros, etc.)

... y posiblemente otros. Básicamente, su palabra clave ref solo debe ser utilizable si el origen del parámetro es un campo no final o una variable local. Cualquier otra fuente debe generar un error de compilación cuando se usa con la ref .

Un ejemplo de (1):

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

Un ejemplo de (2):

passByReference(ref this); // Definitely impossible

Un ejemplo de (3):

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

Un ejemplo de (4):

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

Y luego están las expresiones de asignación, que me parecen una especie de juicio:

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

También es un poco extraño que su sintaxis requiera la palabra clave ref tanto para la definición del método como para la invocación del método. Creo que la definición del método sería suficiente.


Piense en cómo podría implementarse con un tipo primitivo, digamos int . Java, la JVM, no solo el lenguaje, no tiene ningún tipo de "puntero" a una variable local, en el marco (pila de método) o la pila de operandos. Sin eso, no es posible pasar realmente por referencia.

Otros idiomas que admiten punteros de uso de paso por referencia (creo, aunque no veo ninguna otra posibilidad). Las referencias de C ++ (como int& ) son punteros disfrazados.

He pensado en crear un nuevo conjunto de clases que extiendan Number , que contengan int , long , etc. pero que no sean inmutables. Esto podría dar algo del efecto de pasar primitivas por referencia, pero no serán encajonadas automáticamente y es posible que algunas otras características no funcionen.

Sin soporte en la JVM, no puede tener un verdadero paso por referencia. Lo siento, pero esa es mi comprensión.

Por cierto, ya hay varias clases de tipo de referencia (como le gustaría para Holder). ThreadLocal<> (que tiene get() y set() ), o los extensores de Reference , como WeakReference (que creo que solo tienen get() ).

Edición: después de leer algunas otras respuestas, sugeriría que ref sea ​​una forma de auto-boxeo. Así:

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

¿Pero ver dónde hay potencial para poner el tipo de tipo incorrecto en el ReferenceHolder ? Si está genérico correctamente, el compilador puede advertir a veces, pero como es probable que desee que el nuevo código se parezca al código normal tanto como sea posible, existe la posibilidad de un CCEx con cada llamada de auto-ref.


Respondiendo a su pregunta sobre cómo extender el lenguaje, mi elección sería: - Usar varias técnicas de titulares como describen otras respuestas. - Usar anotaciones para adjuntar metadatos con respecto a qué argumentos deben pasarse por referencia y luego comenzar a hacer malabares con una biblioteca de manipulación de códigos de bytes, como cglib para cumplir sus ideas en el propio código de bytes.

Aunque toda esta idea parece extraña.


Su intento de modificar el lenguaje ignora el hecho de que esta "característica" se omitió explícitamente para evitar que los errores conocidos de efectos secundarios puedan ocurrir en primer lugar. Java recomienda hacer lo que intenta archivar mediante el uso de clases de titulares de datos:

public class Holder<T> { protected T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }

Una versión segura para subprocesos sería la AtomicReference .

Ahora, almacenar una sola Cadena en una clase parece sobrecargarla y lo más probable es que lo sea, sin embargo, por lo general, tiene una clase de datos para varios valores relacionados en lugar de una sola Cadena.

El gran beneficio de este enfoque es que lo que sucede dentro del método es muy explícito . Así que incluso si está programando un lunes por la mañana después de un fin de semana lleno de actividades y la máquina de café se ha averiado, aún puede decir fácilmente lo que está haciendo el código ( KISS ), evitando que incluso ocurran varios errores, en primer lugar, simplemente porque Olvidé esa característica del método foo.

Si piensa en lo que puede hacer su enfoque que la versión del titular de los datos no puede, pronto se dará cuenta de que está implementando algo solo porque es diferente, pero en realidad no tiene un valor real.


Usando la clase AtomicReference como objeto titular.

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