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.两个具有继承关系类的初始化顺序 父类的(静态变量.静态初始化块)= ...
随机推荐
- RequestHelper
Request["param"] 全部 Request.QueryString["param"] get Request.Form["param&qu ...
- 常用kubectl命令总结
command kubectl kubectl 输出格式 显示Pod的更多信息 kubectl get pod <pod-name> -o wide 以yaml格式显示Pod的详细信息 k ...
- asp.net mvc 在JS中跳转到其它controller/action
平时在ASP.NET 中经常这样写, $('#loginOut').click(function() { $.messager.confirm('系统提示', '您确定要退出本次登 ...
- Mina集成Spring --- 在配置文件中配置sessionconfig
这个找了很久,一直想用这个功能,在xml里,配置如下: <?xml version="1.0" encoding="UTF-8"?> <bea ...
- dropdownlist的OnSelectedIndexChanged方法不触发
解决方法: AutoPostBack="true" if (!IsPostBack){ 数据绑定 } 如果数据绑定后不触发,但是手动添加数据的情况下触发<%@ Page En ...
- LinkedBlockingQueue源码解析(2)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3.3.public void put(E e) throws InterruptedException 原 ...
- 删除 iptables nat 规则
原文:https://www.cnblogs.com/hixiaowei/p/8954161.html 删除FORWARD 规则: iptables -nL FORWARD --line-number ...
- 程序媛计划——python正则表达式
#定义 正则表达式是对字符串操作的一种逻辑公式,通过它我们能筛选过滤出我们需要的内容,如判断一串数字是否是电话号码. #原理 先把正则表达式的字符串转换成 Pattern 对象,接着用这个对象处理文本 ...
- docker 网络实践
#docker 网络模式 环境 centos7. , Docker version -ce docker自带网络类型 bridge,host,none,container,overlay,macvla ...
- Django 一些少用却很实用的orm查询方法
一.使用Q对象进行限制条件之间 "或" 连接查询 from django.db.models import Q from django.contrib.auth.models im ...