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

 更新时间:2023年07月17日 10:20:46   作者:明明如月学长  
这篇文章主要介绍了如何避免在Java中使用双括号初始化,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

结论先行

避免像这样,在 Java 中使用双括号初始化:

new HashMap<String, String>() {{
     put("key", value);
}};

内存泄漏追踪

我最近正在 LeakCanary 看到了以下内存泄漏追踪信息:

┬───
│ GC Root: Global variable in native code
│
├─ com.bugsnag.android.AnrPlugin instance
│   Leaking: UNKNOWN
│   ↓ AnrPlugin.client
│               ~~~~~~
├─ com.bugsnag.android.Client instance
│   Leaking: UNKNOWN
│   ↓ Client.breadcrumbState
│               ~~~~~~~~~~~~~~~
├─ com.bugsnag.android.BreadcrumbState instance
│   Leaking: UNKNOWN
│   ↓ BreadcrumbState.store
│                       ~~~~~
├─ com.bugsnag.android.Breadcrumb[] array
│   Leaking: UNKNOWN
│   ↓ Breadcrumb[494]
│               ~~~~~
├─ com.bugsnag.android.Breadcrumb instance
│   Leaking: UNKNOWN
│   ↓ Breadcrumb.impl
│                   ~~~~
├─ com.bugsnag.android.BreadcrumbInternal instance
│   Leaking: UNKNOWN
│   ↓ BreadcrumbInternal.metadata
│                           ~~~~~~~~
├─ com.example.MainActivity$1 instance
│   Leaking: UNKNOWN
│   Anonymous subclass of java.util.HashMap
│   ↓ MainActivity$1.this$0
│                       ~~~~~~
╰→ com.example.MainActivity instance
    Leaking: YES (Activity#mDestroyed is true)

当打开一个内存泄漏追踪日志时,我首先会看底部的对象,了解它的生命周期,这将帮助我理解内存泄漏追踪中的其他对象是否应该有相同的生命周期。

在底部,我们看到:

╰→ com.example.MainActivity instance
    Leaking: YES (Activity#mDestroyed is true)

Activity 已经被销毁,应该已被垃圾回收器给回收掉了,但它仍驻留在内存中。

此时,我开始在内存泄漏追踪日志中寻找已知类型,并尝试弄清楚它们是否属于同一个被销毁的范围(=> 正在泄漏)或更高的范围(=> 没有泄漏)。

在顶部,我们看到:

├─ com.bugsnag.android.Client instance
│   Leaking: UNKNOWN

我们的 BugSnag 客户端是一个用于分析崩溃报告单例,由于每个应用我们创建一个实例,所以它没有泄漏。

├─ com.bugsnag.android.Client instance
│   Leaking: NO

所以我们现在需要转变焦点,特别关注从最后一个 Leaking: NO 到第一个 Leaking: YES 的部分:

…
├─ com.bugsnag.android.Client instance
│   Leaking: NO
│   ↓ Client.breadcrumbState
│               ~~~~~~~~~~~~~~~
├─ com.bugsnag.android.BreadcrumbState instance
│   Leaking: UNKNOWN
│   ↓ BreadcrumbState.store
│                       ~~~~~
├─ com.bugsnag.android.Breadcrumb[] array
│   Leaking: UNKNOWN
│   ↓ Breadcrumb[494]
│               ~~~~~
├─ com.bugsnag.android.Breadcrumb instance
│   Leaking: UNKNOWN
│   ↓ Breadcrumb.impl
│                   ~~~~
├─ com.bugsnag.android.BreadcrumbInternal instance
│   Leaking: UNKNOWN
│   ↓ BreadcrumbInternal.metadata
│                           ~~~~~~~~
├─ com.example.MainActivity$1 instance
│   Leaking: UNKNOWN
│   Anonymous subclass of java.util.HashMap
│   ↓ MainActivity$1.this$0
│                       ~~~~~~
╰→ com.example.MainActivity instance
    Leaking: YES (Activity#mDestroyed is true)

BugSnag 客户端保持了一个面包屑的环形缓冲区。这些应该保留在内存中,它们也没有泄漏。

所以让我们跳过上述内容,从下面这里继续分析:

├─ com.bugsnag.android.BreadcrumbInternal instance
│   Leaking: NO

我们只需要关注从最后一个 Leaking: NO 到第一个Leaking: YES 的部分:

…
├─ com.bugsnag.android.BreadcrumbInternal instance
│   Leaking: NO
│   ↓ BreadcrumbInternal.metadata
│                           ~~~~~~~~
├─ com.example.MainActivity$1 instance
│   Leaking: UNKNOWN
│   Anonymous subclass of java.util.HashMap
│   ↓ MainActivity$1.this$0
│                       ~~~~~~
╰→ com.example.MainActivity instance
    Leaking: YES (Activity#mDestroyed is true)
  • BreadcrumbInternal.metadata :内存泄漏追踪通过面包屑实现的元数据字段。
  • MainActivity$1 实例是 java.util.HashMap 的匿名子类:MainActivity$1 是在MainActivity 中定义的 HashMap 的匿名子类。它是从 MainActivity.java 中定义的第一个匿名类(因为是 $1 )。
  • this$0:每个匿名类都有一个隐式字段引用到定义它的外部类,这个字段被命名为 this$0

也就是说:记录到 BugSnag 的面包屑之一有一个元数据映射,这是一个 HashMap 的匿名子类 ,它保留对外部类的引用,这个外部类就是被销毁的 Activity

让我们看看我们在 MainActivity 中记录面包屑的地方:

void logSavingTicket(String ticketId) {
    Map<String, Object> metadata = new HashMap<String, Object>() {{
      put("ticketId", ticketId);
    }};
    bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);
}

这段代码利用了一个被称为“双括号初始化” 的有趣的 Java 代码块 。它允许你创建一个 HashMap,并通过添加代码到 HashMap 的匿名子类的构造函数中同时初始化它。

new HashMap<String, Object>() {{
  	put("ticketId", ticketId);
}};

Java 的匿名类总是隐式地引用其外部类。

因此,这段代码:

void logSavingTicket(String ticketId) {
    Map<String, Object> metadata = new HashMap<String, Object>() {{
      put("ticketId", ticketId);
    }};
    bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);
}

实际上被编译为:

class MainActivity$1 extends HashMap<String, Object> {
  private final MainActivity this$1;
  MainActivity$1(MainActivity this$1, String ticketId) {
     this.this$1 = this$1;
     put("ticketId", ticketId);
  }
}
void logSavingTicket(String ticketId) {
  Map<String, Object> metadata = new MainActivity$1(this, ticketId);
  bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);
}

结果,这个 breadcrumb 就一直持有对已销毁的 activity 实例的引用。

总结

尽管使用 Java 的双括号初始化看起来很"炫酷",但它会无故地额外创建类,可能会导致内存泄漏。因此避免在 Java 中使用双括号初始化。

你可以用下面这种更安全的方式来解决这个问题:

Map<String, Object> metadata = new HashMap<>();
metadata.put("ticketId", ticketId);
bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);

