使用Anonymous类有什么危害?

时间:2013-05-06 10:43:00

标签: java anonymous-class

在阅读question - How do I join two lists in java的答案时出现了问题。这个answer提供了解决方案

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

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

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


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

6 个答案:

答案 0 :(得分:47)

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

给出一段示例源代码:

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

答案 1 :(得分:18)

&#34;丑陋&#34;并且&#34;不要在生产中使用&#34;注释是指匿名类的这种特定用法,而不是一般的匿名类。

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

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

答案 2 :(得分:16)

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

  1. 这是一个鲜为人知的习语。在阅读和/或修改使用它的代码时,不了解它(或知道它不会大量使用它)的开发人员将会变慢。
  2. 它实际上误用了一种语言功能:你不是要尝试定义一种新的ArrayList,你只是想要一些包含一些现有值的数组列表
  3. 它创建了一个占用资源的新类:用于保存类定义的磁盘空间,用于解析/验证/ ...的时间,用于保存类定义的permgen,...
  4. 即使“真实代码”稍微长一点,也可以很容易地将其移动到一个恰当命名的实用工具方法(joinLists(listOne, listTwo)
  5. 在我看来,#1是避免它的最重要原因,紧随其后的是#2。 #3通常不是一个问题,但不应该被遗忘。

答案 3 :(得分:4)

因为你不需要一个单独的子类 - 你只需要创建一个普通类的新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;
}

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:1)

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

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

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