Establecer la variable externa desde la clase interna anónima

¿Hay alguna forma de acceder a las variables del ámbito de llamadas desde una clase interna anónima en Java?

Aquí está el código de muestra para entender lo que necesito:

public Long getNumber(final String type, final String refNumber, final Long year) throws ServiceException { Long result = null; try { Session session = PersistenceHelper.getSession(); session.doWork(new Work() { public void execute(Connection conn) throws SQLException { CallableStatement st = conn.prepareCall("{ CALL PACKAGE.procedure(?, ?, ?, ?) }"); st.setString(1, type); st.setString(2, refNumber); st.setLong(3, year); st.registerOutParameter(4, OracleTypes.NUMBER); st.execute(); result = st.getLong(4) ; } }); } catch (Exception e) { log.error(e); } return result; } 

El código está en una clase de servicio DAO. Obviamente no comstack, porque pide que el result sea ​​final, si lo es, no se comstack porque bash modificar una var final. Estoy obligado a JDK5. Además de doWork() completo, ¿hay alguna forma de establecer el valor del resultado dentro de doWork() ?

Java no sabe que doWork va a ser sincrónico y que el marco de stack en el que se encuentra ese resultado seguirá allí. Debes modificar algo que no está en la stack.

Creo que esto funcionaría

  final Long[] result = new Long[1]; 

y entonces

  result[0] = st.getLong(4); 

en execute() . Al final, debe return result[0];

Esta situación surge mucho en Java, y la forma más limpia de manejarlo es con una clase de contenedor de valor simple. Es el mismo tipo de cosa que el enfoque de matriz, pero es más limpio IMO.

 public class ValContainer { private T val; public ValContainer() { } public ValContainer(T v) { this.val = v; } public T getVal() { return val; } public void setVal(T val) { this.val = val; } } 

Long es inmutable. Si usa una clase mutable, manteniendo un valor largo, puede cambiar el valor. Por ejemplo:

 public class Main { public static void main( String[] args ) throws Exception { Main a = new Main(); System.out.println( a.getNumber() ); } public void doWork( Work work ) { work.doWork(); } public Long getNumber() { final LongHolder result = new LongHolder(); doWork( new Work() { public void doWork() { result.value = 1L; } } ); return result.value; } private static class LongHolder { public Long value; } private static abstract class Work { public abstract void doWork(); } } 

Si la clase que contiene es MyClass ->

 MyClass.this.variable = value; 

No recuerdo si esto funcionaría con una variable privada (creo que funcionaría).

Solo funciona para los atributos de la clase (variable de clase). No funciona para las variables locales del método. En JSE 7 probablemente habrá cierres para hacer ese tipo de cosas.

La solución estándar para esto es devolver un valor. Ver, por ejemplo, ye olde java.security.AccessController.doPrivileged .

Entonces el código se vería así:

 public Long getNumber( final String type, final String refNumber, final Long year ) throws ServiceException { try { Session session = PersistenceHelper.getSession(); return session.doWork(new Work() { public Long execute(Connection conn) throws SQLException { CallableStatement st = conn.prepareCall("{ CALL PACKAGE.procedure(?, ?, ?, ?) }"); try { st.setString(1, type); st.setString(2, refNumber); st.setLong(3, year); st.registerOutParameter(4, OracleTypes.NUMBER); st.execute(); return st.getLong(4); } finally { st.close(); } } }); } catch (Exception e) { throw ServiceException(e); } } 

(También se corrigió la posible fuga de recursos y se devuelve null para cualquier error).

Actualización: Aparentemente Work es de una biblioteca de terceros y no se puede modificar. Así que sugiero no usarlo, al menos aislar su aplicación para que no la use directamente. Algo como:

 public interface WithConnection { T execute(Connection connnection) throws SQLException; } public class SessionWrapper { private final Session session; public SessionWrapper(Session session) { session = nonnull(session); } public  T withConnection(final WithConnection task) throws Service Exception { nonnull(task); return new Work() { T result; { session.doWork(this); } public void execute(Connection connection) throws SQLException { result = task.execute(connection); } }.result; } } 

La manera más fácil (y más limpia) de hacer esto es utilizar AtomicLong disponible desde Java 1.5

 public Long getNumber(final String type, final String refNumber, final Long year) throws ServiceException { final AtomicLong result = new AtomicLong; try { Session session = PersistenceHelper.getSession(); session.doWork(new Work() { public void execute(Connection conn) throws SQLException { //... result.set(4); //... } }); } catch (Exception e) { log.error(e); } return result.get; } 

Hay otras variantes de AtomicXXX disponibles en el paquete java.util.concurrent.atomic : AtomicInteger , AtomicBoolean , AtomicReference (for your POJOs) etc.

Las clases / métodos anónimos no son cierres: esta es exactamente la diferencia.

El problema es que doWork() podría crear un nuevo hilo para llamar a execute() y getNumber() podría regresar antes de que se establezca el resultado, e incluso más problemático: dónde debería execute() escribir el resultado cuando el marco de stack contenga la variable ¿se ha ido? Los lenguajes con cierres tienen que introducir un mecanismo para mantener estas variables vivas fuera de su scope original (o asegurarse de que el cierre no se ejecute en un hilo separado).

Una solución:

 Long[] result = new Long[1]; ... result[0] = st.getLong(4) ; ... return result[0]; 

A partir de Hibernate 4, el método Session#doReturningWork(ReturningWork work) devolverá el valor de retorno del método interno:

 public Long getNumber(final String type, final String refNumber, final Long year) throws ServiceException { try { Session session = PersistenceHelper.getSession(); return session.doReturningWork(conn -> { CallableStatement st = conn.prepareCall("{ CALL PACKAGE.procedure(?, ?, ?, ?) }"); st.setString(1, type); st.setString(2, refNumber); st.setLong(3, year); st.registerOutParameter(4, OracleTypes.NUMBER); st.execute(); return st.getLong(4); }); } catch (Exception e) { log.error(e); } return null; } 

(Limpiar usando una lambda Java 8)