在Java的隐藏特性中,最上面的答案提到了双大括号初始化,语法非常诱人:
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
这个习惯用法创建了一个匿名的内部类,其中只有一个实例初始化式,它“可以使用任何[…]]方法”。
主要问题:这真的像听起来那么低效吗?它的使用应该仅限于一次性的初始化吗?(当然还有炫耀!)
第二个问题:新的HashSet必须是实例初始化器中使用的“this”…有人能解释一下原理吗?
第三个问题:在产品代码中使用这个习语是否过于晦涩?
非常非常好的回答,谢谢大家。关于问题(3),人们认为语法应该清楚(尽管我建议偶尔注释一下,特别是如果您的代码将传递给可能不熟悉它的开发人员)。
对于问题(1),生成的代码应该快速运行。额外的.class文件确实会导致jar文件混乱,并略微减慢程序启动速度(感谢@coobird测量)。@Thilo指出,垃圾收集可能会受到影响,在某些情况下,额外加载类的内存成本可能是一个因素。
问题(2)对我来说是最有趣的。如果我理解了答案,DBI中发生的事情是匿名内部类扩展了new操作符所构造的对象的类,因此有一个“this”值引用正在构造的实例。非常整洁。
Overall, DBI strikes me as something of an intellectual curiousity. Coobird and others point out you can achieve the same effect with Arrays.asList, varargs methods, Google Collections, and the proposed Java 7 Collection literals. Newer JVM languages like Scala, JRuby, and Groovy also offer concise notations for list construction, and interoperate well with Java. Given that DBI clutters up the classpath, slows down class loading a bit, and makes the code a tad more obscure, I'd probably shy away from it. However, I plan to spring this on a friend who's just gotten his SCJP and loves good natured jousts about Java semantics! ;-) Thanks everyone!
7/2017: Baeldung很好地总结了双大括号初始化,并认为这是一种反模式。
2017年12月12日:@Basil Bourque指出,在新的Java 9中,你可以说:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
这肯定是正确的选择。如果你还在使用以前的版本,可以看看谷歌Collections的ImmutableSet。
加载许多类会在开始时增加几毫秒的时间。如果启动不是那么关键,你看看类启动后的效率,没有区别。
package vanilla.java.perfeg.doublebracket;
import java.util.*;
/**
* @author plawrey
*/
public class DoubleBracketMain {
public static void main(String... args) {
final List<String> list1 = new ArrayList<String>() {
{
add("Hello");
add("World");
add("!!!");
}
};
List<String> list2 = new ArrayList<String>(list1);
Set<String> set1 = new LinkedHashSet<String>() {
{
addAll(list1);
}
};
Set<String> set2 = new LinkedHashSet<String>();
set2.addAll(list1);
Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
{
put(1, "one");
put(2, "two");
put(3, "three");
}
};
Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
map2.putAll(map1);
for (int i = 0; i < 10; i++) {
long dbTimes = timeComparison(list1, list1)
+ timeComparison(set1, set1)
+ timeComparison(map1.keySet(), map1.keySet())
+ timeComparison(map1.values(), map1.values());
long times = timeComparison(list2, list2)
+ timeComparison(set2, set2)
+ timeComparison(map2.keySet(), map2.keySet())
+ timeComparison(map2.values(), map2.values());
if (i > 0)
System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
}
}
public static long timeComparison(Collection a, Collection b) {
long start = System.nanoTime();
int runs = 10000000;
for (int i = 0; i < runs; i++)
compareCollections(a, b);
long rate = (System.nanoTime() - start) / runs;
return rate;
}
public static void compareCollections(Collection a, Collection b) {
if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
throw new AssertionError();
}
}
打印
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
每当有人使用双大括号初始化时,就会有一只小猫被杀死。
除了语法相当不寻常且不是真正的惯用(当然,品味是有争议的)之外,您还不必要地在应用程序中创建了两个重大问题,我最近在博客中对此进行了更详细的讨论。
1. 你创建了太多的匿名类
每次使用双大括号初始化都会创建一个新类。例如这个例子:
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
... 将产生这些类:
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
这对您的类加载器来说是相当大的开销——毫无意义!当然,如果你只做一次,就不会花费太多初始化时间。但是如果你在整个企业应用程序中做了20000次这样的事情……那么多内存只是为了一点“语法糖”?
2. 您可能会造成内存泄漏!
如果您使用上述代码并从一个方法返回该映射,那么该方法的调用者可能会毫无疑问地持有无法被垃圾收集的非常重的资源。考虑下面的例子:
public class ReallyHeavyObject {
// Just to illustrate...
private int[] tonsOfValues;
private Resource[] tonsOfResources;
// This method almost does nothing
public Map quickHarmlessMethod() {
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
return source;
}
}
返回的Map现在将包含对ReallyHeavyObject的封装实例的引用。你可能不想冒这个险:
图片来自http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
3.你可以假装Java有地图字面量
为了回答你的实际问题,人们一直在使用这种语法来假装Java有类似map字面量的东西,类似于现有的数组字面量:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
有些人可能会觉得这在语法上很刺激。