Używanie definicji klas wewnątrz metody w Javie


105

Przykład:

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

Dowiedziałem się, że powyższy fragment kodu jest całkowicie legalny w Javie. Mam następujące pytania.

  1. Jaki jest pożytek z posiadania definicji klasy wewnątrz metody?
  2. Czy zostanie wygenerowany plik klasy DummyClass
  3. Trudno mi sobie wyobrazić tę koncepcję w sposób obiektowy. Posiadanie definicji klasy wewnątrz zachowania. Prawdopodobnie ktoś może mi powiedzieć na podstawie równoważnych przykładów ze świata rzeczywistego.
  4. Klasy abstrakcyjne wewnątrz metody brzmią dla mnie trochę szalenie. Ale żadne interfejsy nie są dozwolone. Czy jest jakiś powód?

1
Zgadzam się, wygląda to niesamowicie niechlujnie. Sprawdziłem kod, który napisał mój kolega i znalazłem tę lokalną klasę w metodzie ... po prostu poczułem, że ten moduł jest całkowicie skalany.
Someone Somewhere

7
Czasem bardziej chodzi o to, żeby ukrywać rzeczy, których nie potrzebujesz nigdzie indziej, niż o wygląd;)
sorrymissjackson

Odpowiedzi:


71

Nazywa się to klasą lokalną.

2 jest łatwy: tak, zostanie wygenerowany plik klasy.

1 i 3 to w pewnym sensie to samo pytanie. Użyłbyś klasy lokalnej, w której nigdy nie musisz tworzyć instancji ani wiedzieć o szczegółach implementacji gdziekolwiek, ale w jednej metodzie.

Typowym zastosowaniem byłoby utworzenie jednorazowej implementacji jakiegoś interfejsu. Na przykład często zobaczysz coś takiego:

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

Jeśli potrzebujesz utworzyć kilka z nich i coś z nimi zrobić, możesz zmienić to na

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

Odnośnie interfejsów: nie jestem pewien, czy istnieje problem techniczny, który sprawia, że ​​interfejsy zdefiniowane lokalnie są problemem dla kompilatora, ale nawet jeśli nie ma, nie dodają żadnej wartości. Gdyby klasa lokalna implementująca interfejs lokalny była używana poza metodą, interfejs byłby bez znaczenia. A gdyby klasa lokalna miała być używana tylko wewnątrz metody, zarówno interfejs, jak i klasa zostałyby zaimplementowane w tej metodzie, więc definicja interfejsu byłaby zbędna.


Masz jakiś pomysł, w której wersji lokalnych klas Java została wprowadzona?
Sanki

1
Klasy wewnętrzne zostały dodane w Javie 1.1 - domyślam się, że klasy lokalne też były, ale nie mam na to dokumentacji.
Jacob Mattison

Czy mógłbyś podać lepszy przykład na przykład użycia nieanonimowej klasy lokalnej? Twój drugi blok kodu można przepisać za pomocą klas anonimowych.
Sergey Pauk

1
Większość zastosowań nieanonimowych klas lokalnych można osiągnąć za pomocą klas anonimowych. Nie rozwinąłem tego przykładu, ale zazwyczaj używałbyś nazwanej klasy lokalnej, jeśli chcesz utworzyć więcej niż jedną instancję tego samego typu klasy.
Jacob Mattison,

1
W przypadku OP: zwróć uwagę, że klasa lokalna zapewnia sposób komunikacji wątków - parameterpowyższe może być zadeklarowane w metodzie otaczającej i jest dostępne dla obu wątków.
flow2k

15

Nazywa się to klasami lokalnymi . Szczegółowe wyjaśnienie i przykład można znaleźć tutaj . Przykład zwraca konkretną implementację, o której nie musimy wiedzieć poza metodą.


2
Świetny link (nadal działa po 7+ latach!). W szczególności uwaga: „Podobnie jak klasy składowe, klasy lokalne są powiązane z instancją zawierającą i mogą uzyskać dostęp do wszystkich członków, w tym prywatnych, klasy zawierającej ”.
flow2k

