Java中的双括号初始化语法({{ ... }}
)是什么?
Java中的双括号初始化语法({{ ... }}
)是什么?
双括号初始化会创建一个派生自指定类的匿名类(外侧大括号),并在该类中提供了一个初始化块(内部大括号)。例如:
new ArrayList<Integer>() {{
add(1);
add(2);
}};
请注意,使用这种双括号初始化的效果是创建了匿名内部类。所创建的类对周围外部类有一个隐含的this
指针。虽然通常不会出现问题,但在某些情况下(例如序列化或垃圾回收时)可能会引起麻烦,因此值得注意。每当有人使用双大括号初始化法时,就会有一只小猫被杀死。
除了语法相当不寻常且不太习惯(当然,口味是有争议的),你还会在应用程序中不必要地创建两个重大问题,我最近在这里更详细地讨论了这个问题。
每次使用双大括号初始化法都会创建一个新类。例如,这个例子:
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
这会给你的类加载器带来相当大的开销,但却是没有意义的!当然,如果你只做一次初始化,那么它不会花费太多的时间。但如果你在企业应用程序中执行这个动作20,000次......所有这些堆内存只为了一点“语法糖”?
如果你使用上述代码并从一个方法中返回该映射表,则该方法的调用者可能会不知情地持有无法进行垃圾回收的重型资源。请考虑以下示例:
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/
回答您实际的问题,人们一直在使用此语法来假装Java有类似于现有数组字面值的映射字面值:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
有些人可能会发现这在语法上很刺激。
{{...}}
并声明为static
字段,是否仍然是一种有效且不错的静态映射声明方式?这样做不应该会有任何可能的内存泄漏,只有一个匿名类和没有封闭实例引用,对吗? - lorenzo-sMap.of()
来实现这个目的,所以那将是更好的解决方案。 - Lukas EderReallyHeavyObject
。此外,匿名内部类捕获了类体内使用的所有局部变量,因此如果您在初始化集合或地图时不仅使用常量,内部类实例将捕获所有这些变量,并在实际从集合或地图中删除时继续引用它们。因此,在这种情况下,这些实例不仅需要两倍于必要的引用内存,而且在这方面还存在另一个内存泄漏问题。 - HolgerMap source = Map.of("firstName", "John", "lastName", "Smith", "organizations", Map.of("0", Map.of("id", "1234"), "abc", Map.of("id", "5678")))
(自Java 9起),这确实生成一个不可变的映射。 - Holger例如:
public class TestHashMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<String,String>(){
{
put("1", "ONE");
}{
put("2", "TWO");
}{
put("3", "THREE");
}
};
Set<String> keySet = map.keySet();
for (String string : keySet) {
System.out.println(string+" ->"+map.get(string));
}
}
}
它是如何工作的
第一个大括号创建一个新的匿名内部类。这些内部类能够访问其父类的行为。所以,在我们的案例中,我们实际上创建了HashSet类的子类,因此这个内部类能够使用put()方法。
而第二组大括号只是实例初始化器。如果你记得核心Java概念,那么你可以很容易地将实例初始化器块与静态初始化器关联起来,因为它们有相似的结构。唯一的区别是静态初始化器添加了static关键字,并且只运行一次;无论你创建多少对象,它都只运行一次。
关于双括号初始化的有趣应用,请参见这里Dwemthy's Array in Java。
一个摘录
private static class IndustrialRaverMonkey
extends Creature.Base {{
life = 46;
strength = 35;
charisma = 91;
weapon = 2;
}}
private static class DwarvenAngel
extends Creature.Base {{
life = 540;
strength = 6;
charisma = 144;
weapon = 50;
}}
现在,准备好迎接 BattleOfGrottoOfSausageSmells
和 … 厚切培根!
我认为强调Java中不存在"双括号初始化"是很重要的。Oracle网站上没有这个术语。在这个示例中,有两个功能一起使用:匿名类和初始化块。看起来旧的初始化块已经被开发人员遗忘,并在这个主题中造成了一些困惑。Oracle文档的引用:
实例变量的初始化程序块看起来与静态初始化程序块完全相同,但没有static关键字:
{
// whatever code is needed for initialization goes here
}
1- 双括号初始化并不存在:
我想指出的是,双括号初始化并不存在。只存在普通的传统一括号初始化块。第二个括号块与初始化无关。回答中提到这两个括号会初始化某些东西,但实际上不是这样的。
2- 不仅用于匿名类,而是所有类:
几乎所有答案都谈到了在创建匿名内部类时使用该技术。我认为,读者会得出这只用于创建匿名内部类的印象。但实际上,它在所有类中都可以使用。读完这些答案,似乎这是一种全新的专门用于匿名类的特殊功能,我认为这是误导。
3- 目的仅仅是把括号放在一起,并没有新的概念:
此外,这个问题谈论的是第二个开括号紧跟在第一个开括号后的情况。通常,在正常的类中,两个括号之间会有一些代码,但这完全相同。因此,这只是一个括号的问题。因此,我认为我们不应该说这是一种新的令人兴奋的东西,因为这是我们都知道的事情,只是在括号之间写了一些代码。我们不应该创造一个名为“双括号初始化”的新概念。
4- 创建嵌套的匿名类与两个括号无关:
我不同意使用过多匿名类的论点。你并不是因为初始化块创建它们,而只是因为你需要它们。即使没有使用两个括号初始化,这些匿名类也会被创建,因此,即使没有初始化,这些问题仍然会发生... 初始化不是创建初始化对象的因素。
此外,我们不应该谈论使用这个不存在的东西“双括号初始化”或甚至正常的一括号初始化所创建的问题,因为所描述的问题只存在于创建匿名类时,因此与原始问题无关。但是所有答案都会给读者留下这不是创建匿名类的错误,而是这个邪恶(不存在)的“双括号初始化”。
为了避免双括号初始化带来的所有负面影响,如:
做以下事情:
示例:
public class MyClass {
public static class Builder {
public int first = -1 ;
public double second = Double.NaN;
public String third = null ;
public MyClass create() {
return new MyClass(first, second, third);
}
}
protected final int first ;
protected final double second;
protected final String third ;
protected MyClass(
int first ,
double second,
String third
) {
this.first = first ;
this.second= second;
this.third = third ;
}
public int first () { return first ; }
public double second() { return second; }
public String third () { return third ; }
}
使用方法:
MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();
优点:
缺点:
因此,我们拥有了最简单的Java Builder模式。
请在Github上查看所有样例:java-sf-builder-simple-example
MyClass my = new MyClass.Builder().first(1).third("3").create();
这种写法至少和你的变量创建方式一样简单,而且不需要创建匿名子类。同时还可以立即验证值的正确性。 - HolgerMyClass my = new MyClass(); my.first(1); my.third(3);
我认为这种写法更加简单且更优秀。您不需要使用构建器类,从而重要地简化了代码。但是,我肯定不是一个时髦的人。 - AgilePro正如 @Lukas Eder 所指出的,应避免使用双括号初始化集合。
这会创建一个匿名内部类,由于所有内部类都会保留对其父实例的引用,如果这些集合对象被多个对象引用而不仅仅是声明它们的对象,则可能 - 99% 的情况下 - 防止垃圾回收。
Java 9 引入了方便的方法 List.of
、Set.of
和 Map.of
,应该使用它们代替。它们比双括号初始化更快、更高效。
List<Character> characters = new ArrayList<Character>() {
{
for (char c = 'A'; c <= 'E'; c++) add(c);
}
};
Random rnd = new Random();
List<Integer> integers = new ArrayList<Integer>() {
{
while (size() < 10) add(rnd.nextInt(1_000_000));
}
};