Использование определений классов внутри метода в Java

105

Пример:

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

Я обнаружил, что приведенный выше фрагмент кода совершенно законен на Java. У меня следующие вопросы.

  1. Какая польза от определения класса внутри метода?
  2. Будет ли создан файл класса для DummyClass
  3. Мне сложно представить эту концепцию объектно-ориентированным образом. Наличие определения класса внутри поведения. Возможно, кто-нибудь подскажет мне эквивалентные примеры из реального мира.
  4. Для меня абстрактные классы внутри метода звучат немного безумно. Но никаких интерфейсов не допускается. Есть ли в этом причина?
хвастун
источник
1
Я согласен, это выглядит невероятно грязно. Я проверил код, который написал мой коллега, и нашел этот локальный класс в методе ... это просто заставило меня почувствовать, что этот модуль полностью осквернен.
Someone Somewhere
7
Иногда дело больше в том, чтобы спрятать вещи, которые вам больше нигде не нужны, а не в взглядах;)
sorrymissjackson

Ответы:

71

Это называется локальным классом.

2 простой: да, файл класса будет сгенерирован.

1 и 3 - это как бы один и тот же вопрос. Вы бы использовали локальный класс, в котором вам никогда не нужно создавать его экземпляр или знать детали реализации где угодно, кроме одного метода.

Типичное использование - создание одноразовой реализации некоторого интерфейса. Например, вы часто будете видеть что-то вроде этого:

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

Если вам нужно было создать их несколько и что-то с ними сделать, вы можете изменить это на

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

Что касается интерфейсов: я не уверен, есть ли техническая проблема, которая делает локально определенные интерфейсы проблемой для компилятора, но даже если это не так, они не добавят никакой ценности. Если бы локальный класс, реализующий локальный интерфейс, использовался вне метода, интерфейс был бы бессмысленным. И если локальный класс будет использоваться только внутри метода, и интерфейс, и класс будут реализованы в этом методе, поэтому определение интерфейса будет избыточным.

Джейкоб Мэттисон
источник
Есть идеи, в какой версии локальных классов Java были представлены?
Sled
1
Внутренние классы были добавлены в Java 1.1 - я предполагаю, что локальные классы тоже были, но у меня нет документации по этому поводу.
Джейкоб Мэттисон
Не могли бы вы дать лучший пример того, что могло бы быть вариантом использования неанонимного локального класса? Второй блок кода можно переписать анонимными классами.
Сергей Паук,
1
Большинство случаев использования неанонимных локальных классов может быть выполнено с помощью анонимных классов. Я не конкретизировал пример, но вы обычно используете именованный локальный класс, если вам нужно создать более одного экземпляра того же типа класса.
Джейкоб Мэттисон,
1
Для OP: обратите внимание, что локальный класс обеспечивает способ взаимодействия потоков - parameterвышеуказанное может быть объявлено во включающем методе и доступно для обоих потоков.
flow2k
15

Их называют местными классами . Вы можете найти подробное объяснение и пример здесь . Пример возвращает конкретную реализацию, о которой нам не нужно знать вне метода.

BalusC
источник
2
Отличная ссылка (все еще работает через 7+ лет!). В частности, обратите внимание: «Как и классы-члены, локальные классы связаны с содержащим экземпляром и могут получить доступ к любым членам, включая частные члены, содержащего класса ».
flow2k
10
  1. Класс нельзя увидеть (т.е. создать экземпляр, получить доступ к его методам без отражения) извне метода. Кроме того, он может получить доступ к локальным переменным, определенным в testMethod (), но до определения класса.

  2. Я вообще-то подумал: «Такой файл писать не будет». пока только не попробовал: Ах да, такой файл создается! Он будет называться что-то вроде A $ 1B.class, где A - внешний класс, а B - локальный класс.

  3. Специально для функций обратного вызова (обработчиков событий в графическом интерфейсе пользователя, таких как onClick () при нажатии кнопки и т. Д.) Довольно часто используются «анонимные классы» - в первую очередь потому, что их может быть много. Но иногда анонимные классы недостаточно хороши - особенно вы не можете определить для них конструктор. В этих случаях хорошей альтернативой могут быть локальные классы этих методов.

Крис Леркер
источник
2
2. Эээ, конечно, будет. Файлы классов будут созданы для каждого вложенного, локального или анонимного класса в вашем java-файле.
sepp2k
2
«2. Никакой такой файл записываться не будет». -- это не верно. Он создает TestClass$1TestMethodClass.classаналогично тому, как .classназываются файлы внутренних классов .
polygenelubricants
Хороший ответ, исключение для 2: вы получите сгенерированный анонимный класс, в данном случае «TestClass $ 1TestMethodClass.class»
Стив Б.
Да извини! Я не осознавал этого лишь несколько секунд назад. Вы живете и учитесь :-))
Крис Леркер,
У вас есть мой +1, чтобы подчеркнуть разницу между анонимными и локальными классами: определение конструктора.
Matthieu
7

Настоящая цель этого - позволить нам создавать классы, встроенные в вызовы функций, чтобы утешить тех из нас, кто любит делать вид, что мы пишем на функциональном языке;)

Стив Б.
источник
4

Единственный случай, когда вы хотели бы иметь полноценный внутренний класс функции и анонимный класс (также известный как закрытие Java), - это когда выполняются следующие условия

  1. вам необходимо предоставить интерфейс или реализацию абстрактного класса
  2. вы хотите использовать некоторые окончательные параметры, определенные в вызывающей функции
  3. вам необходимо записать некоторое состояние выполнения вызова интерфейса.

Например, кто-то хочет, Runnableа вы хотите записать, когда выполнение началось и закончилось.

С анонимным классом это невозможно, с внутренним классом вы можете это сделать.

Вот пример, демонстрирующий мою точку зрения

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

Однако, прежде чем использовать этот шаблон, оцените, является ли простой старый класс верхнего уровня, внутренний класс или статический внутренний класс лучшей альтернативой.

Александр Погребняк
источник
Я довольно сильно злоупотребляю №2, чтобы присвоить возвращаемые значения из функций.
Eddie B
2

Основная причина определения внутренних классов (внутри метода или класса) заключается в том, чтобы иметь дело с доступностью членов и переменных включающего класса и метода. Внутренний класс может искать частные данные-члены и работать с ними. Если внутри метода, он также может иметь дело с конечной локальной переменной.

Наличие внутренних классов действительно помогает убедиться, что этот класс недоступен для внешнего мира. Это особенно верно для случаев программирования пользовательского интерфейса в GWT или GXT и т. Д., Когда код генерации JS написан на java, а поведение для каждой кнопки или события должно определяться путем создания анонимных классов.

Фазаль
источник
1

Весной я наткнулся на хороший пример. Фреймворк использует концепцию определений локальных классов внутри метода для единообразной обработки различных операций с базой данных.

Предположим, у вас есть такой код:

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

Давайте сначала посмотрим на реализацию 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());
    }

Обратите внимание на последнюю строку. Spring делает этот точный «трюк» и для остальных методов:

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

«Уловка» с локальными классами позволяет структуре обрабатывать все эти сценарии одним методом, который принимает эти классы через интерфейс StatementCallback. Этот единственный метод действует как мост между действиями (выполнение, обновление) и общими операциями вокруг них (например, выполнение, управление соединениями, преобразование ошибок и вывод консоли 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());
        }
    }
с-евгени
источник