有没有办法从Java中的匿名内部类访问调用者范围的变量?
以下是了解我需要的示例代码:
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;
}
代码位于DAO服务类中。显然它不会编译,因为它要求result
是最终的,如果是 - 它不会编译,因为我尝试修改最终的var。我被JDK5绑定了。除了完全放弃doWork()
之外,有没有办法在doWork()
内设置结果值?
答案 0 :(得分:62)
Java不知道doWork将是同步的,并且结果所在的堆栈帧仍然存在。你需要改变堆栈中没有的东西。
我认为这会起作用
final Long[] result = new Long[1];
然后
result[0] = st.getLong(4);
execute()
中的。最后,您需要return result[0];
答案 1 :(得分:15)
这种情况在Java中出现很多,处理它的最简单方法是使用简单的值容器类。它与阵列方法的类型相同,但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;
}
}
答案 2 :(得分:8)
如果包含的类是MyClass - &gt;
MyClass.this.variable = value;
不记得这是否适用于私有变量(我认为它会起作用)。
仅适用于类的属性(类变量)。不适用于方法局部变量。在JSE 7中,可能会有闭包来做这种事情。
答案 3 :(得分:8)
龙是不变的。如果使用可变类,持有long值,则可以更改该值。例如:
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();
}
}
答案 4 :(得分:5)
最简单(也是最干净)的方法是使用自Java 1.5以来可用的AtomicLong
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;
}
java.util.concurrent.atomic
包中还有其他AtomicXXX变体:AtomicInteger
,AtomicBoolean
,AtomicReference<V> (for your POJOs)
e.t.c
答案 5 :(得分:2)
匿名类/方法不是闭包 - 这正是区别。
问题是,doWork()
可以创建一个新线程来调用execute()
,而getNumber()
可能会在设置结果之前返回 - 甚至更有问题:应该在哪里execute()
当包含变量的堆栈帧消失时写入结果?带闭包的语言必须引入一种机制来使这些变量保持在原始范围之外(或确保闭包不在单独的线程中执行)。
解决方法:
Long[] result = new Long[1];
...
result[0] = st.getLong(4) ;
...
return result[0];
答案 6 :(得分:2)
此标准解决方案是返回一个值。例如,请参阅java.security.AccessController.doPrivileged
。
所以代码看起来像这样:
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);
}
}
(还修复了潜在的资源泄漏,并针对任何错误返回null
。)
更新:显然Work
来自第三方库,无法更改。所以我建议不要使用它,至少要隔离你的应用程序,以免你直接使用它。类似的东西:
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;
}
}
答案 7 :(得分:1)
从Hibernate 4开始,方法Session#doReturningWork(ReturningWork<T> work)
将从内部方法返回返回值:
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;
}
(使用Java 8 lambda清理)
答案 8 :(得分:0)
在类似的情况下,使用AtomicLong可以帮助我,并且代码看起来很干净。
// Create a new final AtomicLong variable with the initial value 0.
final AtomicLong YOUR_VARIABLE = new AtomicLong(0);
...
// set long value to the variable within inner class
YOUR_VARIABLE.set(LONG_VALUE);
...
// get the value even outside the inner class
YOUR_VARIABLE.get();