Verwendung von Klassendefinitionen innerhalb einer Methode in Java

105

Beispiel:

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

Ich fand heraus, dass der obige Code in Java vollkommen legal ist. Ich habe folgende Fragen.

  1. Was nützt es, jemals eine Klassendefinition in einer Methode zu haben?
  2. Wird eine Klassendatei für generiert? DummyClass
  3. Es fällt mir schwer, mir dieses Konzept objektorientiert vorzustellen. Eine Klassendefinition in einem Verhalten haben. Wahrscheinlich kann mir jemand mit gleichwertigen Beispielen aus der realen Welt erzählen.
  4. Abstrakte Klassen innerhalb einer Methode klingen für mich ein bisschen verrückt. Aber keine Schnittstellen erlaubt. Gibt es einen Grund dafür?
Prahler
quelle
1
Ich stimme zu, es sieht unglaublich chaotisch aus. Ich habe einen Code inspiziert, den mein Kollege geschrieben hat, und diese lokale Klasse in einer Methode gefunden. Ich hatte nur das Gefühl, dass dieses Modul völlig verunreinigt war.
Jemand irgendwo
7
Manchmal geht es mehr darum,
Dinge zu

Antworten:

71

Dies wird als lokale Klasse bezeichnet.

2 ist ganz einfach: Ja, es wird eine Klassendatei generiert.

1 und 3 sind die gleiche Frage. Sie würden eine lokale Klasse verwenden, in der Sie niemals eine instanziieren müssen oder die Implementierungsdetails nur in einer Methode kennen.

Eine typische Verwendung wäre das Erstellen einer Wegwerfimplementierung einer Schnittstelle. Zum Beispiel sehen Sie oft so etwas:

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

Wenn Sie eine Reihe davon erstellen und etwas damit tun müssen, können Sie dies in ändern

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

In Bezug auf Schnittstellen: Ich bin nicht sicher, ob es ein technisches Problem gibt, das lokal definierte Schnittstellen zu einem Problem für den Compiler macht, aber selbst wenn dies nicht der Fall ist, würden sie keinen Mehrwert bringen. Wenn eine lokale Klasse, die eine lokale Schnittstelle implementiert, außerhalb der Methode verwendet würde, wäre die Schnittstelle bedeutungslos. Und wenn eine lokale Klasse nur innerhalb der Methode verwendet werden würde, würden sowohl die Schnittstelle als auch die Klasse innerhalb dieser Methode implementiert, sodass die Schnittstellendefinition redundant wäre.

Jacob Mattison
quelle
Irgendeine Idee, in welcher Version von lokalen Java-Klassen eingeführt wurde?
Schlitten
1
Innere Klassen wurden in Java 1.1 hinzugefügt - ich vermute, lokale Klassen waren es auch, aber ich habe keine Dokumentation dazu.
Jacob Mattison
Könnten Sie ein besseres Beispiel für einen Anwendungsfall einer nicht anonymen lokalen Klasse liefern? Ihr zweiter Codeblock könnte mit anonymen Klassen neu geschrieben werden.
Sergey Pauk
1
Die meisten Verwendungen nicht anonymer lokaler Klassen können mit anonymen Klassen durchgeführt werden. Ich habe das Beispiel nicht konkretisiert, aber normalerweise verwenden Sie eine benannte lokale Klasse, wenn Sie mehr als eine Instanz desselben Klassentyps erstellen müssen.
Jacob Mattison
1
Für das OP: Beachten Sie, dass die lokale Klasse eine Möglichkeit für die Kommunikation von Threads bietet. parameterDies kann in der einschließenden Methode deklariert werden und ist für beide Threads zugänglich.
flow2k
15

Diese werden lokale Klassen genannt . Eine ausführliche Erklärung und ein Beispiel finden Sie hier . Das Beispiel gibt eine bestimmte Implementierung zurück, über die wir außerhalb der Methode nichts wissen müssen.

BalusC
quelle
2
Toller Link (funktioniert immer noch nach 7+ Jahren!). Hinweis: "Wie Mitgliedsklassen sind lokale Klassen einer enthaltenden Instanz zugeordnet und können auf alle Mitglieder, einschließlich privater Mitglieder, der enthaltenden Klasse zugreifen ."
flow2k
10
  1. Die Klasse kann von außerhalb der Methode nicht gesehen werden (dh instanziiert, auf ihre Methoden wird ohne Reflexion zugegriffen). Es kann auch auf die in testMethod () definierten lokalen Variablen zugreifen, jedoch vor der Klassendefinition.

  2. Ich dachte tatsächlich: "Es wird keine solche Datei geschrieben." bis ich es gerade ausprobiert habe: Oh ja, eine solche Datei wird erstellt! Es wird so etwas wie A $ 1B.class genannt, wobei A die äußere Klasse und B die lokale Klasse ist.

  3. Insbesondere für Rückruffunktionen (Ereignishandler in GUIs wie onClick (), wenn auf eine Schaltfläche geklickt wird usw.) wird häufig "anonyme Klassen" verwendet - vor allem, weil Sie möglicherweise viele davon haben. Aber manchmal sind anonyme Klassen nicht gut genug - insbesondere können Sie keinen Konstruktor für sie definieren. In diesen Fällen können diese Methodenklassen eine gute Alternative sein.

