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

Пример:

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. Абстрактные классы внутри метода звучат для меня немного безумно. Но интерфейсы не допускаются. Есть ли причина этого?

9 ответов

Решение

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

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

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

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

  1. Класс не может быть виден (т.е. создан, его методы доступны без отражения) извне метода. Кроме того, он может обращаться к локальным переменным, определенным в testMethod(), но перед определением класса.

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

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

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

Единственный случай, когда вы хотите иметь полноценный внутренний класс функции против анонимного класса (или закрытие 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 );
}

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

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

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

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

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

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

Здесь все понятно, но я хотел привести еще один пример разумного варианта использования этого типа определения класса для следующих читателей.

Что касается ответа @jacob-mattison, если мы предположим, что у нас есть некоторые общие действия в этих одноразовых реализациях интерфейса, поэтому лучше написать его один раз, но также сохранить анонимность реализации:

          //within some method
    abstract class myRunnableClass implements Runnable {

        protected abstract void DO_AN_SPECIFIC_JOB();

        public void run() {
            someCommonCode();
            //...
            DO_AN_SPECIFIC_JOB();
            //..
            anotherCommonCode();
        }
    }

Затем легко использовать этот определенный класс и просто реализовать конкретную задачу отдельно:

          taskExecutor.execute(new myRunnableClass() {
        protected void DO_AN_SPECIFIC_JOB() {
            // Do something
        }
    });
    taskExecutor.execute(new myRunnableClass() {
        protected void DO_AN_SPECIFIC_JOB() {
            // Do another thing
        }
    });

Локальные классы — это удобный способ получить значения по умолчанию из аннотации. Допустим, вы реализовали аннотацию с набором параметров со значениями по умолчанию:

      @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Foo {

  String paramBar() default "Bar";

  String paramVar() default "Var";
}

Теперь предположим, что у вас есть код, в котором вы ищете свойFooаннотации к занятиям, предоставленные Вам в комплектеClass<?>объекты, и когда они присутствуют, вы читаете параметры, но при их отсутствии вы хотите получить значения по умолчанию. Вот как это можно сделать:

      void aMethod(Set<Class<?>> classSet) {
  @Foo
  final class FooDefault { }

  for (var cls : classSet) {
    Foo foo;

    if (cls.isAnnotationPresent(Foo.class)) {
      foo = cls.getAnnotation(Foo.class);
    } else {
      foo = FooDefault.class.getAnnotation(Foo.class);
    }

    doSomethingCool(cls, foo);
  }
}
Другие вопросы по тегам