在阅读这个问题 - 如何在Java中连接两个列表的答案时,我产生了一个问题。这个答案提供了解决方案
List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } };
阅读评论,用户们表示这是恶意的、丑陋的,不应在生产中使用。
我想知道使用它有什么危害?为什么在生产中使用它会被认为是丑陋、恶意或不好的?
在阅读这个问题 - 如何在Java中连接两个列表的答案时,我产生了一个问题。这个答案提供了解决方案
List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } };
阅读评论,用户们表示这是恶意的、丑陋的,不应在生产中使用。
我想知道使用它有什么危害?为什么在生产中使用它会被认为是丑陋、恶意或不好的?
除了已经提到的关于良好编程风格和继承误用的问题之外,还存在一个更加微妙的问题 - 内部类和(非静态)匿名类实例充当闭包。这意味着它们保持对封闭类实例的隐式引用。这可能会导致防止垃圾回收,最终导致内存泄漏。
给出源代码的一个示例:
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://dev59.com/pnNA5IYBdhLWcg3wh-YC#924536ArrayList
,而是MyClass$1
)。 - wcharginArrayList.equals()
不会检查类,因为这会破坏 List
的 equals()
契约! - Joachim Sauer"丑陋"和"不要在生产中使用"的评论是指特定使用匿名类的情况,而不是匿名类本身。
这个特定的用法将newList
赋值为ArrayList<String>
的一个匿名子类,完全是为了初始化一个列表,并填充两个具体列表的内容。这种方式不太易读(即使对有经验的读者来说也需要花费几秒钟才能理解),但更重要的是,它可以在同样数量的操作中实现而无需子类化。
实质上,该解决方案通过创建一个新的子类来获得一点方便,而这可能会在以后出现问题,例如,在尝试使用自动化框架来持久化此集合并期望集合具有特定类型的情况下。
使用匿名类的这种特殊用法存在几个问题:
ArrayList
,而只是想要一些带有某些现有值的数组列表。joinLists(listOne, listTwo)
)。在我看来,避免第一点是最重要的原因,紧随其后的是第二点。第三点通常不是那么大的问题,但也不应该被忘记。
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;
}
创建不必要的子类是一种不好的做法。你不需要扩展或者继承行为,只需改变数据值即可。
<T>
替换 <String>
,并在 static
后添加一个 <T>
,这样就可以在所有的列表上运行。另外,加入可变参数会很好,这样你就可以连接任意数量的列表。 - Joachim Sauer这种方法本身并不是不好的,比如在性能方面等,但这种方法有点晦涩难懂,而且使用类似的东西时,你总是(比如说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 ...
}});