Utilisation de définitions de classe dans une méthode en Java

105

Exemple:

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

J'ai découvert que le morceau de code ci-dessus est parfaitement légal en Java. J'ai les questions suivantes.

  1. Quelle est l'utilité d'avoir une définition de classe dans une méthode?
  2. Un fichier de classe sera-t-il généré pour DummyClass
  3. Il m'est difficile d'imaginer ce concept d'une manière orientée objet. Avoir une définition de classe dans un comportement. Quelqu'un peut probablement me dire avec des exemples équivalents dans le monde réel.
  4. Les classes abstraites à l'intérieur d'une méthode me semblent un peu folles. Mais aucune interface n'est autorisée. Y a-t-il une raison à cela?
bragboy
la source
1
Je suis d'accord, c'est incroyablement désordonné. J'ai inspecté du code que mon collègue a écrit et j'ai trouvé cette classe locale dans une méthode ... cela m'a juste donné l'impression que ce module était totalement souillé.
Someone Somewhere
7
Parfois, il s'agit plus de cacher des choses dont vous n'avez besoin nulle part ailleurs que de
chercher

Réponses:

71

C'est ce qu'on appelle une classe locale.

2 est le plus simple: oui, un fichier de classe sera généré.

1 et 3 sont un peu la même question. Vous utiliseriez une classe locale où vous n'aurez jamais besoin d'en instancier une ou de connaître les détails d'implémentation ailleurs que dans une seule méthode.

Une utilisation typique serait de créer une implémentation jetable d'une interface. Par exemple, vous verrez souvent quelque chose comme ceci:

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

Si vous deviez en créer un certain nombre et en faire quelque chose, vous pouvez changer cela en

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

En ce qui concerne les interfaces: je ne suis pas sûr qu'il y ait un problème technique qui fasse des interfaces définies localement un problème pour le compilateur, mais même s'il n'y en a pas, elles n'ajouteraient aucune valeur. Si une classe locale qui implémente une interface locale était utilisée en dehors de la méthode, l'interface n'aurait aucun sens. Et si une classe locale ne devait être utilisée qu'à l'intérieur de la méthode, l'interface et la classe seraient implémentées dans cette méthode, donc la définition de l'interface serait redondante.

Jacob Mattison
la source
Une idée dans quelle version des classes locales Java ont été introduites?
Traîneau
1
Des classes internes ont été ajoutées dans Java 1.1 - je suppose que les classes locales l'étaient également, mais je n'ai pas de documentation à ce sujet.
Jacob Mattison
Pourriez-vous fournir un meilleur exemple de ce qui pourrait être un cas d'utilisation d'une classe locale non anonyme? Votre deuxième bloc de code pourrait être réécrit avec des classes anonymes.
Sergey Pauk
1
La plupart des utilisations de classes locales non anonymes peuvent être effectuées avec des classes anonymes. Je n'ai pas étoffé l'exemple, mais vous utiliseriez généralement une classe locale nommée si vous avez besoin de créer plus d'une instance du même type de classe.
Jacob Mattison
1
Pour l'OP: notez que la classe locale fournit un moyen pour les threads de communiquer - ce qui parameterprécède peut être déclaré dans la méthode englobante et est accessible par les deux threads.
flow2k
15

Celles-ci sont appelées classes locales . Vous pouvez trouver une explication détaillée et un exemple ici . L'exemple renvoie une implémentation spécifique dont nous n'avons pas besoin de connaître en dehors de la méthode.

