Java“双支撑初始化”的效率?

本文翻译自:Efficiency of Java “Double Brace Initialization”?

In Hidden Features of Java the top answer mentions Double Brace Initialization , with a very enticing syntax: Java隐藏功能中 ,最佳答案提到了Double Brace Initialization ,它具有非常诱人的语法:

Set<String> flavors = new HashSet<String>() {{
    add("vanilla");
    add("strawberry");
    add("chocolate");
    add("butter pecan");
}};

This idiom creates an anonymous inner class with just an instance initializer in it, which "can use any [...] methods in the containing scope". 这个成语创建了一个匿名内部类,其中只包含一个实例初始化程序,“可以使用包含作用域中的任何[...]方法”。

Main question: Is this as inefficient as it sounds? 主要问题:这听起来效率低吗? Should its use be limited to one-off initializations? 它的使用是否应限于一次性初始化? (And of course showing off!) (当然炫耀!)

Second question: The new HashSet must be the "this" used in the instance initializer ... can anyone shed light on the mechanism? 第二个问题:新的HashSet必须是实例初始化程序中使用的“this”...任何人都可以了解机制吗?

Third question: Is this idiom too obscure to use in production code? 第三个问题:在生产代码中使用这个成语是否过于模糊

Summary: Very, very nice answers, thanks everyone. 简介:非常非常好的答案,谢谢大家。 On question (3), people felt the syntax should be clear (though I'd recommend an occasional comment, especially if your code will pass on to developers who may not be familiar with it). 在问题(3)中,人们认为语法应该是清楚的(尽管我建议偶尔发表评论,特别是如果你的代码会传递给可能不熟悉它的开发人员)。

On question (1), the generated code should run quickly. 在问题(1)上,生成的代码应该快速运行。 The extra .class files do cause jar file clutter, and slow program startup slightly (thanks to @coobird for measuring that). 额外的.class文件会导致jar文件混乱,并且会稍微减慢程序启动速度(感谢@coobird测量它)。 @Thilo pointed out that garbage collection can be affected, and the memory cost for the extra loaded classes may be a factor in some cases. @Thilo指出垃圾收集可能会受到影响,在某些情况下,额外加载类的内存成本可能是一个因素。

Question (2) turned out to be most interesting to me. 问题(2)对我来说最有趣。 If I understand the answers, what's happening in DBI is that the anonymous inner class extends the class of the object being constructed by the new operator, and hence has a "this" value referencing the instance being constructed. 如果我理解答案,那么DBI中发生的事情是匿名内部类扩展了由new运算符构造的对象的类,因此具有引用正在构造的实例的“this”值。 Very neat. 井井有条。

Overall, DBI strikes me as something of an intellectual curiousity. 总的来说,DBI让我感到非常好奇。 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. Coobird和其他人指出,您可以使用Arrays.asList,varargs方法,Google Collections和提议的Java 7 Collection文字获得相同的效果。 Newer JVM languages like Scala, JRuby, and Groovy also offer concise notations for list construction, and interoperate well with Java. Scala,JRuby和Groovy等较新的JVM语言也为列表构建提供了简明的符号,并且与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. 鉴于DBI使类路径混乱,减慢了类加载速度,并使代码更加模糊,我可能会回避它。 However, I plan to spring this on a friend who's just gotten his SCJP and loves good natured jousts about Java semantics! 但是,我打算在一位刚刚获得SCJP的朋友身上发表这篇文章,并且喜欢关于Java语义的好朋友! ;-) Thanks everyone! ;-) 感谢大家!

7/2017: Baeldung has a good summary of double brace initialization and considers it an anti-pattern. 7/2017:Baeldung对双支撑初始化有很好的总结 ,并认为它是一种反模式。

扫描二维码关注公众号,回复: 10784955 查看本文章

12/2017: @Basil Bourque notes that in the new Java 9 you can say: 12/2017:@Basil Bourque指出,在新的Java 9中你可以说:

Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");

That's for sure the way to go. 这肯定是要走的路。 If you're stuck with an earlier version, take a look at Google Collections' ImmutableSet . 如果您遇到早期版本,请查看Google Collections的ImmutableSet


#1楼

参考:https://stackoom.com/question/3sRp/Java-双支撑初始化-的效率


#2楼

I was researching this and decided to do a more in depth test than the one provided by the valid answer. 我正在研究这个并决定进行比有效答案提供的更深入的测试。

Here is the code: https://gist.github.com/4368924 这是代码: https//gist.github.com/4368924

and this is my conclusion 这是我的结论

I was surprised to find that in most of the run tests the internal initiation was actually faster (almost double in some cases). 我惊讶地发现,在大多数运行测试中,内部启动实际上更快(在某些情况下几乎翻倍)。 When working with large numbers the benefit seems to fade away. 当大量工作时,好处似乎逐渐消失。

Interestingly, the case that creates 3 objects on the loop loses it's benefit rans out sooner than on the other cases. 有趣的是,在循环中创建3个对象的情况失去了它的好处,比其他情况更快。 I am not sure why this is happening and more testing should be done to reach any conclusions. 我不确定为什么会这样,应该做更多的测试来得出任何结论。 Creating concrete implementations may help to avoid the class definition to be reloaded (if that's what's happening) 创建具体实现可能有助于避免重新加载类定义(如果这是正在发生的事情)

However, it is clear that not much overhead it observed in most cases for the single item building, even with large numbers. 然而,很明显,在大多数情况下,即使数量很大,单个项目构建也没有太大的开销。

One set back would be the fact that each of the double brace initiations creates a new class file that adds a whole disk block to the size of our application (or about 1k when compressed). 一个例子是每个双支撑启动创建一个新的类文件,它将整个磁盘块添加到我们的应用程序的大小(或压缩时大约1k)。 A small footprint, but if it's used in many places it could potentially have an impact. 占地面积小,但如果在很多地方使用它可能会产生影响。 Use this 1000 times and you are potentially adding a whole MiB to you applicaiton, which may be concerning on an embedded environment. 使用此1000次,您可能会在应用程序中添加整个MiB,这可能与嵌入式环境有关。

My conclusion? 我的结论? It can be ok to use as long as it is not abused. 只要它没有被滥用,它就可以使用。

Let me know what you think :) 让我知道你的想法 :)


