Java(匿名与否)内部类:使用它们是否合适?

问题描述 投票:20回答:15

在我的一些项目和一些书中据说不使用内部类(匿名与否,静态与否) - 除了在某些限制条件下,如EventListeners或Runnables--是最佳实践。他们甚至在我的第一个行业项目中都是“禁止”。

这真的是最佳做法吗?为什么?

(我不得不说我经常使用它们......)

- 编辑--- 我不能在所有这些答案中选择一个正确的答案:大部分都是正确的部分:我仍然会使用内部类,但我会尽量少用它们!

java inner-classes
15个回答
26
投票

在我看来,Java代码中90%的内部类是与单个类关联的实体,因此被“推入”作为内部类,或者因为Java不支持Lambdas而存在的匿名内部类。

我个人不喜欢看复杂的内部课程。它们增加了源文件的复杂性,它们使它更大,在调试和分析等方面处理它们很难。我喜欢将我的项目分成许多包,在这种情况下我可以使大多数实体成为顶级类仅限于包装。

这给我留下了必要的内部类 - 例如动作听众,虚假的“功能”编程等等。这些通常是匿名的,虽然我不是粉丝(在许多情况下会更喜欢Lambda),但我和他们住在一起但是不要不喜欢他们。

我多年没有做过任何C#,但我想知道在引入Lambdas时内部类的流行程度或C#等价物的流行程度是否会被删除。


1
投票

在尝试模拟多重继承时,内部类是合适的。它类似于使用C ++在幕后发生的事情:当你在C ++中有多个继承时,内存中的对象布局实际上是几个对象实例的串联;然后编译器计算出在调用方法时如何调整“this”指针。在Java中,没有多重继承,但内部类可用于在另一种类型下提供给定实例的“视图”。

大多数情况下,可以坚持单继承,但偶尔使用多继承是正确的工具,这是使用内部类的时候。

这意味着内部类在某种程度上比通常的类更复杂,就像多继承比单继承更复杂一样:许多程序员在围绕这个概念时都会遇到一些麻烦。因此,“最佳实践”:避免内部课程,因为它会让你的同事感到困惑。在我看来,这不是一个好的论据,在我的工作场所,当我们认为合适时,我们很乐意使用内部课程。

(内部类的一个小缺点是它们在源代码中添加了一个额外级别的缩进。当有人希望将代码保留在79列中时,这有点令人厌烦。)


1
投票

当我们需要使用一个方法(如Runnable,ActionListener和其他方法)实现接口时,通常会使用匿名内部类。

匿名内部类的另一个优秀设备是,当您不想创建某个类的子类但需要覆盖其中一个(或两个)方法时。

当您希望在两个类之间实现紧密一致时,可以使用命名的内部类。它们不像匿名内部类那么有用,我不能确定它是一个好用的做法。

Java还有嵌套(或内部静态)类。当您想要提供某些特殊访问权限并且标准公共或默认访问级别不够时,可以使用它们。


1
投票

内部类通常用于“传递行为”作为方法的参数。具有闭包的其他语言以优雅的方式支持此功能。由于语言的限制,使用内部类会产生一些不优雅的代码(恕我直言),但它很有用并且广泛用于处理事件和blocks一般与内部类。

所以我会说内部类非常有用。


1
投票

没有内部类的代码更易于维护和读取。当您从内部类访问外部类的私有数据成员时,JDK编译器在外部类中创建package-access成员函数,以便内部类访问private members。这留下了一个安全漏洞。一般来说,我们应该避免使用内部类。

仅当内部类仅与外部类的上下文相关时才使用内部类,并且/或者内部类可以设置为私有,以便只有外部类可以访问它。内部类主要用于实现辅助类,如迭代器,比较器等,它们在外部类的上下文中使用。


0
投票

是的,使用它们是好的,当你试图保持一个类的内聚,并且这些类永远不应该从外部类的上下文之外实例化,使构造函数成为私有的,并且你有非常好的内聚封装。任何说你永远不应该使用它们的人都不知道他们在说什么。对于事件处理程序和匿名内部类擅长的其他事情,它们比使用大量仅适用于特定类的事件处理程序来混淆包命名空间的方法更好。


0
投票

由于其他海报给出的原因,我倾向于避免使用非静态内部类。但是我有一个特别喜欢的模式,其中非静态内部类非常有效地工作:延迟加载有状态类。

典型的延迟加载有状态类使用实体ID构造,然后按需可以延迟加载其他实体信息。通常,为了延迟加载附加信息,我们将需要依赖项。但依赖+状态==反模式!

非静态内部类提供了一种避免这种反模式的方法。希望以下简单示例比单词更好地说明这一点:

/*
 * Stateless outer class holding dependencies
 */
public class DataAssembler {
  private final LoadingService loadingService;

  @Inject
  DataAssembler(LoadingService loadingService) {
    this.loadingService = loadingService;
  }

