Javaのメソッド内でのクラス定義の使用


105

例:

public class TestClass {

    public static void main(String[] args) {
        TestClass t = new TestClass();
    }

    private static void testMethod() {
        abstract class TestMethod {
            int a;
            int b;
            int c;

            abstract void implementMe();
        }

        class DummyClass extends TestMethod {
            void implementMe() {}
        }

        DummyClass dummy = new DummyClass();
    }
}

上記のコードはJavaでは完全に合法であることがわかりました。以下の質問があります。

  1. メソッド内でクラス定義を使用することの用途は何ですか?
  2. クラスファイルが生成されますか DummyClass
  3. オブジェクト指向の方法でこの概念を想像するのは難しいです。ビヘイビアー内にクラス定義がある。たぶん、誰かが同等の現実世界の例を教えてくれるでしょう。
  4. メソッド内の抽象クラスは、少し奇妙に聞こえます。しかし、許可されたインターフェースはありません。これには理由がありますか?

1
私は同意します、それは信じられないほど乱雑に見えます。同僚が書いたコードを調べたところ、このローカルクラスがメソッド内に見つかりました...このモジュールが完全に汚染されているように感じました。
誰か、どこかで

7
時には、外見ではなく、どこにも必要のないものを隠すことが
重要

回答:


71

これはローカルクラスと呼ばれます。

2は簡単です。はい、クラスファイルが生成されます。

1と3は同じ質問の一種です。ローカルクラスを使用すると、インスタンスを作成したり、実装の詳細を1つのメソッド内で知ったりする必要がありません。

典型的な用途は、いくつかのインターフェースの使い捨て実装を作成することです。たとえば、次のようなものが頻繁に表示されます。

  //within some method
  taskExecutor.execute( new Runnable() {
       public void run() {
            classWithMethodToFire.doSomething( parameter );
       }
  }); 

これらの束を作成して何かをする必要がある場合は、これを

  //within some method
  class myFirstRunnableClass implements Runnable {
       public void run() {
            classWithMethodToFire.doSomething( parameter );
       }
  }
  class mySecondRunnableClass implements Runnable {
       public void run() {
            classWithMethodToFire.doSomethingElse( parameter );
       }
  }
  taskExecutor.execute(new myFirstRunnableClass());
  taskExecutor.execute(new mySecondRunnableClass());

インターフェースに関して:ローカルで定義されたインターフェースがコンパイラーに問題となるような技術的な問題があるかどうかはわかりませんが、そうでない場合でも、値が追加されることはありません。ローカルインターフェースを実装するローカルクラスがメソッドの外部で使用された場合、インターフェースは無意味になります。また、ローカルクラスがメソッド内でのみ使用される場合、インターフェイスとクラスの両方がそのメソッド内に実装されるため、インターフェイス定義は冗長になります。


どのバージョンのJavaローカルクラスが導入されたのですか?
そり2014年

1
内部クラスはJava 1.1で追加されました-ローカルクラスも同様だと思いますが、それに関するドキュメントはありません。
Jacob Mattison、2014年

非匿名のローカルクラスのユースケースとなる可能性があるものについて、より良い例を提供できますか?コードの2番目のブロックは、匿名クラスで書き直すことができます。
Sergey Pauk、2015

1
非匿名ローカルクラスのほとんどの使用は、匿名クラスで実現できます。私は例を具体化しませんでしたが、同じクラス型の複数のインスタンスを作成する必要がある場合は、通常、名前付きローカルクラスを使用します。
Jacob Mattison、2015

1
OPの場合:ローカルクラスは、スレッドが通信する方法を提供することに注意してください。parameter上記は、囲んでいるメソッドで宣言されている可能性があり、両方のスレッドからアクセスできます。
flow2k 2017

15

これらはローカルクラスと呼ばれます。詳細な説明と例については、こちらをご覧ください。この例は、メソッドの外側について知る必要のない特定の実装を返します。


2
素晴らしいリンク(7年以上経っても機能します!)。特に、「メンバークラスと同様に、ローカルクラスは包含インスタンスに関連付けられ、包含クラスのプライベートメンバーを含むすべてのメンバーにアクセスできます。」
flow2k 2017

10
  1. クラスは、メソッドの外から見ることができません(つまり、インスタンス化され、そのメソッドにReflectionなしでアクセスできます)。また、testMethod()で定義されているローカル変数にアクセスできますが、クラス定義の前でもかまいません。

  2. 「そのようなファイルは書き込まれないだろう」と私は実際に考えました。試してみるまで:そうです、そのようなファイルが作成されます!A $ 1B.classのように呼ばれます。Aは外部クラスで、Bはローカルクラスです。

  3. 特にコールバック関数(ボタンがクリックされたときのonClick()などのGUIのイベントハンドラー)では、「匿名クラス」を使用するのが一般的です。ただし、匿名クラスでは不十分な場合があります。特に、クラスにコンストラクタを定義できない場合があります。これらのケースでは、これらのメソッドローカルクラスが適切な代替手段となります。