Chris Lercher
quelle
2
2. Ehrm, sicher wird es. Klassendateien werden für jede verschachtelte, lokale oder anonyme Klasse in Ihrer Java-Datei generiert.
sepp2k
2
"2. Es wird keine solche Datei geschrieben." -- das ist falsch. Es wird TestClass$1TestMethodClass.classanalog zur .classBenennung innerer Klassendateien erstellt .
polygenelubricants
Gute Antwort, Ausnahme für 2: Sie erhalten die anonyme Klasse generiert, in diesem Fall "TestClass $ 1TestMethodClass.class"
Steve B.
Ja tut mir leid! Ich habe das erst vor ein paar Sekunden bemerkt. Du lebst und lernst :-))
Chris Lercher
Sie haben meine +1, um den Unterschied zwischen anonymen und lokalen Klassen hervorzuheben: Definieren eines Konstruktors.
Matthieu
7

Der eigentliche Zweck ist es, uns zu ermöglichen, Klassen in Funktionsaufrufen inline zu erstellen, um diejenigen von uns zu trösten, die gerne so tun, als würden wir in einer funktionalen Sprache schreiben;)

Steve B.
quelle
4

Der einzige Fall, in dem Sie eine voll funktionsfähige innere Klasse gegenüber einer anonymen Klasse (auch bekannt als Java-Schließung) wünschen, ist, wenn die folgenden Bedingungen erfüllt sind

  1. Sie müssen eine Schnittstelle oder eine abstrakte Klassenimplementierung bereitstellen
  2. Sie möchten einige endgültige Parameter verwenden, die in der aufrufenden Funktion definiert sind
  3. Sie müssen einen Ausführungsstatus des Schnittstellenaufrufs aufzeichnen.

ZB will jemand eine Runnable und Sie möchten aufzeichnen, wann die Ausführung begonnen und beendet hat.

Mit anonymer Klasse ist das nicht möglich, mit innerer Klasse ist das möglich.

Hier ist ein Beispiel, das meinen Standpunkt demonstriert

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

Bevor Sie dieses Muster verwenden, prüfen Sie bitte, ob einfache alte Klassen der obersten Ebene, innere Klassen oder statische innere Klassen bessere Alternativen sind.

Alexander Pogrebnyak
quelle
Ich missbrauche # 2 ziemlich oft, um Rückgabewerte von Funktionen zuzuweisen.
Eddie B
2

Der Hauptgrund für die Definition innerer Klassen (innerhalb einer Methode oder einer Klasse) besteht darin, die Zugänglichkeit von Mitgliedern und Variablen der einschließenden Klasse und Methode zu behandeln. Eine innere Klasse kann private Datenelemente nachschlagen und sie bearbeiten. Wenn innerhalb einer Methode, kann es auch mit der endgültigen lokalen Variablen umgehen.

Innere Klassen helfen dabei, sicherzustellen, dass diese Klasse für die Außenwelt nicht zugänglich ist. Dies gilt insbesondere für Fälle der UI-Programmierung in GWT oder GXT usw., in denen JS-Generierungscode in Java geschrieben ist und das Verhalten für jede Schaltfläche oder jedes Ereignis durch Erstellen anonymer Klassen definiert werden muss

Fazal
quelle
1

Ich bin im Frühjahr auf ein gutes Beispiel gestoßen. Das Framework verwendet das Konzept lokaler Klassendefinitionen innerhalb der Methode, um verschiedene Datenbankoperationen auf einheitliche Weise zu behandeln.

Angenommen, Sie haben einen Code wie diesen:

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

Schauen wir uns zunächst die Implementierung von execute () an:

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

Bitte beachten Sie die letzte Zeile. Spring macht genau diesen "Trick" auch für den Rest der Methoden:

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

Der "Trick" mit lokalen Klassen ermöglicht es dem Framework, alle diese Szenarien in einer einzigen Methode zu behandeln, die diese Klassen über die StatementCallback-Schnittstelle akzeptiert. Diese einzelne Methode fungiert als Brücke zwischen Aktionen (Ausführen, Aktualisieren) und allgemeinen Vorgängen (z. B. Ausführung, Verbindungsverwaltung, Fehlerübersetzung und Ausgabe der DBMS-Konsole).

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