24

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

这真的是最佳实践吗?为什么?

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

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

4

15 回答 15

32

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

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

这给我留下了必要的内部类——比如动作监听器、虚假的“函数式”编程等。这些通常是匿名的,虽然我不是粉丝(在很多情况下会更喜欢 Lambda),但我和他们住在一起,但不要不喜欢他们。

多年来我没有做过任何 C#,但我想知道当他们引入 Lambdas 时,内部类或任何 C# 等价物的流行是否会下降。

于 2010-02-17T21:24:41.530 回答
17

清洁度。如果代码被分解成逻辑部分,而不是全部混入同一个文件,则更容易理解代码。

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

于 2010-02-17T21:12:40.363 回答
6

匿名类在进行基于事件的编程时非常适合使用,尤其是在摇摆中。

于 2010-02-17T21:12:55.457 回答
5

是的,禁止内部类是一种有用的做法,因为找到一个禁止它们的地方是警告我不要在那里工作的好方法,从而保持我未来的理智。:)

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

于 2010-02-18T11:05:03.563 回答
4

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

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

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

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

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

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

你应该问那些禁止你使用它们的人......恕我直言,这一切都取决于上下文......

于 2011-08-16T10:52:09.203 回答
3

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

命名内部类的好处是有一个名字,希望能说明问题,它可以用通常的方式记录下来,但它与周围的类联系在一起。一个很好的例子是 Builder 模式,其中内部类负责为初始化过程提供状态,而不是拥有大量的构造函数。这样的构建器不能在类之间重用,因此将 Builder 与父类紧密绑定是非常有意义的。

于 2010-02-18T03:30:51.200 回答
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。

使用它们,但要小心!

马丁

于 2011-11-30T22:40:17.850 回答
3

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

如果可以选择,请不要使用内部类。

于 2012-01-18T17:31:38.170 回答
3

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

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

于 2018-03-16T09:28:38.973 回答
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;
  }
}

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

于 2010-11-01T19:48:17.297 回答
1

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

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

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

(内部类的一个小缺点是它们在源代码中添加了一层额外的缩进。有时这有点令人讨厌,当人们想要将代码保持在 79 列内时。)

于 2010-02-17T21:22:05.893 回答
1

当我们需要使用一种方法实现接口时,通常会使用匿名内部类,例如 Runnable、ActionListener 等。

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

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

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

于 2010-02-17T21:24:48.677 回答
1

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

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

于 2010-02-17T22:27:06.740 回答
0

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

于 2010-02-17T21:56:18.050 回答
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 特性一样 - 有适当的时间可以使用它们,也有不适当的时间!

于 2019-02-16T03:41:03.943 回答