或者利用 Collections.singletonMap() 进一步简化代码:

Map<String, Object> metadata = singletonMap("ticketId", ticketId);
bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);

或者,直接将文件转换为 Kotlin。

到此这篇关于如何避免在Java 中使用双括号初始化的文章就介绍到这了,更多相关java双括号初始化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java中for循环删除集合陷阱

    java中for循环删除集合陷阱

    java中在增强for循环为什么不能增删集合呢?一个循环迭代,跟集合的增删改没什么关系。修改集合不是for去做的。for只管循环迭代,你在循环里边修改集合,改变集合的长度,顺序对循环都有影响
    2015-05-05
  • java使用elasticsearch分组进行聚合查询过程解析

    java使用elasticsearch分组进行聚合查询过程解析

    这篇文章主要介绍了java使用elasticsearch分组进行聚合查询过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • java之使用stream对日期排序方式

    java之使用stream对日期排序方式

    这篇文章主要介绍了java之使用stream对日期排序方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Java 入门图形用户界面设计之事件处理上

    Java 入门图形用户界面设计之事件处理上

    图形界面(简称GUI)是指采用图形方式显示的计算机操作用户界面。与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受,本篇精讲Java语言中关于图形用户界面的事件处理
    2022-02-02
  • Gson中的TypeToken与泛型擦除详情

    Gson中的TypeToken与泛型擦除详情

    这篇文章主要介绍了Gson中的TypeToken与泛型擦除详情,其Gson类提供了toJson()与fromJson()方法,分别用来序列化与反序列化,更多相关内容需要的朋友可以参考一下
    2022-09-09
  • Maven setting.xml配置文件详解

    Maven setting.xml配置文件详解

    本篇文章主要介绍了Maven setting.xml 配置文件详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • Spring Boot中优雅的获取yml文件工具类

    Spring Boot中优雅的获取yml文件工具类

    今天小编就为大家分享一篇关于Spring Boot中优雅的获取yml文件工具类,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • Java实现雪花算法(snowflake)

    Java实现雪花算法(snowflake)

    这篇文章主要介绍了Java实现雪花算法(snowflake),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java如何实现HTTP断点续传功能

    Java如何实现HTTP断点续传功能

    其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已,本文将详细介绍Java如何实现HTTP断点续传功能,需要的朋友可以参考下
    2012-11-11
  • Java函数习惯用法详解

    Java函数习惯用法详解

    本篇文章主要给大家总结了java中最常用的函数的用法和写法,需要的朋友参考一下吧。
    2017-12-12

最新评论