在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
我同意,这看起来太乱了。我检查了我的同事编写的一些代码,并在一种方法中找到了该本地类……这让我感到此模块已完全被污染。
某处某人2015年

7
有时,更多的是隐藏您不需要的东西,而不是看起来的东西;)
对不起

Answers:


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本地类?
雪橇2014年

1
Java 1.1中添加了内部类-我猜本地类也是如此,但是我没有关于它的文档。
Jacob Mattison 2014年

您能否提供一个更好的示例来说明非匿名本地类的用例?您的第二个代码块可以用匿名类重写。
谢尔盖·帕克

1
非匿名本地类的大多数使用都可以通过匿名类来完成。我没有充实该示例,但是如果您需要创建一个以上的同一类类型的实例,则通常使用命名的本地类。
Jacob Mattison 2015年

1
对于OP:请注意,本地类为线程提供了一种通信方式- parameter可以在封闭方法中声明以上内容,并且两个线程均可访问。
flow2k

15

这些被称为本地课程。您可以在此处找到详细的说明和示例。该示例返回一个特定的实现,我们无需在方法之外知道。


2
很棒的链接(超过7年仍然有效!)。尤其要注意“像成员类一样,本地类与包含实例相关联,并且可以访问包含类的任何成员,包括私有成员。”
flow2k

10
  1. 从方法外部无法看到该类(即,实例化了其方法,无需使用Reflection即可访问)。而且,它可以访问在testMethod()中定义的局部变量,但可以在类定义之前。

  2. 我实际上以为:“不会写入任何此类文件。” 直到我尝试为止:哦,是的,这样的文件已创建!它将被称为A $ 1B.class之类的东西,其中A是外部类,而B是本地类。

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


2
2. Ehrm,肯定会。将为您的java文件中的每个嵌套,本地或匿名类生成类文件。
sepp2k 2010年

2
“ 2.将不会写入此类文件。” -这是错误的。它创建TestClass$1TestMethodClass.class,类似于内部类.class文件的命名方式。
polygenelubricants 2010年

好的答案,例外2:您将生成匿名类,在本例中为“ TestClass $ 1TestMethodClass.class”
Steve B. 2010年

是的,很抱歉!直到几秒钟前我才意识到这一点。您生活和学习:
克里斯·勒彻

您有+1可以突出显示匿名类和本地类之间的区别:定义一个构造函数。
Matthieu

7

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


4

当您希望满足以下条件时,唯一的情况是想要拥有完整的函数内部类vs匿名类(又名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 );
}

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


我滥用第二种方法从函数分配返回值。
Eddie

2

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

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


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());
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.