10
  1. Klasy nie można zobaczyć (tj. Utworzyć instancji, jej metody są dostępne bez odbicia) spoza metody. Może również uzyskać dostęp do zmiennych lokalnych zdefiniowanych w testMethod (), ale przed definicją klasy.

  2. Właściwie pomyślałem: „Żaden taki plik nie zostanie zapisany”. dopóki tego nie wypróbowałem: O tak, taki plik jest tworzony! Nazywa się coś w rodzaju A $ 1B.class, gdzie A to klasa zewnętrzna, a B to klasa lokalna.

  3. Zwłaszcza w przypadku funkcji wywołania zwrotnego (obsługi zdarzeń w GUI, takich jak onClick () po kliknięciu przycisku itp.) Dość często używa się „klas anonimowych” - przede wszystkim dlatego, że można uzyskać ich dużo. Ale czasami klasy anonimowe nie są wystarczająco dobre - zwłaszcza, że ​​nie można na nich zdefiniować konstruktora. W takich przypadkach te metody lokalne klasy mogą być dobrą alternatywą.


2
2. Ehrm, na pewno. Pliki klas zostaną wygenerowane dla każdej zagnieżdżonej, lokalnej lub anonimowej klasy w twoim pliku java.
sepp2k

2
"2. Żaden taki plik nie zostanie zapisany." -- To jest źle. Tworzy TestClass$1TestMethodClass.class, analogicznie do tego, jak .classnazywane są pliki klas wewnętrznych .
smary wielogenowe

Dobra odpowiedź, wyjątek dla 2: otrzymasz wygenerowaną anonimową klasę, w tym przypadku „TestClass $ 1TestMethodClass.class”
Steve B.

Tak, przepraszam! Zdawałem sobie z tego sprawę dopiero kilka sekund temu. Żyjesz i uczysz się :-))
Chris Lercher

Masz moje +1 za podkreślenie różnicy między klasami anonimowymi i lokalnymi: definiowanie konstruktora.
Matthieu

7

Prawdziwym celem tego jest umożliwienie nam tworzenia klas wbudowanych w wywołania funkcji, aby pocieszyć tych z nas, którzy lubią udawać, że piszemy w języku funkcjonalnym;)


4

Jedynym przypadkiem, w którym chciałbyś mieć pełnowymiarową klasę wewnętrzną funkcji w porównaniu z klasą anonimową (znaną jako zamknięcie Java), jest spełnienie następujących warunków

  1. musisz dostarczyć interfejs lub implementację klasy abstrakcyjnej
  2. chcesz użyć końcowych parametrów zdefiniowanych w wywołaniu funkcji
  3. musisz zapisać stan wykonania wywołania interfejsu.

Np. Ktoś chce, Runnablea Ty chcesz zarejestrować, kiedy wykonanie rozpoczęło się i zakończyło.

Z klasą anonimową nie jest to możliwe, z klasą wewnętrzną możesz to zrobić.

Oto przykład, który demonstruje mój punkt widzenia

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

Jednak przed użyciem tego wzorca należy ocenić, czy zwykła stara klasa najwyższego poziomu, klasa wewnętrzna lub statyczna klasa wewnętrzna są lepszą alternatywą.


Trochę nadużywam # 2, aby przypisać wartości zwracane przez funkcje.
Eddie B

2

Głównym powodem definiowania klas wewnętrznych (w ramach metody lub klasy) jest zajęcie się dostępnością elementów członkowskich i zmiennych otaczającej klasy i metody. Klasa wewnętrzna może wyszukiwać członków prywatnych danych i operować na nich. Jeśli znajduje się w metodzie, może również obsługiwać końcową zmienną lokalną.

Posiadanie klas wewnętrznych pomaga w upewnieniu się, że ta klasa nie jest dostępna dla świata zewnętrznego. Dotyczy to zwłaszcza przypadków programowania interfejsu użytkownika w GWT lub GXT itp., Gdzie kod generujący JS jest napisany w java, a zachowanie dla każdego przycisku lub zdarzenia musi być zdefiniowane przez utworzenie anonimowych klas


1

Wiosną trafiłem na dobry przykład. Framework wykorzystuje koncepcję lokalnych definicji klas wewnątrz metody, aby w jednolity sposób radzić sobie z różnymi operacjami na bazie danych.

Załóżmy, że masz taki kod:

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

Najpierw spójrzmy na implementację 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());
    }

Zwróć uwagę na ostatnią linię. Spring wykonuje tę samą sztuczkę w przypadku pozostałych metod:

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

„Sztuczka” z klasami lokalnymi umożliwia platformie radzenie sobie ze wszystkimi scenariuszami w jednej metodzie, która akceptuje te klasy za pośrednictwem interfejsu StatementCallback. Ta pojedyncza metoda działa jako pomost między akcjami (wykonanie, aktualizacja) i typowymi operacjami wokół nich (np. Wykonywanie, zarządzanie połączeniami, translacja błędów i wyjście konsoli 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());
        }
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.