Uso de definições de classe dentro de um método em Java


105

Exemplo:

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

Eu descobri que o código acima é perfeitamente legal em Java. Tenho as seguintes perguntas.

  1. Qual é a utilidade de ter uma definição de classe dentro de um método?
  2. Um arquivo de classe será gerado para DummyClass
  3. É difícil para mim imaginar este conceito de uma forma orientada a objetos. Ter uma definição de classe dentro de um comportamento. Provavelmente alguém pode me dizer exemplos equivalentes do mundo real.
  4. Classes abstratas dentro de um método parecem um pouco malucas para mim. Mas nenhuma interface é permitida. Existe alguma razão por trás disso?

1
Eu concordo, é incrivelmente bagunçado. Eu inspecionei algum código que meu colega escreveu e encontrei essa classe local em um método ... isso me fez sentir como se este módulo estivesse totalmente corrompido.
Alguém em algum lugar de

7
Às vezes, é mais sobre como esconder coisas que você não precisa em nenhum outro lugar, ao invés de olhares;)
sorrymissjackson

Respostas:


71

Isso é chamado de classe local.

2 é o mais fácil: sim, um arquivo de classe será gerado.

1 e 3 são quase a mesma pergunta. Você usaria uma classe local onde nunca precisaria instanciar uma ou saber sobre os detalhes de implementação em qualquer lugar, exceto em um método.

Um uso típico seria criar uma implementação descartável de alguma interface. Por exemplo, você frequentemente verá algo assim:

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

Se você precisar criar um monte deles e fazer algo com eles, você pode alterar isso para

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

Com relação às interfaces: Não tenho certeza se há um problema técnico que torna as interfaces definidas localmente um problema para o compilador, mas mesmo se não houvesse, elas não agregariam valor. Se uma classe local que implementa uma interface local fosse usada fora do método, a interface não teria sentido. E se uma classe local fosse usada apenas dentro do método, a interface e a classe seriam implementadas dentro desse método, portanto, a definição da interface seria redundante.


Alguma ideia em que versão das classes locais Java foram introduzidas?
Trenó de

1
As classes internas foram adicionadas em Java 1.1 - estou supondo que as classes locais também foram, mas não tenho documentação sobre isso.
Jacob Mattison

Você poderia fornecer um exemplo melhor do que poderia ser um caso de uso de uma classe local não anônima? Seu segundo bloco de código pode ser reescrito com classes anônimas.
Sergey Pauk de

1
A maioria dos usos de classes locais não anônimas pode ser realizada com classes anônimas. Não desenvolvi o exemplo, mas você normalmente usaria uma classe local nomeada se precisar criar mais de uma instância do mesmo tipo de classe.
Jacob Mattison

1
Para o OP: observe que a classe local fornece uma maneira para os threads se comunicarem - o parameteracima pode ser declarado no método envolvente e é acessível por ambos os threads.
flow2k

15

Essas são chamadas de classes locais . Você pode encontrar uma explicação detalhada e um exemplo aqui . O exemplo retorna uma implementação específica que não precisamos saber fora do método.


2
Ótimo link (ainda funciona depois de 7 anos ou mais!). Em particular, observe "Como as classes-membro, as classes locais são associadas a uma instância contida e podem acessar qualquer membro, incluindo membros privados, da classe contida ".
flow2k

10
  1. A classe não pode ser vista (ou seja, instanciada, seus métodos acessados ​​sem Reflexão) de fora do método. Além disso, ele pode acessar as variáveis ​​locais definidas em testMethod (), mas antes da definição da classe.

  2. Na verdade, pensei: "Esse arquivo não será gravado." até que eu tentei: Oh sim, esse arquivo é criado! Ela será chamada de algo como A $ 1B.class, onde A é a classe externa e B é a classe local.

  3. Especialmente para funções de retorno de chamada (manipuladores de eventos em GUIs, como onClick () quando um botão é clicado etc.), é bastante comum usar "classes anônimas" - em primeiro lugar, porque você pode acabar com muitas delas. Mas às vezes as classes anônimas não são boas o suficiente - especialmente, você não pode definir um construtor para elas. Nestes casos, essas classes locais do método podem ser uma boa alternativa.


2
2. Ehrm, claro que sim. Os arquivos de classe serão gerados para cada classe aninhada, local ou anônima em seu arquivo java.
sepp2k

2
"2. Esse arquivo não será gravado." -- isto está errado. Ele cria TestClass$1TestMethodClass.class, de forma análoga a como os .classarquivos de classes internas são nomeados.
poligenelubrificantes de

Boa resposta, exceção para 2: você obterá a classe anônima gerada, neste caso "TestClass $ 1TestMethodClass.class"
Steve B.

Sim desculpa! Eu não percebi isso até alguns segundos atrás. Você vive e aprende :-))
Chris Lercher

Você tem meu +1 por destacar a diferença entre classes anônimas e locais: definir um construtor.
Matthieu

7

O verdadeiro propósito disso é nos permitir criar classes embutidas em chamadas de função para consolar aqueles de nós que gostam de fingir que estamos escrevendo em uma linguagem funcional;)


4

O único caso em que você gostaria de ter uma classe interna de função totalmente desenvolvida versus classe anônima (também conhecida como encerramento de Java) é quando as seguintes condições são atendidas

  1. você precisa fornecer uma interface ou implementação de classe abstrata
  2. você deseja usar alguns parâmetros finais definidos na função de chamada
  3. você precisa registrar algum estado de execução da chamada de interface.

Por exemplo, alguém deseja um Runnablee você deseja registrar quando a execução foi iniciada e encerrada.

Com a classe anônima não é possível fazer, com a classe interna você pode fazer isso.

Aqui está um exemplo para demonstrar meu ponto

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

Antes de usar este padrão, por favor, avalie se a velha e simples classe de nível superior, ou classe interna, ou classe interna estática são alternativas melhores.


Eu abusei de # 2 um pouco para atribuir valores de retorno de funções.
Eddie B

2

A principal razão para definir classes internas (dentro de um método ou classe) é lidar com a acessibilidade de membros e variáveis ​​da classe e método envolvente. Uma classe interna pode pesquisar membros de dados privados e operar neles. Se estiver dentro de um método, ele pode lidar com a variável local final também.

Ter classes internas ajuda a garantir que essa classe não seja acessível ao mundo externo. Isso é verdadeiro especialmente para casos de programação UI em GWT ou GXT etc, onde o código de geração de JS é escrito em java e o comportamento de cada botão ou evento deve ser definido pela criação de classes anônimas


1

Encontrei um bom exemplo na primavera. A estrutura está usando o conceito de definições de classes locais dentro do método para lidar com várias operações de banco de dados de maneira uniforme.

Suponha que você tenha um 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);

Vejamos primeiro a implementação 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());
    }

Observe a última linha. O Spring também faz esse "truque" exato para o resto dos métodos:

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

O "truque" com as classes locais permite que o framework lide com todos esses cenários em um único método que aceita essas classes por meio da interface StatementCallback. Este método único atua como uma ponte entre as ações (executar, atualizar) e as operações comuns em torno delas (por exemplo, execução, gerenciamento de conexão, tradução de erro e saída do 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());
        }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.