#3楼

Loading many classes can add some milliseconds to the start. 加载许多类可以在开始时添加几毫秒。 If the startup isn't so critical and you are look at the efficiency of classes after startup there is no difference. 如果启动不是那么关键,你在启动后看到类的效率,没有区别。

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();
    }
}

prints 版画

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

#4楼

Mario Gleichman describes how to use Java 1.5 generic functions to simulate Scala List literals, though sadly you wind up with immutable Lists. Mario Gleichman 描述了如何使用Java 1.5泛型函数来模拟Scala List文字,尽管遗憾的是你最终得到了不可变列表。

He defines this class: 他定义了这个类:

package literal;

public class collection {
    public static <T> List<T> List(T...elems){
        return Arrays.asList( elems );
    }
}

and uses it thusly: 并因此使用它:

import static literal.collection.List;
import static system.io.*;

public class CollectionDemo {
    public void demoList(){
        List<String> slist = List( "a", "b", "c" );
        List<Integer> iList = List( 1, 2, 3 );
        for( String elem : List( "a", "java", "list" ) )
            System.out.println( elem );
    }
}

Google Collections, now part of Guava supports a similar idea for list construction. Google Collections现在是Guava的一部分,它支持列表构建的类似想法。 In this interview , Jared Levy says: 这次访谈中 ,Jared Levy说:

[...] the most heavily-used features, which appear in almost every Java class I write, are static methods that reduce the number of repetitive keystrokes in your Java code. [...]最常用的功能,几乎出现在我编写的每个Java类中,都是静态方法,可以减少Java代码中重复键击的次数。 It's so convenient being able to enter commands like the following: 能够输入如下命令非常方便:

Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();

List<String> animals = Lists.immutableList("cat", "dog", "horse");

7/10/2014: If only it could be as simple as Python's: 2014年7月10日:如果它只是像Python那样简单:

animals = ['cat', 'dog', 'horse']


#5楼

While this syntax can be convenient, it also adds a lot of this$0 references as these become nested and it can be difficult to step debug into the initializers unless breakpoints are set on each one. 虽然这种语法很方便,但它也会添加很多这个$ 0引用,因为它们会嵌套,除非在每个引用上设置断点,否则很难将步骤调试到初始化器中。 For that reason, I only recommend using this for banal setters, especially set to constants, and places where anonymous subclasses don't matter (like no serialization involved). 出于这个原因,我只推荐将它用于平庸的setter,特别是设置为常量,以及匿名子类无关紧要的地方(比如没有涉及序列化)。


#6楼

Every time someone uses double brace initialisation, a kitten gets killed. 每当有人使用双支撑初始化时,一只小猫就会被杀死。

Apart from the syntax being rather unusual and not really idiomatic (taste is debatable, of course), you are unnecessarily creating two significant problems in your application, which I've just recently blogged about in more detail here . 除了语法相当不寻常而且不是真正的惯用语(当然味道有争议)之外,您在应用程序中不必要地创建了两个重要问题, 我最近在这里更详细地讨论了这些问题

1. You're creating way too many anonymous classes 你创造了太多的匿名课程

Each time you use double brace initialisation a new class is made. 每次使用双括号初始化时,都会生成一个新类。 Eg this example: 例如这个例子:

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");
        }});
    }});
}};

... will produce these classes: ...将产生这些类:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

That's quite a bit of overhead for your classloader - for nothing! 这对你的类加载器来说是一个相当大的开销 - 什么都不是! Of course it won't take much initialisation time if you do it once. 当然,如果你这样做一次,它将不需要太多的初始化时间。 But if you do this 20'000 times throughout your enterprise application... all that heap memory just for a bit of "syntax sugar"? 但是,如果你在整个企业应用程序中执行此操作20'000次...所有堆内存只是为了一点“语法糖”?

2. You're potentially creating a memory leak! 你可能会造成内存泄漏!

If you take the above code and return that map from a method, callers of that method might be unsuspectingly holding on to very heavy resources that cannot be garbage collected. 如果您使用上面的代码并从方法返回该映射,那么该方法的调用者可能会毫无疑问地保留非常繁重的资源,而这些资源无法进行垃圾回收。 Consider the following example: 请考虑以下示例:

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;
    }
}

The returned Map will now contain a reference to the enclosing instance of ReallyHeavyObject . 返回的Map现在将包含对ReallyHeavyObject的封闭实例的ReallyHeavyObject You probably don't want to risk that: 你可能不想冒这样的风险:

记忆泄漏就在这里

Image from http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/ 图片来自http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. You can pretend that Java has map literals 你可以假装Java有地图文字

To answer your actual question, people have been using this syntax to pretend that Java has something like map literals, similar to the existing array literals: 为了回答你的实际问题,人们一直在使用这种语法假装Java有类似于现有数组文字的地图文字:

String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};

Some people may find this syntactically stimulating. 有些人可能会发现这种语法刺激。

发布了0 篇原创文章 · 获赞 75 · 访问量 56万+

猜你喜欢

转载自blog.csdn.net/w36680130/article/details/105429982