116

例子:

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. 方法中的抽象类对我来说听起来有点疯狂。但不允许任何接口。这背后有什么原因吗?
4

7 回答 7

78

这称为本地类。

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

关于接口:我不确定是否存在使本地定义的接口成为编译器问题的技术问题,但即使没有,它们也不会增加任何价值。如果在方法之外使用实现本地接口的本地类,则该接口将毫无意义。如果只在方法内部使用本地类,那么接口和类都将在该方法中实现,因此接口定义将是多余的。

于 2010-03-11T20:15:17.417 回答
17

那些被称为本地类您可以在此处找到详细说明和示例。该示例返回一个特定的实现,我们不需要在方法之外了解它。

于 2010-03-11T20:02:32.737 回答
11
  1. 无法从方法外部看到该类(即实例化,在没有反射的情况下访问其方法)。此外,它可以访问在 testMethod() 中定义的局部变量,但在类定义之前。

  2. 我实际上是在想:“不会写这样的文件。” 直到我尝试过:哦,是的,创建了这样的文件!它将被称为 A$1B.class,其中 A 是外部类,B 是本地类。

  3. 特别是对于回调函数(GUI 中的事件处理程序,例如单击按钮时的 onClick() 等),使用“匿名类”是很常见的——首先,因为您最终会得到很多。但有时匿名类还不够好​​——尤其是,您不能在它们上定义构造函数。在这些情况下,这些方法本地类可能是一个不错的选择。

于 2010-03-11T20:00:56.877 回答
7

这样做的真正目的是允许我们在函数调用中创建内联类,以安慰我们这些喜欢假装我们正在用函数式语言编写的人;)

于 2010-03-11T20:07:45.370 回答
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 );
}

不过,在使用此模式之前,请评估普通的旧顶级类、内部类或静态内部类是否是更好的选择。

于 2010-03-11T20:45:50.670 回答
2

定义内部类(在方法或类中)的主要原因是处理封闭类和方法的成员和变量的可访问性。内部类可以查找私有数据成员并对其进行操作。如果在一个方法中,它也可以处理最终的局部变量。

拥有内部类确实有助于确保外部世界无法访问该类。这尤其适用于 GWT 或 GXT 等 UI 编程的情况,其中 JS 生成代码是用 java 编写的,并且每个按钮或事件的行为必须通过创建匿名类来定义

于 2010-03-11T20:27:33.747 回答
2

我在春天遇到了一个很好的例子。该框架在方法内部使用本地类定义的概念,以统一的方式处理各种数据库操作。

假设您有这样的代码:

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());
        }
    }
于 2020-04-09T17:09:01.273 回答