Uso de definiciones de clases dentro de un método en Java

105

Ejemplo:

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

Descubrí que el código anterior es perfectamente legal en Java. Tengo las siguientes preguntas.

  1. ¿De qué sirve tener una definición de clase dentro de un método?
  2. ¿Se generará un archivo de clase para DummyClass
  3. Es difícil para mí imaginar este concepto de una manera orientada a objetos. Tener una definición de clase dentro de un comportamiento. Probablemente alguien pueda decirme con ejemplos equivalentes del mundo real.
  4. Las clases abstractas dentro de un método me suenan un poco locas. Pero no se permiten interfaces. ¿Hay alguna razón detrás de esto?
fanfarrón
fuente
1
Estoy de acuerdo, es increíblemente desordenado. Inspeccioné un código que escribió mi colega y encontré esta clase local en un método ... simplemente me hizo sentir que este módulo estaba totalmente contaminado.
Someone Somewhere
7
A veces se trata más de esconder cosas que no necesitas en ningún otro lugar, en lugar de miradas;)
sorrymissjackson

Respuestas:

71

Esto se llama clase local.

2 es el más fácil: sí, se generará un archivo de clase.

1 y 3 son la misma pregunta. Usaría una clase local en la que nunca necesitaría crear una instancia o conocer los detalles de implementación en cualquier lugar excepto en un método.

Un uso típico sería crear una implementación desechable de alguna interfaz. Por ejemplo, a menudo verá algo como esto:

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

Si necesita crear un montón de estos y hacer algo con ellos, puede cambiar esto a

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

Con respecto a las interfaces: no estoy seguro de si hay un problema técnico que haga que las interfaces definidas localmente sean un problema para el compilador, pero incluso si no lo hubiera, no agregarían ningún valor. Si una clase local que implementa una interfaz local se usara fuera del método, la interfaz no tendría sentido. Y si una clase local solo se usaría dentro del método, tanto la interfaz como la clase se implementarían dentro de ese método, por lo que la definición de la interfaz sería redundante.

Jacob Mattison
fuente
¿Alguna idea de qué versión de las clases locales de Java se introdujeron?
Trineo
1
Las clases internas se agregaron en Java 1.1; supongo que las clases locales también lo fueron, pero no tengo documentación al respecto.
Jacob Mattison
¿Podría proporcionar un mejor ejemplo de lo que podría ser un caso de uso de una clase local no anónima? Su segundo bloque de código podría reescribirse con clases anónimas.
Sergey Pauk
1
La mayoría de los usos de clases locales no anónimas se pueden lograr con clases anónimas. No desarrollé el ejemplo, pero normalmente usaría una clase local con nombre si necesita crear más de una instancia del mismo tipo de clase.
Jacob Mattison
1
Para el OP: tenga en cuenta que la clase local proporciona una forma para que los subprocesos se comuniquen; lo parameteranterior podría declararse en el método adjunto y es accesible para ambos subprocesos.
flow2k
15

Esas se llaman clases locales . Puede encontrar una explicación detallada y un ejemplo aquí . El ejemplo devuelve una implementación específica que no necesitamos conocer fuera del método.

BalusC
fuente
2
Gran enlace (¡todavía funciona después de más de 7 años!). En particular, tenga en cuenta "Al igual que las clases de miembros, las clases locales están asociadas con una instancia contenedora y pueden acceder a cualquier miembro, incluidos los miembros privados, de la clase contenedora ".
flow2k
10
  1. La clase no se puede ver (es decir, instanciar, se accede a sus métodos sin Reflection) desde fuera del método. Además, puede acceder a las variables locales definidas en testMethod (), pero antes de la definición de la clase.

  2. De hecho, pensé: "No se escribirá tal archivo". hasta que lo probé: ¡Oh, sí, se crea un archivo de este tipo! Se llamará algo así como A $ 1B.class, donde A es la clase externa y B es la clase local.

  3. Especialmente para las funciones de devolución de llamada (controladores de eventos en GUI, como onClick () cuando se hace clic en un botón, etc.), es bastante habitual usar "clases anónimas", en primer lugar porque puede terminar con muchas de ellas. Pero a veces las clases anónimas no son lo suficientemente buenas, especialmente, no puedes definir un constructor en ellas. En estos casos, estas clases locales de métodos pueden ser una buena alternativa.

Chris Lercher
fuente
2
2. Ehrm, seguro que lo hará. Los archivos de clase se generarán para cada clase anidada, local o anónima en su archivo java.
sepp2k
2
"2. No se escribirá tal archivo". -- esto está mal. Crea TestClass$1TestMethodClass.class, de forma análoga a cómo .classse nombran los archivos de clases internas .
polygenelubricants
Buena respuesta, excepción para 2: obtendrá la clase anónima generada, en este caso "TestClass $ 1TestMethodClass.class"
Steve B.
¡Sí lo siento! No me di cuenta de esto hasta hace unos segundos. Vives y aprendes :-))
Chris Lercher
Tienes mi +1 para resaltar la diferencia entre clases anónimas y locales: definir un constructor.
Matthieu
7

El propósito real de esto es permitirnos crear clases en línea en llamadas a funciones para consolar a aquellos de nosotros a quienes nos gusta fingir que estamos escribiendo en un lenguaje funcional;)

Steve B.
fuente
4

El único caso en el que le gustaría tener una clase interna de función completa frente a una clase anónima (también conocida como cierre de Java) es cuando se cumplen las siguientes condiciones

  1. necesita proporcionar una interfaz o implementación de clase abstracta
  2. desea utilizar algunos parámetros finales definidos en la función de llamada
  3. necesita registrar algún estado de ejecución de la llamada de interfaz.

Por ejemplo, alguien quiere un Runnabley usted quiere registrar cuándo ha comenzado y terminado la ejecución.

Con la clase anónima no es posible hacerlo, con la clase interna puedes hacerlo.

Aquí hay un ejemplo para demostrar mi 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 );
}

Sin embargo, antes de usar este patrón, evalúe si la clase simple de nivel superior, la clase interna o la clase interna estática son mejores alternativas.

Alexander Pogrebnyak
fuente
Abuso bastante del n. ° 2 para asignar valores de retorno de funciones.
Eddie B
2

La razón principal para definir clases internas (dentro de un método o una clase) es lidiar con la accesibilidad de los miembros y las variables de la clase y el método adjuntos. Una clase interna puede buscar miembros de datos privados y operar sobre ellos. Si está dentro de un método, también puede tratar con la variable local final.

Tener clases internas ayuda a asegurarse de que esta clase no sea accesible para el mundo exterior. Esto es válido especialmente para los casos de programación de UI en GWT o GXT, etc., donde el código de generación de JS está escrito en Java y el comportamiento de cada botón o evento debe definirse mediante la creación de clases anónimas.

Fazal
fuente
1

Me encontré con un buen ejemplo en primavera. El marco utiliza el concepto de definiciones de clases locales dentro del método para manejar varias operaciones de base de datos de manera uniforme.

Suponga que tiene un código como este:

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

Veamos primero la implementación 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());
    }

Tenga en cuenta la última línea. Spring también hace este "truco" exacto para el resto de los métodos:

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

El "truco" con las clases locales permite que el marco se ocupe de todos esos escenarios en un solo método que acepta esas clases a través de la interfaz StatementCallback. Este método único actúa como un puente entre las acciones (ejecutar, actualizar) y las operaciones comunes a su alrededor (por ejemplo, ejecución, administración de conexiones, traducción de errores y salida de la consola 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
fuente