Modificador de acceso private en Groovy, no muy privado.

Hoy me encontré con algo curioso en Groovy, a ver si alguien sabe la razón del hallazgo.

Se supone que al hacer una declaración en Groovy sin indicar modificador de acceso, éste crea una property, y que cuando indicamos un modificador de acceso Groovy será field.

Pues bien, en el primer caso todo funciona perfecto, los getters y setters son agregados por Groovy y accesibles desde dentro o fuera de la clase.

El problema que he encontrado es que al marcar un atributo privado, éste efectivamente no se convierte en property, pero sigue siendo visible desde fuera de la clase.

Por ejemplo en este script:

class Saluda{
  private String nombre

  Saluda(String aQuien){
    nombre = aQuien[0].toUpperCase() + aQuien[1..-1]
  }
  def salute() { println "Hola $nombre!" }
}

s = new Saluda('mundo')
s.salute()
println s.nombre

El resultado imprime también el atributo nombre marcado como privado:

WTF!!??

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

Raro

Pues sí está raro... si le quitas el modificador a la variable y lo compilas, la clase compilada trae los métodos getNombre y setNombre, pero con el modificador no trae esos métodos. Es la única diferencia entre las clases generadas (con private o sin private). La variable sí se declara como privada (esto lo vi con javap).

groovyc Raro.groovy
javap -private Saluda
public class Saluda extends java.lang.Object implements groovy.lang.GroovyObject{
    private java.lang.String nombre;
...

De modo que no es porque se implemente de todas maneras un getter... esto lo probé en Groovy 1.7.6 y 1.8.0 y en ambas versiones el comportamiento es el mismo. Esto no concuerda con lo que indica la documentación.

Y se pone peor... le quité el modificador a la variable y le puse getter y setter privados, y aún así jala... la clase compilada tiene unos métodos medio raros que seguramente son los que se usan en runtime y le dan la vuelta a la protección:

public class Saluda extends java.lang.Object implements groovy.lang.GroovyObject{
    private java.lang.String nombre;
...
    private void setNombre(java.lang.String);
    private java.lang.String getNombre();
...
    public void this$2$setNombre(java.lang.String);
    public java.lang.String this$2$getNombre();
...
}

A ver si algún groovero experimentado nos puede resolver esta duda.

Imagen de ezamudio

Bug...

Parece que este problema viene desde la primera versión, ya está bien documentado y se ha escrito al respecto y parece que no lo van a arreglar.

Por lo que se es que en

Por lo que se es que en realidad está invocando el getter de la propiedad privada, en la platica de las Open Talks se dijo que no necesitabas escribir los metodos de acceso

Imagen de benek

Closures

Estoy leyendo en el enlace que puso @ezamudio que lo han dejado así para garantizar la compilación de los closures, ya que éstos podrían necesitar un atributo que marcado realmente como privado no podrían acceder.

También estoy leyendo aquí que se planea darle solución en la versión 2.0.

Leí a un tipo que dijo que existía un workaround o quick fix, pero no vi que lo pusiera.

ah jijos, ya entendi cual es

ah jijos, ya entendi cual es el verdadero problema y claro es de preocuparse porque resulta que en groovy todo resulta ser todo menos privado... bueno hay que ver qe sucede cuando las clases estan en diferentes archivos

Es decir si en clase A.groovy se define un metodo privado y se intenta aceder desde B.groovy

....

Ya lo hice y no, tampoco funciona. Explico lo que intentaba descubrir:
Pensé que al estar varias clases en el mismo archivo funconarian como clases nternas de una Case (la clase principal seria el archivo) y que en vez de usar private pensaba que pudiera ponerlas protected acesibles para si mismas. Enotnces siguindo esto, quise verificar si entonces sucedia el error de acceso a metodos privados desde una clase (en otro archivo)

Imagen de ezamudio

no son internas

En Groovy puedes declarar varias clases en un archivo y no son internas, ambas quedan como publicas y además el archivo no tiene que llamarse igual que las clases, puedes tener un Uno.groovy que contenga simplemente class Dos {} y eso compila bien, te deja un Dos.class.

La bronca es que a la clase compilada, Groovy le agrega algo cuando tienes métodos privados. Por ejemplo a esta clase:

class A {
  void publico() { println "Publico" }
  private void privado() { println "Metodo privado" }
  private static void privadoEstatico() { println "Metodo privado estatico" }
}

Si ves el código compilado (ejecutando javap -private A) verás esto (lo abrevio porque salen un montón de métodos que agrega Groovy):

public class A extends java.lang.Object implements groovy.lang.GroovyObject{
...
    public A();
    public void publico();
    private void privado();
    private static void privadoEstatico();
    public void this$2$privado();
...
}

Lo raro es que no veo algo similar para privadoEstatico. Pero desde una clase B puedo invocar A.privadoEstatico() y también new A().privado() sin problemas. Si hago A en Java:

public class A {
  public void publico() { System.out.println("Metodo publico"); }
  private void privado() { System.out.println("Metodo privado"); }
  private static void privadoEstatico() { System.out.println("Metodo privado estatico"); }
}

Mi clase B en groovy sigue siendo capaz de invocar los métodos privados.

Pero... si hago una clase B en Java que intente invocar los métodos privados usando Reflection:

import java.lang.reflect.*;

public class B {
  public void prueba() throws Exception {
    A a = new A();
    a.publico();
    for (Method m : A.class.getDeclaredMethods()) {
      System.out.printf("Invocando %s%n", m);
      if (m.getName().startsWith("publico") || m.getName().startsWith("privado")) {
        m.invoke(a);
      }
    }
  }

