java-双大括号实例初始化的反模式
今天在看springboot的batch时, 看到这样一段代码, 直接把我看懵了, 于是找了一下, 发现这 两个大括号 {{ 叫实例初始化器
FlatFileItemReader<Person> reader = new FlatFileItemReader<>(); // 设置 csv文件的路径
reader.setResource(new ClassPathResource("people.csv"));
// 对cvs文件的数据和领域模型做对应映射
reader.setLineMapper(new DefaultLineMapper<Person>() {{
setLineTokenizer(new DelimitedLineTokenizer() {{
setNames(new String[] {"name", "age", "nation", "address"});
}});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
setTargetType(Person.class);
}});
}});
1, 什么是java的实例初始化器
1, 平时创建map并put值的时候, 是这样操作的
Set<Integer> set = new HashSet<>();
set.add();
set.add();
set.add();
或者, 在静态代码块中进行初始化
private static final Set<Integer> set = new HashSet<>();
static {
set.add();
set.add();
set.add();
}
或者, 简写成这种形式的
Set<Integer> set = new HashSet<Integer>() {{
add();
add();
add();
}};
于是, 可以作为函数参数传入:
someFunction(new HashSet<Integer>() {{
add();
add();
add();
}}
);
这儿的大括号, 其实就是一段 { } 局部代码的写法:
Set<Integer> set = new HashSet<Integer>() {
{
add();
add();
add();
}
};
这样, 不管使用哪一个构造器, 都会执行我们的这段代码
2, 反模式的内存泄漏
原博客地址: http://deepinmind.iteye.com/blog/2165827
说它是一个反模式主要出于三方面的原因:
1. 可读性
可读性是最次要的一个原因。尽管它使得程序编写变得更简单,并且看起来跟JSON中数据结构的初始化差不多:
{
"firstName" : "John"
, "lastName" : "Smith"
, "organizations" :
{
"" : { "id", "" }
, "abc" : { "id", "" }
}
}
2. 一个实例,一种类型
通过一次双括号的初始化我们其实就已经创建了一个新类型了!通过这种方式所生成的每一个新map,都会隐式地创建了一个无法重复使用的新类型。如果仅用一次的话也无可厚非。但如果在一个大型的应用中到处都充斥着这种代码的话,无形中会给你的类加载器增加了许多负担,你的堆会持有着这些类的引用。不信么?编译下上述代码并查看下编译器的输出。大概会是这样的:
Test$$$.class
Test$$$.class
Test$$.class
Test$.class
Test.class
这里只有最外围的Test.class是有意义的。
不过这还不是最重要的问题。
3. 内存泄露!
最重要的问题就是匿名类所造成的。它们持有着外围实例的引用,这简直就是个定时ZHADAN。想像一下,你把这个看似NB的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("", new HashMap(){{
put("id", "");
}});
put("abc", new HashMap(){{
put("id", "");
}});
}});
}}; // Some more code here
}
}
这个ReallyHeavyObject类中有许多资源,当ReallyHeavyObject对象被垃圾回收掉时这些资源是需要尽快被释放掉的。不过调用quickHarmlessMethod()方法并不会造成什么影响,因为这个map很快就会被回收掉了。
好的。
我们假设另外一个开发人员,他重构了一下这个方法,返回了这个map,或者是map中的某一部分:
public Map quickHarmlessMethod() {
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("", new HashMap(){{
put("id", "");
}});
put("abc", new HashMap(){{
put("id", "");
}});
}});
}}; 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());
}
确实是这样!。如果你仍不相信的话,还可以使用调试器来查看下这个返回的map的内部状态。
你会发现外围实例的引用就在这个匿名的HashMap子类中安静地躺着。所有的这些匿名子类型都会持有一个这样的引用。
因此,不要使用这个反模式
你可能会说,如果将quickHarmlessMethod()声明成static的不就好了,这不会出现3中的泄露问题了,你说的没错。
不过上述代码中最糟糕的问题就是即便你知道这个静态上下文中的map该如何使用,但下一个开发人员可能会注意不到,他还可能会把这个static重构或者删除掉。他们还可能会把这个map存储在一个单例中,这样你就很难再从代码中看出哪里会有一个无用的ReallyHeavyObject的引用。
内部类是一头野兽。它已经造成过许多的问题以及认知失衡。匿名内部类则更为严重,因为读到这段代码的人可能完全没有意识到自己已经包装了一个外围实例,并且把这个实例传递到了别处。
结论便是:
不要自作聪明了,别使用双括号来进行初始化。
java-双大括号实例初始化的反模式的更多相关文章
- Java类、实例初始化的顺序
求如下 java 代码的输出?? class T implements Cloneable{ public static int k = 0; public static T t1 = new T(& ...
- java类及实例初始化顺序
1.静态变量.静态代码块初始化顺序级别一致,谁在前,就先初始化谁.从上而下初始化(只在类加载时,初始化一次) 2.非静态变量.非静态代码块初始化顺序级别一致,谁在前,就先初始化谁.从上而下初始化(只要 ...
- 对Java中使用两个大括号进行初始化的理解
最近重读Java 编程思想,读到有关实例化代码块儿 的内容,使我对于使用两个大括号进行初始化有了更深的理解. 实例化代码块儿: 和静态代码块儿的概念相对应,静态代码块儿是static 关键字 + 大括 ...
- Java 类的实例变量初始化的过程 静态块、非静态块、构造函数的加载顺序
先看一道Java面试题: public class Baset { private String baseName = "base"; // 构造方法 public Baset() ...
- 什么是Java实例初始化块
在本篇文章,我将会使用一个例子展示什么是实例变量初始化块,实例初始化块和静态初始化块,然后说明在Java中实例初始化块是如何工作的. 执行顺序 查看下面的代码,你知道哪个先执行吗? package s ...
- Java静态初始化,实例初始化以及构造方法
首先有三个概念需要了解: 一.静态初始化:是指执行静态初始化块里面的内容. 二.实例初始化:是指执行实例初始化块里面的内容. 三.构造方法:一个名称跟类的名称一样的方法,特殊在于不带返回值. 我们先来 ...
- java实例初始化块
实例初始化程序块用于初始化实例数据成员. 它在每次创建类的对象时运行.实例变量的初始化可以是直接的,但是可以在初始化实例初始化块中的实例变量时执行额外的操作. 什么是实例初始化块的使用,我们可以直接分 ...
- 关于java中构造方法、实例初始化、静态初始化执行顺序
在Java笔试中,构造方法.实例初始化.静态初始化执行顺序,是一个经常被考察的知识点. 像下面的这道题(刚刚刷题做到,虽然做对了,但是还是想整理一下) 运行下面的代码,输出的结果是... class ...
- Java初始化顺序(静态变量、静态初始化块、实例变量、实例初始化块、构造方法)
1.执行顺序 1.1.一个类中的初始化顺序 类内容(静态变量.静态初始化块) => 实例内容(变量.初始化块.构造器) 1.2.两个具有继承关系类的初始化顺序 父类的(静态变量.静态初始化块)= ...
随机推荐
- UT源码+019
设计三角形问题的程序 输入三个整数a.b.c,分别作为三角形的三条边,现通过程序判断由三条边构成的三角形的类型为等边三角形.等腰三角形.一般三角形(特殊的还有直角三角形),以及不构成三角形.(等腰直角 ...
- Java程序性能定位工具-火焰图
Java程序性能定位工具-火焰图 前言 Java火焰图是一种新的查看CPU利用率方式.今天就带大家一起使用来自Google大神的工具来生成火焰图.火焰图非常的直观,问题一目了然,希望有一天它能成为JA ...
- MYC编译器源码之语法分析
MyC编译器采用自顶向下的方法进行语法解析,这种语法解析方式,一般是从最左边的Token开始,然后自顶向下看哪一条语法规则可能包含这个Token,如果包含这个Token,则自左向右根据这条语法规则逐一 ...
- signalR常见问题
一.安装signalR会对应安装自己的NewJson包,如果引用了含有不同NewJson包的dll组件,会造成版本不一致.必须在运行环境中指出使用目标版本. 问题截图: 解决方式: <runti ...
- HTML5+规范:Webview(管理应用窗口界面)
一.知识点 Webview模块管理应用窗口界面,实现多窗口的逻辑控制管理操作.通过plus.webview可获取应用界面管理对象. 1.方法 1.1.all: 获取所有Webview窗口 Array[ ...
- 2D and 3D Linear Geometry Kernel ( Geometry Kernels) CGAL 4.13 -User Manual
1 Introduction CGAL, the Computational Geometry Algorithms Library, is written in C++ and consists o ...
- Java 日志学习
Java 日志学习,主要学习log4j,(为了查找方便,直接拷贝别人文章:原文:http://www.cnblogs.com/xt0810/p/3659045.html) [结构] java日志对调试 ...
- Android的四大组件学习
一.Linearlayout : 线性布局 1. android:orientation="vertical" //控件的方向控制,vertical : 垂直布局 , ...
- 双向链表的实现——c++
c++实现双向链表 : #ifndef DOUBLE_LINK_HXX #define DOUBLE_LINK_HXX #include <iostream> using namespac ...
- javaweb 项目的异常处理
首先关于异常的分类: java 中关于异常的分类情况是:throwable 是所有异常和错误的基类,下面在分为Error 和 Exception: 简单的异常体系结构如下图所示: 其中Exceptio ...