为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

梦里花落0921 2019-06-10 16:52:44
Java“双支撑初始化”的效率?在……里面Java的隐藏特征上面的答案提到双支撑初始化,带着非常诱人的语法:Set<String> flavors = new HashSet<String>() {{     add("vanilla");     add("strawberry");     add("chocolate");     add("butter pecan");}};这个成语创建了一个匿名内部类,其中只有一个实例初始化器,“可以在包含范围内使用任何[.]方法”。主要问题:这是低效就像听起来一样?它的使用应该仅限于一次性初始化吗?(当然还有炫耀!)第二个问题:新的HashSet必须是实例初始化器中使用的“this”。有人能说明这个机制吗?第三个问题:这个成语也是吗?晦涩在生产代码中使用?摘要:非常好的答案谢谢大家。关于问题(3),人们认为语法应该是清晰的(虽然我建议偶尔发表评论,特别是如果您的代码将传递给可能不熟悉它的开发人员)。关于问题(1),生成的代码应该运行得很快。额外的.class文件确实会导致JAR文件混乱,程序启动速度会稍微慢一些(这要感谢@coob税务局来测量)。@Thilo指出垃圾收集可能受到影响,在某些情况下,额外加载类的内存开销可能是一个因素。问题(2)对我来说是最有趣的。如果我理解答案,那么DBI中发生的事情就是匿名内部类扩展了由新操作符构造的对象的类,因此有一个引用正在构造的实例的“this”值。非常整洁。总的来说,DBI给我的印象是一种智力上的好奇。Coobbird和其他人指出,您可以使用Arrays.asList、varargs方法、Google集合和建议的Java 7集合文本实现同样的效果。Scala、JRuby和Groovy等较新的JVM语言也为列表构造提供了简洁的符号,并与Java进行了良好的互操作。考虑到DBI扰乱了类路径,减缓了类的加载速度,并使代码变得更加模糊,我可能会回避它。不过,我打算把这件事发到一个朋友身上,他刚刚拿到了他的SCJP,并且喜欢有关Java语义的善意的争论!;-)谢谢大家!7/2017:Baeldung有一个很好的总结双大括号初始化,并认为它是一个反模式。12/2017:@Basil Bourque指出,在新的Java 9中,您可以说:Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");这是肯定的路要走。如果您被更早的版本所困扰,请看一看Google集合的ImmutableSet.
查看完整描述

3 回答

?
慕沐林林

TA贡献2016条经验 获得超9个赞

当我太沉迷于匿名内部类时,问题是:

2009/05/27  16:35             1,602 DemoApp2$1.class2009/05/27  16:35             1,976 DemoApp2$10.class2009/05/27  16:35
             1,919 DemoApp2$11.class2009/05/27  16:35             2,404 DemoApp2$12.class2009/05/27  16:35           
               1,197 DemoApp2$13.class/* snip */2009/05/27  16:35             1,953 DemoApp2$30.class2009/05/27  16:35   
                         1,910 DemoApp2$31.class2009/05/27  16:35             2,007 DemoApp2$32.class2009/05/27  16:35 
                                       926 DemoApp2$33$1$1.class2009/05/27  16:35           
                                         4,104 DemoApp2$33$1.class2009/05/27  16:35          
                                            2,849 DemoApp2$33.class2009/05/27  16:35            
                                               926 DemoApp2$34$1$1.class2009/05/27  16:35         
                                                   4,234 DemoApp2$34$1.class2009/05/27  16:35        
                                                        2,849 DemoApp2$34.class/* snip */2009/05/27  16:35  
                                                                     614 DemoApp2$40.class2009/05/27  16:35 
2,344 DemoApp2$5.class2009/05/27  16:35            
 1,551 DemoApp2$6.class2009/05/27  16:35            
  1,604 DemoApp2$7.class2009/05/27  16:35            
   1,809 DemoApp2$8.class2009/05/27  16:35            
    2,022 DemoApp2$9.class

这些类都是在我创建一个简单的应用程序时生成的,并且使用了大量的匿名内部类-每个类都将被编译成一个单独的类。class档案。

如前所述,“双大括号初始化”是一个带有实例初始化块的匿名内部类,这意味着为每个“初始化”创建了一个新类,所有这些都是为了创建一个单独的对象。

