Використання визначень класів всередині методу на 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
Я згоден, це неймовірно безладно виглядає. Я перевірив якийсь код, який написав мій колега, і знайшов цей локальний клас методом ... це просто змусило мене відчути, що цей модуль був повністю осквернений.
Хтось десь

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, де введено?
Санки

1
Внутрішні класи були додані в Java 1.1 - я здогадуюсь, що і місцеві класи були, але я не маю на це документації.
Джейкоб Меттісон

Чи можете ви надати кращий приклад того, що може бути випадком використання неанонімного місцевого класу? Ваш другий блок коду може бути переписаний анонімними класами.
Сергій Паук

1
Більшість застосувань неанонімних локальних класів можна виконати за допомогою анонімних класів. Я не деталізував приклад, але ти зазвичай використовуєш названий місцевий клас, якщо потрібно створити більше одного екземпляра того ж типу класу.
Джейкоб Меттісон

1
Для ОП: зауважте, що місцевий клас надає спосіб для передачі потоків - parameterвищезазначене може бути оголошено методом вкладення та доступне обома потоками.
flow2k

15

Вони називаються місцевими класами . Ви можете знайти детальне пояснення та приклад тут . Приклад повертає конкретну реалізацію, про яку нам не потрібно знати поза методом.


2
Чудове посилання (все ще працює після 7+ років!). Зокрема, зауважте: "Подібно до класів членів, місцеві класи асоціюються з містить екземпляр, і вони можуть отримати доступ до будь-яких членів, що містять клас, що містять ".
flow2k

10
  1. Клас не можна побачити (тобто інстанціювати, його методи отримати доступ без Reflection) поза методом. Крім того, він може отримати доступ до локальних змінних, визначених у testMethod (), але до визначення класу.

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

  3. Особливо для функцій зворотного дзвінка (обробники подій у графічних інтерфейсах, як-от onClick (), коли натискається кнопка тощо), цілком звичайно використовувати "анонімні класи" - насамперед тому, що ви можете закінчити багато з них. Але іноді анонімні класи недостатньо хороші - особливо, ви не можете визначити конструктор на них. У цих випадках ці методи локальних класів можуть бути хорошою альтернативою.


2
2. Ерм, впевнений, що буде. Файли класу будуть створені для кожного вкладеного, локального або анонімного класу у вашому файлі Java.
sepp2k

2
"2. Такий файл не буде записаний." -- це неправильно. Він створює TestClass$1TestMethodClass.classаналог тому, як .classназиваються файли внутрішніх класів .
полігенмастильні матеріали

Хороша відповідь, виняток за 2: ви отримаєте анонімний клас, у цьому випадку "TestClass $ 1TestMethodClass.class"
Стів Б.

Так, вибачте! Я не усвідомлював цього лише кілька секунд тому. Ти живеш і вчишся :-))
Кріс Лерчер

У вас є мій +1, щоб виділити різницю між анонімними та локальними класами: визначення конструктора.
Матьє

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, щоб присвоювати функції, що повертаються.
Едді Б

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

Зверніть увагу на останній рядок. Весна робить цей точний «трюк» і для решти методів:

//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());
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.