不要“聪明”:双曲括号反模式

我不时发现有人在野外使用双花括号反模式(也称为双花括号初始化 )。 这次是堆栈溢出

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

如果您不了解语法,实际上很简单。 有两个元素:

  1. 我们正在创建匿名类 ,通过编写以下内容扩展HashMap
    new HashMap() {
    }
  2. 在该匿名类中,我们使用实例初始化程序通过编写以下内容来初始化新的匿名HashMap子类型实例:
    {
        put("id", "1234");
    }

    本质上,这些初始化程序只是构造函数代码。

因此,为什么将其称为“双曲括号反模式”

确实有三个原因使之成为反模式:

1.可读性

这是最不重要的原因,它是可读性。 尽管编写起来可能会更容易一些,并且感觉更像JSON中的等效数据结构初始化:

{
  "firstName"     : "John"
, "lastName"      : "Smith"
, "organizations" : 
  {
    "0"   : { "id", "1234" }
  , "abc" : { "id", "5678" }
  }
}

是的。 如果Java具有ListMap类型的集合文字,那将真是太棒了。 从句法上讲,使用双花括号进行模拟很奇怪,而且感觉不太正确。

但是,让我们离开讨论口味和花括号的区域( 我们之前已经做过 ),因为:

2.每个实例一种类型

我们实际上是为每个实例创建一种类型! 每次以这种方式创建新地图时,我们也会隐式地为该HashMap一个简单实例创建一个新的不可重用类。 如果您只执行一次,那可能很好。 如果将此类代码放在整个大型应用程序中,则会给ClassLoader带来不必要的负担, ClassLoader会将对所有这些类对象的引用保留在堆上。 不相信吗? 编译以上代码,并检出编译器输出。 它看起来像这样:

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

如果Test.class是此处唯一合理的类, Test.class封闭类。

但这仍然不是最重要的问题。

3.内存泄漏!

真正最重要的问题是所有匿名类都有的问题。 它们包含对其封闭实例的引用,这确实是一个杀手.。 想象一下,您将巧妙的HashMap初始化放入了EJB或具有良好管理的生命周期的任何真正重的对象中,如下所示:

public class ReallyHeavyObject {

    // Just to illustrate...
    private int[] tonsOfValues;
    private Resource[] tonsOfResources;

    // This method almost does nothing
    public void 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");
                }});
            }});
        }};
        
        // Some more code here
    }
}

因此,此ReallyHeavyObject拥有大量资源,这些资源一旦被垃圾回收或其他原因就需要正确清理。 但是,当您调用quickHarmlessMethod() (这将立即执行quickHarmlessMethod() ,这对您来说并不重要。

精细。

让我们想象一下其他一些开发人员,他们重构该方法以返回您的地图甚至地图的一部分:

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

现在您遇到了大麻烦! 您现在已经无意间将ReallyHeavyObject所有状态暴露给外部,因为这些内部类中的每一个都持有对封闭实例(即ReallyHeavyObject实例)的引用。 不相信吗? 让我们运行这个程序:

public static void main(String[] args) throws Exception {
    Map map = new ReallyHeavyObject().quickHarmlessMethod();
    Field field = map.getClass().getDeclaredField("this$0");
    field.setAccessible(true);
    System.out.println(field.get(map).getClass());
}

该程序返回:

class ReallyHeavyObject

确实是的! 如果仍然不相信,可以使用调试器对返回的map进行自省:

调试输出1

您将在匿名HashMap子类型中看到封闭的实例引用。 并且所有嵌套的匿名HashMap子类型也都具有这种引用。

所以,请不要使用这种反模式

您可能会说,规避问题3的所有麻烦的一种方法是使quickHarmlessMethod()成为防止该封闭实例的静态方法,对此您是正确的。

但是,我们在上面的代码中见过的最糟糕的事情是,即使你知道与你的地图,你可能在一个静态的背景下创建做什么的事实,未来开发商可能没有注意到这一点,重构/删除static再次。 他们可能将Map存储在其他单例实例中,从字面上看,没有办法从代码本身中得知可能只有对ReallyHeavyObject一个悬ReallyHeavyObject用的引用。

内部阶级是野兽。 过去,它们造成了很多麻烦和认知失调。 匿名内部类甚至更糟,因为此类代码的读者可能真的完全忽略了他们正在封装一个外部实例并绕过这个封闭的外部实例这一事实。

结论是:

不要聪明,不要使用双花括号初始化

翻译自: https://www.javacodegeeks.com/2014/12/dont-be-clever-the-double-curly-braces-anti-pattern.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值