java - que - Establecer la variable externa desde la clase interna anónima
que clases internas no llevan nombre (8)
¿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 compila, porque pide que el result
sea final, si lo es, no se compila porque intento 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()
?
A partir de Hibernate 4, el método Session#doReturningWork(ReturningWork<T> 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)
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<T> {
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;
}
}
Java no sabe que doWork va a ser sincrónico y que el marco de pila en el que se encuentra ese resultado seguirá allí. Debes modificar algo que no está en la pila.
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];
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<V> (for your POJOs)
etc.
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<Long>() {
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> {
T execute(Connection connnection) throws SQLException;
}
public class SessionWrapper {
private final Session session;
public SessionWrapper(Session session) {
session = nonnull(session);
}
public <T> T withConnection(final WithConnection<T> 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;
}
}
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 pila contenga la variable ¿se ha ido? Los lenguajes con cierres tienen que introducir un mecanismo para mantener estas variables vivas fuera de su alcance 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];
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.