  public static void main(String[] args) throws Exception {
    new B().prueba();
  }
}

Esa clase la puedo compilar teniendo A.class de Groovy o de Java, no importa. La cosa es que si corro B usando la versión Java de la clase A, truena al querer invocar privado(). Y al compilar la versión Groovy de A, también truena al querer invocar ese mismo método. O sea, que la bronca no reside en cómo Groovy compila los métodos, porque finalmente los métodos que declaramos como private sí los hace privados; la bronca es que de alguna forma en el runtime se puede saltar esa protección, seguramente invoca un método público de la clase que internamente puede ya invocar el método privado.

Imagen de Miguel-1.mx

Colateral por diseño

La gracia de poder compilar sin comprobar que esté en método en tiempo de compilación provocó, entre algunas otras gracias, esta curiosidad. Ha sido tema de muchas charlas. En la tercera emisión de las Opentalks, el Ing. Andrés Almiray mencionó esta particularidad con una muy preciosa frase:

«Groovy can see your privates»

No creo que lo vayan a modificar para Groovy 2, pues bastantes cosas (incluso Hacks) dependen de esta "característica". Para mí es un punto a favor para usar Groovy, pero estoy todo menos autorizado para hablar de programación; que los de sistemas respondan:

https://jira.codehaus.org/browse/GROOVY-1875

Entonces entiendo que el

Entonces entiendo que el runtime de Groovy se lo salta ( o más bien, sabe como invocarlo) .

Estaba revisando brevemente el código generado para este ejemplo:

class A {
   private  def a = "Hola";
   public def e = { System.out.prinltn( a )}
}

Y la línea donde se invoca al atributo "a" es esta:

   25:  invokeinterface #41,  2; //InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callGroovyObjectGetProperty:(Ljava/lang/Object;)Ljava/lang/Object;

Que bueeeno no es así muy revelador ni nada, simplemente llama a

