Установка внешней переменной из анонимного внутреннего класса - PullRequest
51 голосов
/ 12 мая 2011

Есть ли способ получить доступ к переменным в вызывающей области из анонимного внутреннего класса в 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()?

Ответы [ 8 ]

62 голосов
/ 12 мая 2011

Java не знает, что doWork будет синхронным и что кадр стека, в котором находится результат, все равно будет там.Вам нужно изменить что-то, чего нет в стеке.

Я думаю, это сработает

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

, а затем

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

в execute().В конце вам нужно return result[0];

15 голосов
/ 01 февраля 2014

Эта ситуация часто возникает в 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;
    }
}
8 голосов
/ 12 мая 2011

Длинна неизменна. Если вы используете изменяемый класс с длинным значением, вы можете изменить это значение. Например:

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

}
8 голосов
/ 12 мая 2011

Если содержащим классом является MyClass ->

MyClass.this.variable = value;

Не помню, будет ли это работать с закрытой переменной (я думаю, что это будет работать).

Работает только для атрибутовкласса (переменная класса).Не работает для локальных переменных метода.В JSE 7, вероятно, будут замыкания для подобных вещей.

5 голосов
/ 01 марта 2018

Самый простой (и самый чистый) способ сделать это - использовать AtomicLong, доступный с версии 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;
}

В пакете java.util.concurrent.atomic доступны и другие варианты AtomicXXX: AtomicInteger, AtomicBoolean, AtomicReference<V> (for your POJOs) e.t.c

2 голосов
/ 12 мая 2011

Стандартное решение для этого - вернуть значение.См., Например, 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);
    }
}

(Также исправлена ​​потенциальная утечка ресурсов, и для любого возврата возвращается 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;
    }
}
2 голосов
/ 12 мая 2011

Анонимные классы / методы не являются замыканиями - в этом и заключается разница.

Проблема в том, что doWork() может создать новый поток для вызова execute(), а getNumber() может вернуться до того, как будет установлен результат - и еще более проблематично: куда execute() записать результат, когда кадр стека содержит переменную ушел? Языки с замыканиями должны вводить механизм, позволяющий поддерживать такие переменные вне их первоначальной области видимости (или обеспечивать, чтобы замыкание не выполнялось в отдельном потоке).

Обходной путь:

Long[] result = new Long[1];
...
result[0] = st.getLong(4) ;
...
return result[0];
1 голос
/ 20 декабря 2016

Начиная с 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)

...