  public LazyData assemble(long id) {
    return new LazyData(id);
  }

  /*
   * Stateful non-static inner class that has access to the outer
   * class' dependencies in order to lazily load data.
   */
  public class LazyData {
    private final long id;

    private LazyData(long id) {
      this.id = id;
    }

    public long id() {
      return id;
    }

    public String expensiveData() {
      return loadingService.buildExpensiveDate(id);
    }
  }
}

值得注意的是,除了上面的例子之外还有许多其他模式,其中内部类是有用的;内部类与任何其他Java功能一样 - 适当的时候可以使用它们并且不合适的时间!


17
投票

清洁度。如果代码被分解为逻辑部分,而不是所有代码都存储在同一个文件中,则更容易理解代码。

也就是说,我不认为明智地使用内部类是不合适的。有时这些内部类只存在于一个目的,因此我可以将它们存在于使用它们的唯一文件中。但是,根据我的经验,这并没有发生太多。


6
投票

在进行基于事件的编程时,特别是在swing中,匿名类很好用。


4
投票

是的,禁止内部课程是一种有用的做法,因为找到一个禁止他们的地方是警告我在那里工作的好方法,因此保留了我未来的理智。 :)

正如gicappa所指出的,匿名内部类是最接近Java的闭包,并且非常适合在将行为传递到方法中的情况下使用,如果没有别的话。


4
投票

正如其他人所说,很多时候,当你使用一个匿名的内部类时,它也被用在其他一些地方......

因此,您可以轻松地将内部类代码复制到许多地方......当您使用非常简单的内部类来过滤/排序集合时,使用谓词,比较器或类似的东西时,这似乎不是问题...

但是你必须知道,当你使用3次匿名内部类完全相同的事情时(例如删除集合的“”),你实际上在java PermGen上创建了3个新类。

因此,如果每个人都到处使用内部类,这可能会导致应用程序具有更大的permgen。根据应用程序,这可能是一个问题...如果您正在从事该行业,您可以编写具有有限内存的嵌入式应用程序,应该进行优化...

请注意,这也是为什么双花括号语法(带有非静态初始化块的匿名内部类)有时被视为反模式的原因:

new ArrayList<String>() {{
     add("java");
     add("jsp");
     add("servlets");
  }}

你应该问那些禁止你使用它们的人...恕我直言,这完全取决于具体情况......


3
投票

匿名内部类具有能够查看“新”语句周围的字段和变量的好处。这可以使一些非常干净的设计,并且是一个非常好(但有点罗嗦)的方法“我们如何制作简单版本的lambda语句”。

命名内部类的好处是有一个名称,希望能够以通常的方式记录,但是它与周围的类联系在一起。一个非常好的例子是Builder模式,其中内部类负责为初始化过程提供状态,而不是拥有众多构造函数。这样的构建器不能在类之间重用,因此将Builder与父类紧密结合是完全合理的。


3
投票

如果需要方法参数,我建议在使用它时要谨慎。我刚刚发现了与此相关的内存泄漏。它涉及使用GrizzlyContinuation的HttpServlet。 总之,这里是错误的代码:

public void doGet(HttpServletRequest request, final HttpServletResponse response){
  createSubscription(..., new SubscriptionListener(){
    public void subscriptionCreated(final CallController controller) {
      response.setStatus(200);
      ...
      controller.resume();
    }

    public void subscriptionFailed(){
       ...
     }

    public void subscriptionTimeout(){
      ...
  }});
}

因此,由于监听器是由订阅保留的,所以HttpServletResponse也会被保留,以防听众需要它(不明显)。然后只有在删除订阅时才会释放HttpServletResponse实例。如果您使用在其构造函数中获取响应的内部类,则一旦调用恢复释放内存,就可以将其设置为null。

使用它们但要小心!

马丁


2
投票

某些框架,如Wicket,确实需要匿名内部类。

说永远不会是愚蠢的。永远不要把话说绝了!良好使用的一个例子可能是你有一些遗留代码是由许多类直接在Collection字段上运行的人编写的,无论出于何种原因,你不能改变其他类,但需要有条件地将操作镜像到另一个采集。最简单的方法是通过匿名内部类添加此行为。

bagOfStuff = new HashSet(){
  @Override
  public boolean add(Object o) {
    boolean returnValue = super.add(o);
    if(returnValue && o instanceof Job)
    {
      Job job = ((Job)o);
      if(job.fooBar())
         otherBagOfStuff.add(job);
    }
    return returnValue;
  }
}

也就是说,他们绝对可以像穷人的关闭一样使用。


2
投票

这里没有提到的一个项目是(非静态)内部类带有对它的封闭类的引用。更重要的是,内部类可以访问其封闭类的私有成员。它可能会破坏封装。

如果你有选择,不要使用内部类。

© www.soinside.com 2019 - 2024. All rights reserved.