繁体   English   中英

使用Anonymous类有什么危害?

[英]What's the harm in using Anonymous class?

在阅读这个问题的答案时出现了这个问题 - 如何在java中加入两个列表 这个答案给出了解决方案

List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } };

阅读评论,用户说它是邪恶和丑陋的,不应该用于生产。

我想知道使用它有什么害处? 为什么在生产中使用它是丑陋的,邪恶的还是坏的?


注意:被问到这是一个问题因为,引用的帖子太旧了(2008年),回答者几个月后就离开了。

除了已经提到的关于良好编程风格和继承滥用的问题之外,还有一个更微妙的问题 - 内部类和(非静态)匿名类实例充当闭包。 这意味着它们保持对封闭类实例的隐式引用 这可以防止垃圾收集,最终导致内存泄漏。

给出一段示例源代码:

public interface Inner {
    void innerAction();
}

public class Outer {

    public void methodInOuter() {}

    private Inner inner = new Inner() {
        public void innerAction() {
            // calling a method outside of scope of this anonymous class
            methodInOuter();  
        }
    }
}

在编译时会发生什么,是编译器为Inner的新匿名子类创建一个类文件,它获得了一个所谓的合成字段,其中引用了Outer类的实例。 生成的字节码大致相当于这样的东西:

public class Outer$1 implements Inner {

    private final Outer outer; // synthetic reference to enclosing instance

    public Outer$1(Outer outer) {
        this.outer = outer;
    }

    public void innerAction() {
        // the method outside of scope is called through the reference to Outer
        outer.methodInOuter();
    }
}

即使对于从未实际访问封闭类的任何方法或字段的匿名类,例如您的问题中的双括号初始化(DBI)列表,也会发生对封闭实例的引用的捕获。

这导致DBI列表保留对封闭实例的引用,只要它存在,从而防止封闭实例被垃圾收集。 假设DBI列表恰好在应用程序中存在了很长时间,例如作为MVC模式中模型的一部分,并且捕获的封闭类是例如JFrame ,这是一个包含大量字段的相当大的类。 如果您创建了几个DBI列表,则会很快发生内存泄漏。

一种可能的解决方案是仅在静态方法中使用DBI,因为在其范围内没有可用的封闭实例。

另一方面,我仍然认为在大多数情况下仍然不需要使用DBI。 至于列表加入,我会创建一个简单的可重用方法,它不仅更安全,而且更简洁明了。

public static <T> List<T> join(List<? extends T> first, List<? extends T> second) {
    List<T> joined = new ArrayList<>();
    joined.addAll(first);
    joined.addAll(second);
    return joined;
}

然后客户端代码变得简单:

List<String> newList = join(listOne, listTwo);

进一步阅读: https//stackoverflow.com/a/924536/1064809

“丑陋”和“不在生产中使用”注释指的是匿名类的这种特定用法,而不是一般的匿名类。

这个特定的用法为newList指定newList一个ArrayList<String>的匿名子类,这是一个全新的类,它只是为了一个目的而创建的 - 即初始化一个包含两个特定列表内容的列表。 这不是非常易读(即使是经验丰富的读者也会花费几秒钟来解决它),但更重要的是,它可以在没有子类化的情况下实现相同数量的操作。

从本质上讲,该解决方案为创建新子类带来了一些小便利,这可能会导致问题,例如,当您尝试使用期望集合具有特定类型的自动化框架来持久保存此集合时。

这种匿名类的特殊用法有几个问题:

  1. 这是一个鲜为人知的成语。 在阅读和/或修改使用它的代码时,不了解它(或知道它不会大量使用它)的开发人员将会变慢。
  2. 它实际上是在滥用语言功能:你不是要尝试定义一种新的ArrayList ,你只需要一些包含一些现有值的数组列表
  3. 它创建了一个占用资源的新类:用于保存类定义的磁盘空间,用于解析/验证/ ...的时间,用于保存类定义的permgen,...
  4. 即使“真实代码”稍长,也可以轻松地将其移动到一个恰当命名的实用程序方法( joinLists(listOne, listTwo)

在我看来,#1是避免它的最重要原因,紧随其后的是#2。 #3通常不是一个问题,但不应该被遗忘。

因为您不需要单独的子类 - 您只需要创建普通类的新ArrayList,并将addAll()两个列表添加到其中。

像这样:

public static List<String> addLists (List<String> a, List<String> b) {
    List<String> results = new ArrayList<String>();
    results.addAll( a);
    results.addAll( b); 
    return results;
}

创建一个子类是不好的,这是不需要的。 您不需要扩展或子类化行为 - 只需更改数据值

例如,在性能或其他类似方面,这不是一个糟糕的方法,但这种方法有点模糊,当使用这样的东西时,你总是(比方说,99%)必须解释这种方法。 我认为这是不使用这种方法的最大原因之一,并且在输入时:

List<String> newList = new ArrayList<String>();
newList.addAll(listOne);
newList.addAll(listTwo);

是一个更多的打字,它更容易阅读,这有助于理解或调试代码。

在你的例子中,它看起来真的很邪恶和丑陋 ,至少对我来说 - 很难理解代码中发生了什么。 但是有一些模式使用人们习惯的匿名类,因为他们经常看到它们,例如

    Arrays.sort(args, new Comparator<String>() {
        public int compare(String o1, String o2) {
            return  ... 
        }});

我将上述称为最佳实践案例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM