从匿名内部类设置外部variables
有没有办法从Java中的匿名内部类访问调用者范围的variables?
以下是示例代码,了解我需要什么:
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
是最终的,如果是 – 它不会编译,因为我尝试修改最终的variables。 我被绑定到JDK5。 除了完全删除doWork()
,是否有一种方法可以在doWork()
设置结果值?
Java不知道doWork将会是同步的,并且结果所在的堆栈帧将仍然存在。 你需要改变不在堆栈中的东西。
我认为这将工作
final Long[] result = new Long[1];
接着
result[0] = st.getLong(4);
在execute()
。 最后,你需要return result[0];
这种情况在Java中出现了很多,处理它的最简单的方法是使用一个简单的值容器类。 这与arrays方法是一样的,但它更清洁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; } }
长是不变的。 如果你使用一个可变类,保存一个很长的值,你可以改变它的值。 例如:
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(); } }
如果包含的类是MyClass – >
MyClass.this.variable = value;
不记得这是否会与一个私有variables(我认为它会工作)。
只适用于类的属性(类variables)。 不适用于方法局部variables。 在JSE 7中可能会有closures做这样的事情。
标准的解决scheme是返回一个值。 例如,请参阅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
来自第三方库,不能更改。 所以我build议不要使用它,至less隔离你的应用程序,以免直接使用它。 就像是:
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; } }
从Hibernate 4开始, Session#doReturningWork(ReturningWork<T> work)
方法将从内部方法返回返回值val:
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清理)
匿名类/方法不是closures – 这正是差异。
问题是, doWork()
可能会创build一个新的线程来调用execute()
和getNumber()
可能会返回之前的结果设置 – 甚至更有问题:应在哪里execute()
写结果时,包含variables离开了? 有闭包的语言必须引入一个机制来保持这些variables超出其原始范围(或确保闭包不在一个单独的线程中执行)。
解决方法:
Long[] result = new Long[1]; ... result[0] = st.getLong(4) ; ... return result[0];