2
2.ええと、そうでしょう。クラスファイルは、Javaファイル内のネストされたクラス、ローカルクラス、または匿名クラスごとに生成されます。
sepp2k 2010年

2
「2.そのようなファイルは書き込まれません。」 - これは間違っています。これはTestClass$1TestMethodClass.class、内部クラス.classファイルの命名方法に類似したを作成します。
polygenelubricants 2010年

良い答え、2の例外:この場合、匿名クラスが生成されます(この場合は "TestClass $ 1TestMethodClass.class")
スティーブB.

はい、ごめんなさい!数秒前まで気づきませんでした。あなたは生きて学びます:
Chris Lercher 2010年

匿名クラスとローカルクラスの違いを強調するための私の+1、つまりコンストラクタの定義があります。
Matthieu

7

これの本当の目的は、関数呼び出しでインラインでクラスを作成して、関数型言語で書いているふりをしたい人を慰めることです;)


4

本格的な関数の内部クラスと匿名クラス(別名Javaクロージャ)が必要なのは、次の条件が満たされた場合のみです。

  1. インターフェースまたは抽象クラスの実装を提供する必要がある
  2. 関数の呼び出しで定義されたいくつかの最終パラメーターを使用したい
  3. インターフェース呼び出しの実行状態を記録する必要があります。

たとえば、誰かがRunnable実行を開始し、実行が終了したときを記録したいとします。

匿名クラスでは実行できません。内部クラスではこれを実行できます。

ここに私のポイントを実証する例があります

private static void testMethod (
        final Object param1,
        final Object param2
    )
{
    class RunnableWithStartAndEnd extends Runnable{
        Date start;
        Date end;

        public void run () {
            start = new Date( );
            try
            {
                evalParam1( param1 );
                evalParam2( param2 );
                ...
            }
            finally
            {
                end = new Date( );
            }
        }
    }

    final RunnableWithStartAndEnd runnable = new RunnableWithStartAndEnd( );

    final Thread thread = new Thread( runnable );
    thread.start( );
    thread.join( );

    System.out.println( runnable.start );
    System.out.println( runnable.end );
}

ただし、このパターンを使用する前に、プレーンな古いトップレベルクラス、内部クラス、または静的内部クラスがより良い代替手段であるかどうかを評価してください。


#2をかなり乱用して、関数からの戻り値を割り当てています。
Eddie B、

2

(メソッドまたはクラス内で)内部クラスを定義する主な理由は、外側のクラスとメソッドのメンバーと変数のアクセシビリティを処理するためです。内部クラスはプライベートデータメンバーを検索して操作できます。メソッド内であれば、最終的なローカル変数も処理できます。

内部クラスがあると、このクラスが外部からアクセスできないようにするのに役立ちます。これは特に、JS生成コードがJavaで記述され、各ボタンまたはイベントの動作を匿名クラスを作成して定義する必要があるGWTやGXTなどでのUIプログラミングの場合に当てはまります。


1

春に良い例を見つけました。フレームワークは、メソッド内のローカルクラス定義の概念を使用して、さまざまなデータベース操作を統一された方法で処理します。

次のようなコードがあるとします。

JdbcTemplate jdbcOperations = new JdbcTemplate(this.myDataSource);
jdbcOperations.execute("call my_stored_procedure()")
jdbcOperations.query(queryToRun, new MyCustomRowMapper(), withInputParams);
jdbcOperations.update(queryToRun, withInputParams);

まずexecute()の実装を見てみましょう。

    @Override
    public void execute(final String sql) throws DataAccessException {
        if (logger.isDebugEnabled()) {
            logger.debug("Executing SQL statement [" + sql + "]");
        }

        /**
         * Callback to execute the statement.
         (can access method local state like sql input parameter)
         */
        class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
            @Override
            @Nullable
            public Object doInStatement(Statement stmt) throws SQLException {
                stmt.execute(sql);
                return null;
            }
            @Override
            public String getSql() {
                return sql;
            }
        }

        //transforms method input into a functional Object
        execute(new ExecuteStatementCallback());
    }

最後の行に注意してください。Springは、残りのメソッドについてもこの「トリック」を正確に実行します。

//uses local class QueryStatementCallback implements StatementCallback<T>, SqlProvider
jdbcOperations.query(...) 
//uses local class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider
jdbcOperations.update(...)

ローカルクラスの「トリック」により、フレームワークは、StatementCallbackインターフェースを介してこれらのクラスを受け入れる単一のメソッドでこれらのシナリオのすべてを処理できます。この単一のメソッドは、アクション(実行、更新)とそれらに関する一般的な操作(実行、接続管理、エラー変換、dbmsコンソール出力など)の間のブリッジとして機能します。

public <T> T execute(StatementCallback<T> action) throws DataAccessException    {
        Assert.notNull(action, "Callback object must not be null");

        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        Statement stmt = null;
        try {
            stmt = con.createStatement();
            applyStatementSettings(stmt);
            //
            T result = action.doInStatement(stmt);
            handleWarnings(stmt);
            return result;
        }
        catch (SQLException ex) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            String sql = getSql(action);
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("StatementCallback", sql, ex);
        }
        finally {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.