java 正在从匿名内部类设置外部变量

mzsu5hc0  于 2023-01-07  发布在  Java
关注(0)|答案(9)|浏览(115)

在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是final,如果它是--它没有编译,因为我试图修改final变量。我绑定到JDK 5。除了完全删除doWork()之外,有没有办法从doWork()中设置结果值?

eqqqjvef

eqqqjvef1#

Java并不知道doWork是同步的,也不知道result所在的堆栈帧是否仍然存在,你需要修改一些不在堆栈中的东西。
我想这个会有用的

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

然后

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

execute()中。最后,您需要return result[0];
你可能想创建一个类,因为你不喜欢在这里使用数组的样子,但这是基本的想法。

0tdrvxhp

0tdrvxhp2#

这种情况在Java中经常出现,处理它的最简洁的方法是使用一个简单的值容器类,它与数组方法类型相同,但它更简洁。

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;
    }
}
js81xvg6

js81xvg63#

你需要一个“容器”来保存你的值。但是,你不必创建容器类。你可以使用java.util.concurrent.atomic包中的类。它们为一个值提供了一个不可变的 Package 器以及一个set和一个get方法。你有AtomicIntegerAtomicBooleanAtomicReference<V> (for your objects) e.t.c
在外部方法中:

final AtomicLong resultHolder = new AtomicLong();

在匿名内部类方法中

long result = getMyLongValue();
resultHolder.set(result);

在后面的外部方法中

return resultHolder.get();

这里有一个例子。

public Long getNumber() {
   final AtomicLong resultHolder = new AtomicLong();
   Session session = new Session();
   session.doWork(new Work() {
       public void execute() {
           //Inside anonymous inner class
           long result = getMyLongValue();
           resultHolder.set(result);
       }
   });
   return resultHolder.get(); //Returns the value of result
}
ttp71kqs

ttp71kqs4#

长整型是不可变的。如果使用可变类并保存长整型值,则可以更改该值。例如:

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();
}

}
hs1rzwqc

hs1rzwqc5#

如果包含类是MyClass --〉

MyClass.this.variable = value;

不记得这是否适用于私有变量(我认为它适用)。
只适用于类的属性(类变量)。不适用于方法局部变量。在JSE 7中可能会有闭包来做这类事情。

ghhaqwfi

ghhaqwfi6#

匿名类/方法不是闭包--这正是区别所在。
问题是doWork()可能会创建一个新线程来调用execute(),而getNumber()可能会在结果设置之前返回--问题甚至更严重:当包含变量的堆栈帧消失时,execute()应该把结果写在哪里?使用闭包的语言必须引入一种机制,使这些变量在其原始作用域之外保持活动(或者确保闭包不在单独的线程中执行)。
解决方法:

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

0tdrvxhp7#

这个问题的标准解决方案是返回一个值,例如,参见ye olde 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);
    }
}

(Also修复了潜在的资源泄漏,并为任何错误返回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;
    }
}
gz5pxeao

gz5pxeao8#

从Hibernate 4开始,方法Session#doReturningWork(ReturningWork<T> work)将从内部方法返回return瓦尔:

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进行了清理)

svgewumm

svgewumm9#

使用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();

相关问题