Uso delle definizioni di classe all'interno di un metodo in Java


105

Esempio:

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

Ho scoperto che la parte di codice sopra è perfettamente legale in Java. Ho le seguenti domande.

  1. A che serve avere una definizione di classe all'interno di un metodo?
  2. Verrà generato un file di classe per DummyClass
  3. È difficile per me immaginare questo concetto in modo orientato agli oggetti. Avere una definizione di classe all'interno di un comportamento. Probabilmente qualcuno me lo può dire con esempi equivalenti del mondo reale.
  4. Le classi astratte all'interno di un metodo mi sembrano un po 'folli. Ma non sono consentite interfacce. C'è qualche motivo dietro questo?

1
Sono d'accordo, sembra incredibilmente disordinato. Ho ispezionato un po 'di codice che il mio collega ha scritto e ho trovato questa classe locale in un metodo ... mi ha fatto sentire come se questo modulo fosse completamente contaminato.
Someone Somewhere

7
A volte si tratta più di nascondere cose che non ti servono da nessun'altra parte, piuttosto che di sguardi;)
sorrymissjackson

Risposte:


71

Questa è chiamata classe locale.

2 è quello facile: sì, verrà generato un file di classe.

1 e 3 sono più o meno la stessa domanda. Usereste una classe locale in cui non è mai necessario istanziarne una o conoscere i dettagli di implementazione ovunque ma in un metodo.

Un utilizzo tipico sarebbe quello di creare un'implementazione usa e getta di alcune interfacce. Ad esempio vedrai spesso qualcosa del genere:

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

Se avessi bisogno di creare un gruppo di questi e fare qualcosa con loro, potresti cambiarlo in

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

Per quanto riguarda le interfacce: non sono sicuro che ci sia un problema tecnico che rende le interfacce definite localmente un problema per il compilatore, ma anche se non ci fossero, non aggiungerebbero alcun valore. Se una classe locale che implementa un'interfaccia locale fosse utilizzata al di fuori del metodo, l'interfaccia sarebbe priva di significato. E se una classe locale venisse utilizzata solo all'interno del metodo, sia l'interfaccia che la classe sarebbero implementate all'interno di quel metodo, quindi la definizione dell'interfaccia sarebbe ridondante.


Qualche idea in quale versione delle classi locali Java è stata introdotta?
Slitta

1
Le classi interne sono state aggiunte in Java 1.1 - Immagino che lo fossero anche le classi locali ma non ho documentazione su questo.
Jacob Mattison

Potresti fornire un esempio migliore di quello che potrebbe essere un caso d'uso di una classe locale non anonima? Il tuo secondo blocco di codice potrebbe essere riscritto con classi anonime.
Sergey Pauk

1
La maggior parte degli usi di classi locali non anonime può essere realizzata con classi anonime. Non ho arricchito l'esempio, ma in genere useresti una classe locale denominata se hai bisogno di creare più di un'istanza dello stesso tipo di classe.
Jacob Mattison

1
Per l'OP: notare che la classe locale fornisce un modo per i thread di comunicare - quanto parametersopra potrebbe essere dichiarato nel metodo che lo racchiude ed è accessibile da entrambi i thread.
flow2k

15

Quelle sono chiamate classi locali . Puoi trovare una spiegazione dettagliata e un esempio qui . L'esempio restituisce un'implementazione specifica che non è necessario conoscere al di fuori del metodo.


2
Ottimo collegamento (funziona ancora dopo 7+ anni!). In particolare, nota "Come le classi membro, le classi locali sono associate a un'istanza contenitore e possono accedere a qualsiasi membro, inclusi i membri privati, della classe contenitore ."
flow2k

