Setting outer variable from anonymous inner class
Asked Answered
L

9

57

Is there any way to access caller-scoped variables from an anonymous inner class in Java?

Here's the sample code to understand what I need:

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

The code is in a DAO service class. Obviously it doesn't compile, because it asks that result be final, if it is -- it doesn't compile because I try to modify a final var. I'm bound to JDK5. Other than dropping the doWork() altogether, is there a way to set the result value from within doWork()?

Lovesome answered 12/5, 2011 at 12:8 Comment(0)
P
67

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

You might want to make a class because you don't like how it looks to use an array here, but this is the basic idea.

Periphrasis answered 12/5, 2011 at 12:19 Comment(6)
Although this will work, it is a bit of an ugly hack. I think you should use a named inner class instead of an anonymous one if you want to return values like that. It is 'cleaner' IMHO.Cuman
This is so dirty, I want to take a shower now, just because I read it :)Nailbiting
I just puked on my keyboard, and then out of laziness for not wanting to create a holder class, I did just this.Figurant
@Nailbiting After taking a shower, following solution worked for me. public static final AtomicLong RESULT = new AtomicLong(0); RESULT.set(st.getLong(4););Eliason
Ok, even IntelliJ suggests this, but it doesn't look like it's the standard solution...Costume
Great answer, even though it's nasty, it explains the reason. Thanks. For example in a unit test, this solution is absolutely ok for me.Melodize
G
17

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;
    }
}
Grano answered 31/1, 2014 at 20:47 Comment(3)
any references? I need to find some documents regarding this approach? is there?Phooey
@Aritra, it would be hard to find references for something like this, but I've been doing java for 14yrs and really when you need to get a return value from a method (aside from it's actual return value) you have to pass an object, and let that object have properties that you can inspect upon the return. My little snippet of code is just a generic way of doing it, and is a little cleaner than using an array for this purpose, or a dedicated class just for getting "an out parameter".Grano
that helped...+1Yachtsman
I
10

You need a 'container' to hold your value. You, however, do not have to create a container class. You may use classes in the java.util.concurrent.atomic package. They provide an immutable wrapper for a value along with a set and a get method. You have AtomicInteger, AtomicBoolean, AtomicReference<V> (for your objects) e.t.c

In the outer method:

final AtomicLong resultHolder = new AtomicLong();

In the anonymous inner class method

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

Later in your outer method

return resultHolder.get();

Here's an example.

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
}
Islek answered 1/3, 2018 at 14:9 Comment(0)
R
9

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

}
Rameau answered 12/5, 2011 at 12:20 Comment(0)
S
8

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.

Surcingle answered 12/5, 2011 at 12:13 Comment(2)
Java 7 won't have closures. And even when they are introduced in Java 8, they probably won't be able to write to local variables (i.e. the "local-variables-that-are-accessed-must-be-final" rule will probably still hold).Misprize
Bit late to comment, but this does seem to work in Java 8Lema
C
3

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];
Croaky answered 12/5, 2011 at 12:21 Comment(0)
C
2

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;
    }
}
Charles answered 12/5, 2011 at 13:41 Comment(2)
Work is an interface in org.hibernate.jdbc, the signature for execute is public void execute(java.sql.Connection). Would it really eat that return type up & work?Lovesome
I'm sorry, but I don't really get the second (updated) part of your answer. I'm having the problem with the inner anonymous class of a listener (and can't make it return something since it's an override). Do you essentially use this? Or is there another concept I need to get my head around? Or should I just ask a completely new question?Costume
C
1

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)

Chymotrypsin answered 19/12, 2016 at 23:25 Comment(0)
P
0

Using AtomicLong helped me in a very similar situation and the code looked clean.

// 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();
Planer answered 11/9, 2020 at 10:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.