【问题标题】:Use of class definitions inside a method in Java在 Java 的方法中使用类定义
【发布时间】:2011-01-26 13:22:36
【问题描述】:

例子:

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

【问题讨论】:

  • 我同意,它看起来非常混乱。我检查了我同事写的一些代码,并在一个方法中发现了这个本地类……这让我觉得这个模块完全被玷污了。
  • 有时更重要的是隐藏你在别处不需要的东西,而不是看起来;)

标签: java class local-class


【解决方案1】:

这称为本地类。

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 本地类中引入了吗?
  • Java 1.1 中添加了内部类——我猜本地类也是如此,但我没有这方面的文档。
  • 您能否提供一个更好的示例来说明非匿名本地类的用例?您的第二个代码块可以用匿名类重写。
  • 大多数非匿名本地类的使用都可以通过匿名类来完成。我没有详细说明这个示例,但如果您需要创建多个相同类类型的实例,您通常会使用命名的本地类。
  • 对于 OP:请注意,本地类为线程提供了一种通信方式 - 上面的 parameter 可能在封闭方法中声明,并且可以被两个线程访问。
【解决方案2】:

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

【讨论】:

  • 很好的链接(7 年后仍然有效!)。特别要注意“与成员类一样,本地类与包含实例相关联,并且可以访问包含类的任何成员,包括私有成员。”
【解决方案3】:
  1. 无法从方法外部看到该类(即实例化,其方法在没有反射的情况下访问)。此外,它可以访问在 testMethod() 中定义的局部变量,但在类定义之前。

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

  3. 1234563其中。但有时匿名类还不够好​​——尤其是,您不能在它们上定义构造函数。在这些情况下,这些方法本地类可能是一个不错的选择。

【讨论】:

  • 2.嗯,肯定会的。将为您的 java 文件中的每个嵌套、本地或匿名类生成类文件。
  • "2. 不会​​写入这样的文件。" ——这是错误的。它创建TestClass$1TestMethodClass.class,类似于内部类.class 文件的命名方式。
  • 好答案,例外 2:您将生成匿名类,在本例中为“TestClass$1TestMethodClass.class”
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    您希望拥有完整的函数内部类与匿名类(也称为 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 来分配函数的返回值。
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

      假设你有这样的代码:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-07
        • 1970-01-01
        • 1970-01-01
        • 2021-01-24
        相关资源
        最近更新 更多