前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文读懂《Effective Java》第5条:避免创建不必要的对象 & 性能优化

一文读懂《Effective Java》第5条:避免创建不必要的对象 & 性能优化

作者头像
后台技术汇
发布2022-05-28 12:36:25
2640
发布2022-05-28 12:36:25
举报
文章被收录于专栏:后台技术汇

一般来说,最好能重用对象,而不是在每次需要的时候创建同一个相同功能的新对象。重用对象是快速又高效的一种编码手段。

本节讨论的目标:就是如何优化已经出现重复创建对象的代码块,以达到优化性能。

系列文章

本文属于《读懂Effective Java》系列文章,本系列文章的大纲如下:

场景1:不可变对象的重复创建

如果对象是不可变(immutable),它就始终可以被重用。

我们先看 例子1

代码语言:javascript
复制
public class TestStringCreate {
public static void main(String[] args) {
String s1 = new String("stringette");//反例
String s2 = "stringette";//正解
  }
}

代码分析:

语句1:String s1 = new String("stringette");

经过编译期,字符串“stringette”在类加载时已经创建好了对象,这里传递给String构造器的参数 ("stringette") 本身就是一个 String 实例。(因此属于重复创建对象的案例!这里会涉及JVM对String对象的内存分配原理,可以参考公众号的另一篇文章:《String 源码剖析》

语句2:String s2 = "stringette";

这个优化版本,只用了一个实例,而不是执行时创建的新实例。

构造器在每次被调用时,都会创建一个新的对象,而静态方法则不要求也实际上不会这么做。

场景2:可变对象的重复创建

对于已知不会被修改的可变对象,也是可以被重用的。

我们先看 例子2

代码语言:javascript
复制
//反例
public class Person {
  private final Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
  }
public boolean isBababyBoomer(){
//每次调用方法,执行一次
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1,0,0,0);
    Date boomStart = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1,0,0,0);
    Date boomEnd = gmtCal.getTime();
return (birthDate.compareTo(boomStart) >= 0) && (birthDate.compareTo(boomEnd)<0);
  }
}

代码分析:

isBababyBoomer 方法每次被调用,都会创建一个 Calendar 、一个 TimeZone 和两个 Date,显然这是不必要的。

代码优化:

利用一个静态的初始化器(initializer)避免这个效率低下的情况,如例子3:

代码语言:javascript
复制
public class Person2 {
private final Date birthDate;
private static final Date BOOM_START = null;
private static final Date BOOM_END = null;

//静态代码块,执行一次
static {
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1,0,0,0);
Date boomStart = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1,0,0,0);
Date boomEnd = gmtCal.getTime();
  }

public Person2(Date birthDate) {
this.birthDate = birthDate;
  }

public boolean isBababyBoomer(){
return (birthDate.compareTo(BOOM_START) >= 0) && (birthDate.compareTo(BOOM_END)<0);
  }
}

代码分析:

改进后,Calendar 、一个 TimeZone和 Date 都只会在初始化时被实例化一次,而不是isBababyBoomer 方法每次被调用时都会创建。

场景3:自动装箱机制带来的性能开销

jdk发行版(1.5之后),有一种创建多余对象的新方法:自动装箱(autoboxing),它允许程序员将基本类型 & 装箱基本类型(Boxed Primitive Type)进行混用,按需装箱与拆箱。

自动装箱机制,导致基本类型与装箱类型这两者在语义上只有微妙差役,但在性能上有比较明显的差别。

我们看下例子4:

代码语言:javascript
复制
/**
 * <p>
 *     jdk5之后,自动装箱使得 基本类型 & 装箱基本类型(Boxed Primitive Type)混用。
 *     两者在语义上有微妙差役,但在性能上有比较明显的差别。
 * </p>
 */
public class TestAutoBoxing {
public static void main(String[] args) {
    testPrimitive();
    testBoxing();
  }
  //装箱类型
private static void testBoxing() {
long start = System.currentTimeMillis();
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++){
      sum += i;
    }
    System.out.println("User Boxing, sum = " + sum+ ", cost : " + (System.currentTimeMillis() - start)/1000 + "ms");
  }
  //基本类型
private static void testPrimitive() {
long start = System.currentTimeMillis();
long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++){
      sum += i;
    }
    System.out.println("User Primitive type, sum = " + sum+ ", cost : " + (System.currentTimeMillis() - start)/1000 + "ms");
  }
}

允许结果:

代码语言:javascript
复制
User Primitive type, sum = 2305843005992468481, cost : 1ms
User Boxing, sum = 2305843005992468481, cost : 8ms

代码分析:

先说结果:同样一个循环执行代码段,使用自动装箱的运行时间是使用基本类型的8倍。

语句:Long sum = 0L;

使用了自动装箱机制,意味着程序构造了大概2的31次方个多余的Long实例(大约每次往Long sum 增加long时构造一个实例)。

语句:long sum = 0L;

使用了基础类型,减少了多余的创建对象的开销。

优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱所带来的性能损耗。

总结

实际上,由于小对象的构造器知识做很少的显式工作,所以它的创建&回收动作是非常廉价的,特别是现代的JVM实现上更是如此。

但是,我们对于维护自己的对象池(object pool)来避免创建对象不一定是好事,除非对象池的对象十分重要,如:数据库连接。

另外,在提倡保护性拷贝时,因为重用对象而付出的代码要比创建重复对象的代码要高,这一点我们后续再聊。。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后台技术汇 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 系列文章
  • 场景1:不可变对象的重复创建
  • 场景2:可变对象的重复创建
  • 场景3:自动装箱机制带来的性能开销
  • 总结
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档