BalusC
la source
2
Excellent lien (fonctionne toujours après 7+ ans!). En particulier, notez «Comme les classes membres, les classes locales sont associées à une instance conteneur et peuvent accéder à tous les membres, y compris les membres privés, de la classe conteneur ».
flow2k
10
  1. La classe ne peut pas être vue (c'est-à-dire instanciée, ses méthodes accessibles sans Reflection) de l'extérieur de la méthode. De plus, il peut accéder aux variables locales définies dans testMethod (), mais avant la définition de la classe.

  2. J'ai en fait pensé: "Aucun fichier de ce type ne sera écrit." jusqu'à ce que je viens de l'essayer: Oh oui, un tel fichier est créé! Il sera appelé quelque chose comme A $ 1B.class, où A est la classe externe et B est la classe locale.

  3. Surtout pour les fonctions de rappel (gestionnaires d'événements dans les interfaces graphiques, comme onClick () lorsqu'un bouton est cliqué, etc.), il est assez courant d'utiliser des «classes anonymes» - tout d'abord parce que vous pouvez vous retrouver avec beaucoup d'entre elles. Mais parfois, les classes anonymes ne sont pas assez bonnes - en particulier, vous ne pouvez pas définir de constructeur sur elles. Dans ces cas, ces classes locales de méthodes peuvent être une bonne alternative.

Chris Lercher
la source
2
2. Ehrm, bien sûr. Les fichiers de classe seront générés pour chaque classe imbriquée, locale ou anonyme dans votre fichier java.
sepp2k
2
"2. Aucun fichier de ce type ne sera écrit." -- c'est faux. Il crée TestClass$1TestMethodClass.class, analogue à la façon dont les .classfichiers de classes internes sont nommés.
polygenelubricants
Bonne réponse, exception pour 2: vous obtiendrez la classe anonyme générée, dans ce cas "TestClass $ 1TestMethodClass.class"
Steve B.
Oui je suis désolé! Je ne m'en suis rendu compte qu'il y a quelques secondes. Vous vivez et apprenez :-))
Chris Lercher
Vous avez mon +1 pour mettre en évidence la différence entre les classes anonymes et locales: définir un constructeur.
Matthieu
7

Le vrai but de ceci est de nous permettre de créer des classes en ligne dans les appels de fonction pour consoler ceux d'entre nous qui aiment prétendre que nous écrivons dans un langage fonctionnel;)

Steve B.
la source
4

Le seul cas où vous souhaitez avoir une classe interne de fonctions complète par rapport à une classe anonyme (aka fermeture Java) est lorsque les conditions suivantes sont remplies

  1. vous devez fournir une interface ou une implémentation de classe abstraite
  2. vous souhaitez utiliser certains paramètres finaux définis dans la fonction d'appel
  3. vous devez enregistrer un état d'exécution de l'appel d'interface.

Par exemple, quelqu'un veut un Runnableet vous voulez enregistrer le début et la fin de l'exécution.

Avec une classe anonyme, ce n'est pas possible, avec une classe interne, vous pouvez le faire.

Voici un exemple qui démontre mon point

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

Avant d'utiliser ce modèle, veuillez évaluer si une ancienne classe de premier niveau, une classe interne ou une classe interne statique sont de meilleures alternatives.

Alexander Pogrebnyak
la source
J'abuse un peu du # 2 pour attribuer des valeurs de retour à partir de fonctions.
Eddie B
2

La principale raison de définir des classes internes (dans une méthode ou une classe) est de traiter l'accessibilité des membres et des variables de la classe et de la méthode englobantes. Une classe interne peut rechercher des membres de données privées et les exploiter. Si dans une méthode, il peut également traiter la variable locale finale.

Avoir des classes internes aide à s'assurer que cette classe n'est pas accessible au monde extérieur. Ceci est particulièrement vrai pour les cas de programmation d'interface utilisateur dans GWT ou GXT, etc. où le code de génération JS est écrit en java et le comportement de chaque bouton ou événement doit être défini en créant des classes anonymes

Fazal
la source
1

Je suis tombé sur un bon exemple au printemps. Le cadre utilise le concept de définitions de classes locales à l'intérieur de la méthode pour traiter diverses opérations de base de données de manière uniforme.

Supposons que vous ayez un code comme celui-ci:

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

Regardons d'abord l'implémentation de 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());
    }

Veuillez noter la dernière ligne. Spring fait également cette "astuce" exacte pour le reste des méthodes:

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

Le "truc" avec les classes locales permet au framework de gérer tous ces scénarios dans une seule méthode qui accepte ces classes via l'interface StatementCallback. Cette méthode unique agit comme un pont entre les actions (exécuter, mettre à jour) et les opérations courantes autour d'elles (par exemple, exécution, gestion des connexions, traduction des erreurs et sortie de la 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());
        }
    }
s-evgheni
la source