Установка внешней переменной из анонимного внутреннего класса
Есть ли способ получить доступ к переменным с областью вызова из анонимного внутреннего класса в 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()
?
Ответы
Ответ 1
Java не знает, что doWork будет синхронным и что в стеке, в котором находится результат, все равно будет. Вам нужно изменить что-то, чего нет в стеке.
Я думаю, что это сработает
final Long[] result = new Long[1];
а затем
result[0] = st.getLong(4);
в execute()
. В конце вам нужно return result[0];
Ответ 2
Эта ситуация в 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;
}
}
Ответ 3
Длинные неизменны. Если вы используете изменяемый класс, удерживая длинное значение, вы можете изменить значение. Например:
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
Если класс, содержащий класс MyClass →
MyClass.this.variable = value;
Не помню, будет ли это работать с частной переменной (я думаю, что это сработает).
Работает только для атрибутов класса (переменной класса). Не работает для локальных переменных метода. В JSE 7, вероятно, будут закрытия, чтобы делать такие вещи.
Ответ 5
Стандартное решение для этого - вернуть значение. См. Например, 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;
}
}
Ответ 6
Как и в Hibernate 4, метод Session#doReturningWork(ReturningWork<T> work)
вернет возвращаемый val из внутреннего метода:
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)
Ответ 7
Анонимные классы/методы не являются замыканиями - это как раз разница.
Проблема заключается в том, что doWork()
может создать новый поток для вызова execute()
и getNumber()
может вернуться до того, как будет установлен результат, и еще более проблематично: где execute()
записать результат, когда фрейм стека, который содержит переменную? Языки с закрытием должны ввести механизм для сохранения таких переменных вне их первоначальной области (или убедиться, что закрытие не выполняется в отдельном потоке).
Обходной путь:
Long[] result = new Long[1];
...
result[0] = st.getLong(4) ;
...
return result[0];