Setting outer variable from anonymous inner class

醉酒当歌 提交于 2019-11-26 22:09:53

Java doesn't know that doWork is going to be synchronous and that the stack frame that result is in will still be there. You need to alter something that isn't in the stack.

I think this would work

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

and then

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

in execute(). At the end, you need to return result[0];

This situation arises a lot in Java, and the cleanest way to handle it is with a simple value container class. It's the same type thing as the array approach, but it's cleaner 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;
    }
}

Long is immutable. If you use a mutable class, holding a long value, you can change the value. For example:

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

}
SJuan76

If the containing class is MyClass -->

MyClass.this.variable = value;

Do not remember if this would work with a private variable (I think it would work).

Only works for attributes of the class (class variable). Does not work for method local variables. In JSE 7 probably there will be closures to do that kind of thing.

Your easiest (and cleanest) way to do this is to use AtomicLong available since 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;
}

There are other AtomicXXX variants available in the java.util.concurrent.atomic package: AtomicInteger, AtomicBoolean, AtomicReference<V> (for your POJOs) e.t.c

Anonymous classes/methods are not closures - this is exactly the difference.

The problem is that doWork() could create a new thread to call execute() and getNumber() could return before the result is set - and even more problematically: where should execute() write the result when the stack frame that contains the variable is gone? Languages with closures have to introduce a mechanism to keep such variables alive outside their original scope (or ensure that the closure is not executed in a separate thread).

A workaround:

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

The standard solution to this is to return a value. See, for instance, ye olde java.security.AccessController.doPrivileged.

So the code would look something like this:

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 fixed the potential resource leak, and returning null for any error.)

Update: So apparently Work is from a third-party library and can't be altered. So I suggest not using it, at least isolate your application from so that you are not using it directly. Something like:

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

As of Hibernate 4, the method Session#doReturningWork(ReturningWork<T> work) will return the return val from the inner method:

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

(Cleaned up using a Java 8 lambda)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!