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
が最終的なものであることを要求するため、コンパイルしません-最終的な変数を変更しようとするためコンパイルしません。私はJDK5に縛られています。 doWork()
を完全に削除する以外に、doWork()
内から結果値を設定する方法はありますか?
Javaは、doWorkが同期すること、および結果のスタックフレームがまだ存在することを知りません。スタックにないものを変更する必要があります。
これはうまくいくと思う
_ final Long[] result = new Long[1];
_
その後
_ result[0] = st.getLong(4);
_
execute()
で。最後に、_return result[0];
_する必要があります
この状況は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;
}
}
Longは不変です。長い値を保持する可変クラスを使用する場合、値を変更できます。例えば:
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();
}
}
含むクラスがMyClassの場合->
MyClass.this.variable = value;
これがプライベート変数で機能するかどうか覚えていない(それが機能すると思う)。
クラス(クラス変数)の属性に対してのみ機能します。メソッドのローカル変数では機能しません。 JSE 7では、おそらくそのようなことを行うためのクロージャーが存在するでしょう。
これを行う最も簡単な(そして最もクリーンな)方法は、Java 1.5以降で利用可能なAtomicLong
を使用することです
_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
これに対する標準的な解決策は、値を返すことです。例えば、あなたがたの古いものを見る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;
}
}
匿名クラス/メソッドはクロージャーではありません-これはまさに違いです。
問題は、doWork()
が新しいスレッドを作成してexecute()
を呼び出すことができ、getNumber()
が結果が設定される前に戻る可能性があり、さらに問題がある:変数を含むスタックフレームがなくなったときにexecute()
が結果を書き込む必要があるか?クロージャーを使用する言語では、そのような変数を元のスコープ外で存続させるメカニズムを導入する必要があります(またはクロージャーが別のスレッドで実行されないようにします)。
回避策:
Long[] result = new Long[1];
...
result[0] = st.getLong(4) ;
...
return result[0];
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ラムダ)を使用してクリーンアップしました)