考虑到Java虚拟机在使用这些类时需要读取所有这些类,这可能导致在字节码验证流程之类的。更不用说存储所有这些磁盘所需的磁盘空间的增加了。class档案。

在使用双大括号初始化时,似乎存在一定的开销,因此太过使用它可能不是一个好主意。但正如埃迪在评论中所指出的那样,不可能完全肯定其影响。


仅供参考,双大括号初始化如下:

List<String> list = new ArrayList<String>() {{
    add("Hello");
    add("World!");}};

它看起来像是Java的“隐藏”特性,但它只是对以下内容的重写:

List<String> list = new ArrayList<String>() {

    // Instance initialization block
    {
        add("Hello");
        add("World!");
    }};

所以基本上是实例初始化块这是匿名内部类.


约书亚·布洛赫收集文字提案钱币工程大致如下:

List<Integer> intList = [1, 2, 3, 4];Set<String> strSet = {"Apple", "Banana", "Cactus"};Map<String, Integer> truthMap = { "answer" : 42 };

可悲的是不顺既不是Java 7,也不是Java 8,被无限期搁置。


实验

这是我测试过的一个简单的实验-制造1000ArrayListS与元素"Hello""World!"通过add方法,使用以下两种方法:

方法1:双支座初始化

List<String> l = new ArrayList<String>() {{
  add("Hello");
  add("World!");}};

方法2:实例化ArrayListadd

List<String> l = new ArrayList<String>();l.add("Hello");l.add("World!");

我创建了一个简单的程序来编写Java源文件,使用以下两种方法执行1000个初始化:

试验1:

class Test1 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    List<String> l1 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    /* snip */

    List<String> l999 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    System.out.println(System.currentTimeMillis() - st);
  }}

试验2:

class Test2 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>();
    l0.add("Hello");
    l0.add("World!");

    List<String> l1 = new ArrayList<String>();
    l1.add("Hello");
    l1.add("World!");

    /* snip */

    List<String> l999 = new ArrayList<String>();
    l999.add("Hello");
    l999.add("World!");

    System.out.println(System.currentTimeMillis() - st);
  }}

请注意,初始化1000所用的时间ArrayListS和扩展的1000个匿名内部类ArrayList将使用System.currentTimeMillis所以计时器没有很高的分辨率。在我的Windows系统上,分辨率大约是15-16毫秒。

两次测试的10次测试结果如下:

Test1 Times (ms)           Test2 Times (ms)----------------           ----------------
           187                          0
           203                          0
           203                          0
           188                          0
           188                          0
           187                          0
           203                          0
           188                          0
           188                          0
           203                          0

可以看出,双大括号初始化的执行时间为190 ms左右。

同时,ArrayList初始化执行时间为0 ms。当然,计时器决议应该考虑在内,但很可能在15毫秒以下。

因此,这两种方法的执行时间似乎有明显的不同。这两种初始化方法确实存在一定的开销。

是的,有1000个.class通过编译Test1双支撑初始化测试程序。


查看完整回答
反对 回复 2019-06-10
?
Helenr

TA贡献1780条经验 获得超3个赞

到目前为止还没有指出这种方法的一个属性是,因为您创建了内部类,所以整个包含类在其作用域中被捕获。这意味着,只要您的集合是活动的,它将保留一个指向包含实例的指针(this$0)和防止垃圾收集,这可能是一个问题。

这一点,以及一个新类首先被创建,即使一个普通的HashSet工作得很好(甚至更好),这使得我不想使用这个构造(尽管我真的很想要语法糖)。

第二个问题:新的HashSet必须是实例初始化器中使用的“this”。有人能说明这个机制吗?我天真地期望“this”指的是初始化“口味”的对象。

这正是内部类的工作方式。他们有自己的this,但是它们也有指向父实例的指针,因此您也可以调用包含对象的方法。在命名冲突的情况下,内部类(在您的示例中是HashSet)优先,但是可以在“this”前面加上一个类名,以获得外部方法。

public class Test {

    public void add(Object o) {
    }

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // HashSet
              Test.this.add("hello"); // outer instance 
            }
        };
    }}

为了弄清楚正在创建的匿名子类,您也可以在其中定义方法。例如,覆盖HashSet.add()

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // not HashSet anymore ...
            }

            @Override
            boolean add(String s){

            }

        };
    }


查看完整回答
反对 回复 2019-06-10
  • 3 回答
  • 0 关注
  • 387 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信