从匿名内部类设置外部变量

时间:2011-05-12 12:08:53

标签: java anonymous-class

有没有办法从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()内设置结果值?

9 个答案:

答案 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变体:AtomicIntegerAtomicBooleanAtomicReference<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();