【问题标题】:Java (anonymous or not) inner classes: is it good to use them?Java(匿名与否)内部类:使用它们好不好?
【发布时间】:2010-02-17 21:04:35
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: java inner-classes


    【解决方案1】:

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

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

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

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

    【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 根据我的经验,一旦你创建了一个内部类,无论如何你都需要它:)
    • 如果代码被分解成逻辑部分,则更容易理解代码,是的 - 但在 my 书中,这意味着最好定义尽可能接近使用位置的内容,而不是随意将其拆分为自己的文件。 ;)
    • 内部类并不排除你在他们所在的类之外使用它们。枚举是一个很好的例子,枚举通常用于单个类,使其成为公共静态然后你有一个预期使用 Enum 的位置的上下文。工厂模式是内部类生成实现的好地方,而不会污染包命名空间,这些东西只应在工厂对象控制的某些情况下实例化。
    【解决方案3】:

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

    【讨论】:

    • 为什么在基于事件的编程中而不是用于其他用途,例如用于重新组合大量参数的结构,......这更像是一个教条而不是解释:-1。
    【解决方案4】:

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

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

    【讨论】:

    • 至少这个让我微笑,这是公司编码政策中有时愚蠢的规则的一个很好的用法!
    • 事件处理程序没什么特别的,嵌套类的主要目的是封装。
    【解决方案5】:

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

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

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

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

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

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

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

    【讨论】:

    • 这是一个反对内部阶级的好观点。至少在声明一些“标准”行为时。
    • 这是对内部类的愚蠢使用。为什么不直接创建数组列表并添加字符串?展示内部类的愚蠢示例并不会导致所有使用它们都是不好的做法。
    • @ncmathsadist 我不是说它很好,但有些人发现它更具可读性,并在 permgen 不是什么大问题的单元测试中使用它
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

      如果需要方法参数,建议谨慎使用。我刚刚发现与此相关的内存泄漏。它涉及使用 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。

      使用它们但要小心!

      马丁

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

        • 静态内部类?如果它从属于另一个类,则不要使其成为同一文件中的非公共类。无法为侦听器使用内部类意味着要维护痛苦的 getter 链和相互链接的状态变量,这些状态变量只是一个丑陋的散列。
        【解决方案9】:

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

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

        【讨论】:

          【解决方案10】:

          某些框架,如 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;
            }
          }
          

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

          【讨论】:

            【解决方案11】:

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

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

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

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

            【讨论】:

              【解决方案12】:

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

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

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

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

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                  【解决方案14】:

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

                  【讨论】:

                    【解决方案15】:

                    由于其他发帖人给出的原因,我倾向于避免使用非静态内部类。然而,我有一个特别喜欢的模式,非静态内部类可以非常有效地工作:延迟加载有状态类。

                    典型的延迟加载有状态类是用实体 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 功能一样 - 有适当的时间可以使用它们,也有不适当的时间!

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2010-09-26
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-06-05
                      相关资源
                      最近更新 更多