切勿自作聪明,慎用双括号反模式

我经常发现有人会使用双括号这个反模式来进行编程(也被称为[url=http://stackoverflow.com/q/27193550/521799]双括号初始化](http://stackoverflow.com/q/1958636/521799))。[Stack Overflow上刚刚又有一个[/url]:




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



在该匿名类中,我们通过实例初始化器(instance initializer)来将这个HashMap的匿名子类进行初始化:



{
put("id", "1234");
}



这些初始化器其实就对应于构造方法里面的代码。

[b]为什么说它是双括号反模式(Double Curly Braces Anti Pattern)?[/b]

说它是一个反模式主要出于三方面的原因:

1. 可读性

可读性是最次要的一个原因。尽管它使得程序编写变得更简单,并且看起来跟JSON中数据结构的初始化差不多:




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



没错。[url=http://blog.jooq.org/2012/06/01/array-list-set-map-tuple-record-literals-in-java/]如果Java的List和Map类型能有集合字面量就实在是太好了[/url]。通过双括号来模拟的话总显得有点怪怪的,有什么不对劲。

不过这个就当作是个人喜好吧,我们先暂且搁下不谈,因为还有更重要的原因:

2. 一个实例,一种类型

通过一次双括号的初始化我们其实就已经创建了一个新类型了!通过这种方式所生成的每一个新map,都会隐式地创建了一个无法重复使用的新类型。如果仅用一次的话也无可厚非。但如果在一个大型的应用中到处都充斥着这种代码的话,无形中会给你的类加载器增加了许多负担,你的堆会持有着这些类的引用。不信么?编译下上述代码并查看下编译器的输出。大概会是这样的:




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



这里只有最外围的Test.class是有意义的。

不过这还不是最重要的问题。

3. 内存泄露!

最重要的问题就是匿名类所造成的。它们持有着外围实例的引用,这简直就是个定时ZHADAN。想像一下,你把这个看似NB的HashMap初始化放到一个EJB或者是一个很重的包含着这样的生命周期的对象里:




{% raw %}

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

{% endraw %}





这个ReallyHeavyObject类中有许多资源,当ReallyHeavyObject对象被垃圾回收掉时这些资源是需要尽快被释放掉的。不过调用quickHarmlessMethod()方法并不会造成什么影响,因为这个map很快就会被回收掉了。

好的。

我们假设另外一个开发人员,他重构了一下这个方法,返回了这个map,或者是map中的某一部分:



{% raw %}

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;
}
{% endraw %}




这下问题就严重了!现在你把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的内部状态。

[img]https://lukaseder.files.wordpress.com/2014/12/debug-output1.png?w=479[/img]

你会发现外围实例的引用就在这个匿名的HashMap子类中安静地躺着。所有的这些匿名子类型都会持有一个这样的引用。

[b]因此,不要使用这个反模式[/b]

你可能会说,如果将quickHarmlessMethod()声明成static的不就好了,这不会出现3中的泄露问题了,你说的没错。

不过上述代码中最糟糕的问题就是即便你知道这个静态上下文中的map该如何使用,但下一个开发人员可能会注意不到,他还可能会把这个static重构或者删除掉。他们还可能会把这个map存储在一个单例中,这样你就很难再从代码中看出哪里会有一个无用的ReallyHeavyObject的引用。

内部类是一头野兽。它已经造成过许多的问题以及认知失衡。匿名内部类则更为严重,因为读到这段代码的人可能完全没有意识到自己已经包装了一个外围实例,并且把这个实例传递到了别处。

结论便是:

不要自作聪明了,别使用双括号来进行初始化。


原创文章转载请注明出处:[url=http://it.deepinmind.com/java/2014/12/12/dont-be-clever-the-double-curly-braces-anti-pattern.html]http://it.deepinmind.com[/url]

[url=http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/]英文原文链接[/url]
软件工程案例教程·韩万江【课后习题】 !!!! 阅读详情

相关推荐

用AI助手Cursor轻松自动生成流程图

通过 Cursor + Mermaid,我们可以快速、准确地自动生成流程图,上面演示的例子部分流程图可能比较丑,我们可以通过流程图软件导入Mermaid代码,然后手动进行微调就可以了,如果你对美观要求不是特别多,可以直接用,这将极大地提升代码可视化和架构设计的效率。

呱牛 Just do IT 1937

HashMap的匿名内部类

    Map<String,Object> map =new HashMap<String,Object>(){{         put("num",123);     }};

bingguang1993的博客 4813

【头歌实训:逆置顺序表】

根据提示,在右侧编辑器补充函数rev的代码,使用顺序表L的空间就地逆置L。},要求时间复杂度为O(n),空间复杂度为O(1)。第一行为顺序表逆置之前的元素。每个数据后一个空格。第二行为顺序表逆置之后的元素。每个数据后一个空格。本关任务:设计一个算法,将一个顺序表L的元素{a。第一行为顺序表中元素个数n。第二行为空格隔开的n个整数。开始你的任务吧,祝你成功!

guang_Lee的博客 998

Day 26 学习分享 - Map 和 匿名内部类

1. Map 1.1 Map概述 Map 是一个双边队列形式储存为底层结构的接口 Map双边队列中对于数据储存类型 有限制 存储数据类型在创建Map双边队列时进行约束, 保证数据类型一致化 也没有限制 Map可以满足任意类型 Map使用了两个泛型! Map<K, V> 1.2 Map整体结构和常用API interface Map<K, V> --| class HashMap<K, V> 底层储存数据结构使用的方式是哈希表方式, 储存数据是根据当前存储K

WrathPudge的博客 605

MTK 人工智能生态系统 <一> 简介

Neuropilot

New_Tank的博客 3495

群晖设置公网ipv6方式域名解析访问

群晖、IPV6、域名解析

爬山虎的博客 2万+

java中使用双大号初始化

场景 在使用pol-tl操作word的时候,初始化Map的时候使用了如下代码 在上面的初始化方式中,使用到了两个大括号{{}}看似有些陌生的语法,实际上这是匿名内部类和实例化代码块的结合 实例化代码块 先看一道面试题: class Person { { System.out.println("初始化代码块"); } public Person(Strin...

芊雨的博客 248

Arduino创意作品实战案例:手把手教你做温控风扇

通过这个实用的arduino创意作品,轻松实现温度感应与风扇自动控制,结合传感器与编程逻辑,打造智能散热系统,是入门者理想的arduino创意作品实践项目。

weixin_42311427的博客 608

Java对象头Object Header、偏向锁、轻量锁、重量锁研究

Java对象头Object Header。包括markword、klasspoint。 里面有hashcode、gc 的age,还有锁状态,包括:偏向锁、轻量锁、重量锁研究

zyplanke的专栏 1760

Dify 本地部署教程

到这里,Dify 就安装完成了,更多操作设置可以参考官方文档,Dify 和 FastGPT 基本上差不多,也可以同时安装一下 FastGPT 进行体验~Docker Compose 部署 | Dify。

Muti-Agent 6650

从源码编译CesiumForUnreal

从源码编译CesiumForUnreal插件

专注GIS开发 5644

Java面向对象详解(5)继承的作用和特点,instanceof操作符,方法的重写,final关键字,组合,Object类和toString的重写,equals ,super关键字

目录 1:继承 1.1继承的两个作用 1.2 继承的实现和继承的特点 1.3:instanceof操作符 1.3方法的重写 1.4:final关键字 1.5:组合 2:Object类和重写toString方法 2.1.当其他的类没有调用extends是那么该类的父类就是java.lang.Object 2.2.toString方法 2.3重写toString方法 1:继承 1.1继承的两个作用 1.代码复用,便于类的扩展 2.便于建模 1.2 继承的实现和继承的特...

m0_58783923的博客 468

一文读懂OpenClaw:核心组件与工作原理深度解析

Agent核心:负责理解意图、规划任务、协调执行消息系统:统一的消息格式,实现组件间解耦工具注册表:可扩展的工具系统,支持自定义工具模型适配层:多模型支持,统一API接口安全机制:沙箱隔离、权限确认、操作审计性能优化:并行执行、智能缓存、增量更新理解这些核心组件和工作原理,将帮助你更好地使用OpenClaw,也为后续开发自定义扩展打下坚实基础。在下一篇文章中,我们将详细介绍OpenClaw的界面操作,包括命令行与交互模式的各种技巧。

gaoxiang_120的专栏 833

一周极客热文:谷歌发布Android Studio 1.0 正式版

没有什么比直奔主题式的分享再好的了,任何没有价值的分享都是耍流氓,因为小极是很温柔的男子。下面就让我们看看这一周的精华荟萃吧! 下面就让我们看看这一周的精华荟萃吧! 技术干货类 1.谷歌发布Android Studio 1.0 正式版,其第一个稳定的IDE  Android Studio 1.0 终于发布啦,作为一个 Android 团队官方的集成开发环境(IDE),建立在...

csdnjiketoutiao的博客 412

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

java 大括号_java-双大括号实例初始化的反模式

今天在看springboot的batch时, 看到这样一段代码, 直接把我看懵了, 于是找了一下, 发现这 两个大括号 {{ 叫实例初始化器FlatFileItemReader reader = new FlatFileItemReader<>();//设置 csv文件的路径reader.setResource(new ClassPathResource("people.csv"))...

weixin_34208121的博客 333

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

关于Java双大括号{{}}

刚开始我看到这种Java双括弧写法s也是把它奉若圣经,对它只一知半解,昨天在用XStream把一个对象生成XML文件时,其中有一个List属性,我就借用了这种双括符法来初始化元素,结果生成的XML文件走了样,原因是XStream的Converter能处理ArrayList,但无法很好的处理生成的ArrayList的匿名子类。其实就是匿名类啊,会创建出一个HashMap的子类来,匿名类中一个{}括起来的初始化块,里面自然可放置初始化代码。因为在初始化集合时确实方便不少。......

m0_49194578的博客 3721

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

java:使用两对大括号

java:使用两对大括号

听海边涛声 812

Java中双括号初始化是个什么操作

最近在阅读Mybatis源码的时候,看到了一种原来很少见到的语法: public class RichType { ... private List richList = new ArrayList() { { add("bar"); ...

听得到微笑的博客 1258

Java 隐藏特性:双括号初始化(Double Brace Initialization)

Java 隐藏特性:双括号初始化引入双括号初始化理解双括号初始化内部类初始化块(代码块)剖析双括号初始化扩展阅读 引入双括号初始化 Java 中的“双括号初始化”常被人以隐藏特性的方式所提及,那何谓“双括号初始化”呢?我们又在哪里有应用到“双括号初始化”呢? 首先,我们先来观察一段典型的“双括号初始”示例代码片段: Map<String, String> map = new HashM...

www.wuxianjie.net 3178

java细知识点(深入理解和用例)

1、 List<String> list =null; //list通过不同的赋值,可以有三种状态 //状态1,list尚未实例化 list 就为 null; //这时候list是null //状态二 list实例化但元素数量为0, list = new ArrayList<>(); //这时候 list != null 但 list.isEmpty()为空 //可以使用list!=null && list.isEmpty() 为判断 //状态

qq_42199464的博客 1520

JAVA双大括号语法

图解 JAVA双大括号语法原理 匿名内部类 + 初始非静态代码块。 第一个大括号是创建一个继承当前对象的匿名内部类。 第二个大括号是在这个匿 名内部类中创建一个非静态初始化代码块,最后new 的操作是得到当前对象的子类 (匿名内部类)然后向上转型为当前对象的引用。 缺陷: 类中每一处双大括号的引用都会产生一个.class文件,导致堆内存会有这些文件的引用,增加类加载器负担。 使用双大括号初始化所创建的匿名内部类会持有当前对象的引用,会把当前对象的实例暴露出去,造出内存泄漏 ...

qq_41665452的博客 3891

Java语法糖-双括号初始化

Java语法糖-双括号初始化。 该语法糖用于简化创建集合类对象并添加元素的操作,可用于所有集合类对象。原理是通过使用双括号初始化来创建一个匿名内部类,并在该匿名内部类的实例初始化块中添加元素或执行其他操作。

m0_62129859的博客 840

JAVA 双括号初始化

它结合了匿名内部类和实例初始化块的特性,用于快速创建并初始化一个集合对象(如。如果不需要修改集合内容,可以使用不可变集合(Java 9+ 提供的。下面我们来详细分析为什么这种写法会放在这个位置,以及它的作用。可以看到,双括号初始化是一种更简洁的写法,但它的本质是相同的。因此,这段代码直接在方法调用的位置创建了一个包含所需元素的。是一种特殊的语法,称为。,并将其作为参数传递给。

v_nbsp的博客 606

Sonar 规则

bug类型: 1、".equals()" should not be used to test the values of "Atomic" classes. bug 主要 不要使用equals方法对AtomicXXX进行是否相等的判断 Atomic变量永远只会和自身相等,Atomic变量没有覆写equals()方法.2、"=+" should not be used instead of "+=" bug 主要 "=+" 与 "=+" 意义不同 a =+ b;虽然正确但写法不.

qq_37967489的博客 6563

sonar 规则总结

bug类型: 1、".equals()" should not be used to test the values of "Atomic" classes. bug 主要 不要使用equals方法对AtomicXXX进行是否相等的判断 Atomic变量永远只会和自身相等,Atomic变量没有覆写equals()方法. 2、"=+" should not be used instead of...

今日记一事,明日悟一理,积久而成学。 3万+

避免在 Java 中使用双括号初始化

尽管使用 Java 的双括号初始化看起来很"炫酷",但它会无故地额外创建类,可能会导致内存泄漏。因此避免在 Java 中使用双括号初始化。或者利用或者,直接将文件转换为 Kotlin。

悟鸣的技术博客 708
上一篇: ExecutorService的十个使用技巧
下一篇: JVM的自愈能力
spidercoco
博客等级 码龄18年 47粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值