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에 추가되었습니다. 로컬 클래스도 마찬가지라고 생각하지만 이에 대한 문서가 없습니다.
Jacob Mattison 2014

익명이 아닌 로컬 클래스의 사용 사례에 대한 더 나은 예를 제공 할 수 있습니까? 두 번째 코드 블록은 익명 클래스로 다시 작성할 수 있습니다.
Sergey Pauk 2015

1
비 익명 로컬 클래스의 대부분의 사용은 익명 클래스로 수행 할 수 있습니다. 예제를 구체화하지는 않았지만 동일한 클래스 유형의 인스턴스를 하나 이상 만들어야하는 경우 일반적으로 명명 된 로컬 클래스를 사용합니다.
Jacob Mattison 2015

1
OP의 경우 : 로컬 클래스는 스레드가 통신 parameter할 수있는 방법을 제공합니다. 위 의 내용은 엔 클로징 메서드에서 선언 될 수 있으며 두 스레드에서 모두 액세스 할 수 있습니다.
flow2k

15

이를 로컬 클래스 라고 합니다. 여기 에서 자세한 설명과 예를 찾을 수 있습니다 . 이 예제는 메서드 외부에서 알 필요가없는 특정 구현을 반환합니다.


2
훌륭한 링크 (7 년 이상 후에도 여전히 작동합니다!). 특히 "멤버 클래스와 마찬가지로 로컬 클래스는 포함하는 인스턴스와 연결되며 포함 된 클래스의 개인 멤버를 포함한 모든 멤버에 액세스 할 수 있습니다 ."
flow2k

10
  1. 클래스는 메서드 외부에서 볼 수 없습니다 (즉, 인스턴스화, 리플렉션없이 액세스 된 메서드). 또한 testMethod ()에 정의 된 로컬 변수에 액세스 할 수 있지만 클래스 정의 이전에 있습니다.

  2. 나는 실제로 "그런 파일은 기록되지 않을 것"이라고 생각했습니다. 내가 시도 할 때까지 : 오 예, 그런 파일이 생성되었습니다! A $ 1B.class와 같이 호출되며 A는 외부 클래스이고 B는 로컬 클래스입니다.

  3. 특히 콜백 함수 (버튼을 클릭 할 때 onClick ()와 같은 GUI의 이벤트 처리기)의 경우 "익명 클래스"를 사용하는 것이 일반적입니다. 우선 많은 클래스로 끝날 수 있기 때문입니다. 그러나 때로는 익명 클래스가 충분하지 않습니다. 특히 생성자를 정의 할 수 없습니다. 이러한 경우 이러한 메서드 로컬 클래스가 좋은 대안이 될 수 있습니다.


2
2. 음, 확실해. Java 파일의 모든 중첩, 로컬 또는 익명 클래스에 대해 클래스 파일이 생성됩니다.
sepp2k

2
"2. 그러한 파일은 기록되지 않습니다." -이것은 잘못되었습니다. TestClass$1TestMethodClass.class내부 클래스 .class파일의 이름이 지정 되는 방식과 유사하게을 생성 합니다.
polygenelubricants 2010 년

좋은 대답, 예외 2 : 익명 클래스가 생성됩니다.이 경우에는 "TestClass $ 1TestMethodClass.class"
Steve B.

네, 죄송합니다! 나는 이것을 몇 초 전까지 깨닫지 못했습니다. 당신은 살고 배웁니다 :-))
Chris Lercher

익명 클래스와 로컬 클래스의 차이점을 강조하기위한 +1이 있습니다 : 생성자 정의.
Matthieu

7

이것의 진짜 목적은 함수 호출에서 클래스를 인라인으로 만들어 우리가 함수 언어로 작성하는 것처럼 가장하고 싶어하는 사람들을 위로하는 것입니다.)


4

완전한 기능 내부 클래스와 익명 클래스 (일명 Java 클로저)를 원할 때 유일한 경우는 다음 조건이 충족 될 때입니다.

  1. 인터페이스 또는 추상 클래스 구현을 제공해야합니다.
  2. 함수 호출에 정의 된 몇 가지 최종 매개 변수를 사용하고 싶습니다.
  3. 인터페이스 호출의 실행 상태를 기록해야합니다.

예를 들어 누군가가 a 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

내부 클래스 (메서드 또는 클래스 내에서)를 정의하는 주된 이유는 둘러싸는 클래스 및 메서드의 멤버 및 변수에 대한 액세스 가능성을 처리하기위한 것입니다. 내부 클래스는 개인 데이터 멤버를 조회하고 작업 할 수 있습니다. 메서드 내에서 최종 지역 변수도 처리 할 수 ​​있습니다.

내부 클래스가 있으면이 클래스가 외부 세계에서 액세스 할 수 없도록하는 데 도움이됩니다. 이는 JS 생성 코드가 자바로 작성되고 각 버튼 또는 이벤트에 대한 동작을 익명 클래스를 생성하여 정의해야하는 GWT 또는 GXT 등의 UI 프로그래밍의 경우에 특히 그렇습니다.


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());
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.