10
  1. La classe non può essere vista (cioè istanziata, i suoi metodi sono accessibili senza Reflection) dall'esterno del metodo. Inoltre, può accedere alle variabili locali definite in testMethod (), ma prima della definizione della classe.

  2. In realtà ho pensato: "Nessun file di questo tipo verrà scritto". fino a quando non l'ho provato: Oh sì, viene creato un file del genere! Si chiamerà qualcosa come A $ 1B.class, dove A è la classe esterna e B è la classe locale.

  3. Soprattutto per le funzioni di callback (gestori di eventi nelle GUI, come onClick () quando si fa clic su un pulsante, ecc.), È abbastanza usuale usare "classi anonime" - prima di tutto perché puoi ritrovarne molte. Ma a volte le classi anonime non sono abbastanza buone, soprattutto, non è possibile definire un costruttore su di esse. In questi casi, queste classi locali del metodo possono essere una buona alternativa.


2
2. Ehrm, certo che lo farà. I file di classe verranno generati per ogni classe nidificata, locale o anonima nel file java.
sepp2k

2
"2. Nessun file di questo tipo verrà scritto." -- questo è sbagliato. Crea TestClass$1TestMethodClass.class, analogamente a come .classvengono denominati i file delle classi interne .
lubrificanti poligenici

Buona risposta, eccezione per 2: otterrai la classe anonima generata, in questo caso "TestClass $ 1TestMethodClass.class"
Steve B.

Si mi dispiace! Non me ne rendevo conto fino a pochi secondi fa. Vivi e impari :-))
Chris Lercher

Hai il mio +1 per evidenziare la differenza tra classi anonime e locali: definire un costruttore.
Matthieu

7

Il vero scopo di questo è quello di permetterci di creare classi inline in chiamate di funzione per consolare quelli di noi a cui piace fingere di scrivere in un linguaggio funzionale;)


4

L'unico caso in cui desideri avere una funzione di classe interna completa rispetto a una classe anonima (ovvero chiusura Java) è quando sono soddisfatte le seguenti condizioni

  1. è necessario fornire un'interfaccia o un'implementazione di classe astratta
  2. si desidera utilizzare alcuni parametri finali definiti nella funzione di chiamata
  3. è necessario registrare uno stato di esecuzione della chiamata all'interfaccia.

Ad esempio, qualcuno vuole un Runnablee tu vuoi registrare quando l'esecuzione è iniziata e terminata.

Con la classe anonima non è possibile farlo, con la classe interna puoi farlo.

Ecco un esempio che dimostra il mio punto

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

Tuttavia, prima di utilizzare questo modello, valutare se la semplice vecchia classe di primo livello, o la classe interna o la classe interna statica sono alternative migliori.


Abuso abbastanza il n. 2 per assegnare i valori di ritorno dalle funzioni.
Eddie B

2

Il motivo principale per definire le classi interne (all'interno di un metodo o una classe) è quello di gestire l'accessibilità dei membri e delle variabili della classe e del metodo che li racchiude. Una classe interna può cercare membri di dati privati ​​e operare su di essi. Se all'interno di un metodo può trattare anche la variabile locale finale.

Avere classi interne aiuta a garantire che questa classe non sia accessibile al mondo esterno. Ciò vale soprattutto per i casi di programmazione dell'interfaccia utente in GWT o GXT ecc. In cui il codice di generazione di JS è scritto in java e il comportamento per ogni pulsante o evento deve essere definito creando classi anonime


1

Ho trovato un buon esempio in primavera. Il framework utilizza il concetto di definizioni di classi locali all'interno del metodo per gestire varie operazioni di database in modo uniforme.

Supponi di avere un codice come questo:

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

Diamo prima un'occhiata all'implementazione di 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());
    }

Si prega di notare l'ultima riga. La primavera fa questo esatto "trucco" anche per il resto dei metodi:

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

Il "trucco" con le classi locali consente al framework di gestire tutti quegli scenari in un unico metodo che accetta quelle classi tramite l'interfaccia StatementCallback. Questo singolo metodo funge da ponte tra le azioni (esecuzione, aggiornamento) e le operazioni comuni attorno ad esse (ad es. Esecuzione, gestione della connessione, traduzione degli errori e output della console 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());
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.