   CallSiete.callGroovyObjectGetProperty( object )

Es decir, llama dinámicamente el valor de ese atributo y ahí es donde puede tener la oportunidad de saltarselo.

Lo curioso de esto es que en Java se puede hacer casi lo mismo!, bueno con su asegunes.

En Java ya existen closures desde la version 1.2 en forma de clases anónimas y el mecanismo para acceder a miembros privados de otras clases es muy similar. Como tales, estas clases no deben de poder acceder a los atributos ( o métodos ) privados de su clase externa, pero el compilador de Java provee un mecanismo para lograrlo.

A continuación un ejemplo un poco largo pero que demuestra como una clase B puede acceder el valor privado de una clase A que tenga definido un closure ( a.k.a. clase anonima ).

Notesé que el ejemplo es simplemente con fines didácticos y no estoy implicando en lo absoluto que es una forma de obtener acceso un atributo privado.

Ahi va:

C:\>more > A.java
class A {
   private String a = "private";
}
^C
C:\>javac A.java

C:\>javap A
Compiled from "A.java"
class A extends java.lang.Object{
    A();
}

C:\>:: Si agregamos una clase anonima se genera un nuevo método por el compilador

C:\>more > A.java
class A {
   private String a = "private v2";
   private Runnable r = new Runnable() {
        public void run() {
          System.out.println( a );
        }
   };
}
^C
C:\>javac A.java

C:\>javap A
Compiled from "A.java"
class A extends java.lang.Object{
    A();
    static java.lang.String access$000(A);
}
C:\>:: El método de clase "access$000" es creado por el compilador, pero no puede ser usado por otra clase
C:\>more > B.java
class B {
    String v = A.access$000( new A() );
}
^C
C:\>javac B.java
B.java:2: cannot find symbol
symbol  : method access$000(A)
location: class A
    String v = A.access$000( new A() );
                ^
1 error

C:\>:: No puede encontrar el método porque fue creado por el compilador.
C:\>:: Si intentamos declarar el método nosotros mismo el compilador se queja:
C:\>more > A.java
class A {
   private String a = "private v3";
   private Runnable r = new Runnable() {
      public void run() {
          System.out.println( a );
      }
   };
   public static String access$000( A otro ) { return otro.a; }
}
^C
C:\>javac A.java
A.java:5: the symbol access$000(A) conflicts with a compiler-synthesized symbol in A
          System.out.println( a );
                              ^
1 error

C:\>::Pero si quitamos el código del closure ( la clase anonima ) entonces ese método ya puede ser declarado
C:\>more > A.java
class A {
    private String a = "private v4";
    public static String access$000( A otro ) { return otro.a; }
}
^C
C:\>javac A.java
C:\>:: Y la clase b ya puede ser compilada
C:\>type B.java
class B {
    String v = A.access$000( new A() );
}
C:\>javac B.java
C:\>:: Pero claro compila porque tenemos ese metodo declarado. Ahora si lo quitamos, ponemos de nuevo el clousure podremos tener aun el acceso
C:\>::Vamos a crear otra versión de B que imprima el valor
C:\>more > B.java
class B {
   public static void main( String ... args ) {
       System.out.println( A.access$000( new A() ));
   }
}
^C
C:\>javac B.java
C:\>java B
private v4
C:\>:: Entonces, B ya funciona, vamos a quitar el método y dejar que el compilador lo genere
C:\>more > A.java
class A {
   private String a = "no que era privado?";
   private Runnable r = new Runnable() {
      public void run() {
         System.out.println( a );
      }
   };
}
^C
C:\>javac A.java

C:\>javap A
Compiled from "A.java"
class A extends java.lang.Object{
    A();
    static java.lang.String access$000(A);
}

C:\>:: access$000 generado por el compilador
C:\>java B
no que era privado?

C:\>:: B todavía puede accederlo.

Imagen de bferro

No estás usando en B la variable privada de A directamente

Lo que has hecho claro que se puede hacer. En el main de la clase B estás usando una función estática de A que tiene todo el derecho de crear un objeto de su clase y hacer visible sus intimidades. ¿O me equivoqué leyendo tu código?
By the way. en Java no hay closures. No conviene "propagar" ese misconception.

Lo de la "mentira piadosa" de los privates en Groovy no solamente ocurre en Groovy. Varios lenguajes que promueven el uso de propiedades por encima de los fields lo hacen.

Imagen de ezamudio

clases anónimas != closures

De acuerdo. Las clases anónimas son muy distintas de un closure.

Sí el punto es que de esta

Sí, el punto es que de esta clase A:

class A {
   private String a = "no que era privado?";
   private Runnable r = new Runnable() {
      public void run() {
         System.out.println( a );
      }
   };
}

Que no expone de ninguna forma el atributo privado "a", la clase B puede imprimir su valor y no lo hace de ningún método expuesto por la clase A tampoco, sino por un artificio de la implementación que se describe en el ejemplo ( el método access$000 no es definido por la clase A ) el compilador lo pone ahí.

La versión 1.0 de Java no tenía clases anónimas, estas fueron introducidas en la versión 1.1 precisamente para cubrir la funcionalidad de los closures, pero en ese momento se consideró que la sintaxis podía parecer muy extraña ref y se optó por la forma de clases anónimas. Por desgracia se requirió que las variables locales fueran marcadas como final ref

La gran diferencia entre un Closure y una función anónima ( y de una funcion referenciable como en C ) es que el primero retiene el entorno en el que fue definido y los otros no.

Concuerdo, las clases anónimas en Java NO SON closures ( son clases anónimas ). Los bloques en Smalltalk NO SON closures, son "bloques de código". Pero las clases anónimas no son muy distintas a los closures, de hecho es lo más parecido que existe en el mundo Java, al menos hasta que salga la v1.8.

Imagen de bferro

Con tu artificio sí haces que la clase A exponga ese método

@Oscar
Lo que explicaste con el ejemplo está bien, pero al usar ese artificio sí estás diciéndole al que diseña la clase B que existe ese método en la clase A. Como bien dices, de otra manera B no compilaría.
Lo que quise enfatizar es que la clase B la está diseñando el mismo que la clase A y aún así para tener acceso a lo privado de A tiene que usar ese método estático, que al ser de implementación nadie asegura que mañana se va a llamar igual con una nueva versión del compilador.
Pero bueno, nos entendemos perfectamente y como bien sabes, también por reflexión puedes verle los calzones a cualquier objeto. De no ser así no podríamos hacer serialización de objetos.

Yo en lo personal pienso que el concepto de los modificadores de visibilidad es más una herramienta para el diseño correcto y no algo para protegerme totalmente de los intrusos, que quieran destruirme. Una clase bien hecha debe poder usarse consultando su interfaz exclusivamente y tener las cosas necesarias para que pueda extenderse sin necesidad de modificación (open closed principle) y el que la usa, si es bueno debe bastarle eso.
Pero otra vez, entiendo tu intención con el ejemplo, que vale la pena.

Je jej .. si es más como un

Je jej .. si es más como un "hidden feature" de Java o una curiosidad que algo para usar. De hecho NO se puede usar, lo que pongo es para compartir eso que sé :)