开场白
老铁 :上文“NO.11 对象是怎样炼成的”针对对象在内存中的构建步骤进行了说明,可以发现对象的生成过程比我们想象的要更加复杂。知道了这些步骤有什么用处了?这并不是要告诉我们由于创建一个对象的过程过于复杂而不用对象(如果不用对象,我们又怎么来面向对象编程了?),而是告诫我们在定义一个class时要合理进行“瘦身”并控制重型对象的数量。
因此,创建对象无可避免,但是我们可以采取多种方式来提升在创建对象方面的性能,本文主要针对“延迟初始化”进行说明。
延迟对象初始化
所谓“延迟对象初始化”是一项与编程语言无关的技术,而是一种创建对象时机的策略与方法。即:代码会延迟初始化对象,知道不能再延迟为止。初始化的工作显然是不会带来性能的提升,但是如果整个代码运行下来不会用到被初始化的对象,那么就省去了对象初始化所带来的时间与空间开销,进而提升了整体的代码性能。
应用举例
下面我们通过代码举例来说明如何运行“延迟初始化”技术,先看代码1。
import java.awt.Color;
public class Person {
class Head {
private String name;
private Color color;
}
class Arm {
private String name;
private int length;
}
class Body {
private String name;
private int length;
}
class Leg {
private String name;
private int length;
}
private Head head; //成员对象1
private Arm arm; //成员对象2
private Body body; //成员对象3
private Leg leg; //成员对象4
private boolean isGirl;
private String name;
public Person(String name, boolean isG) {
this.head = new Head();
this.arm = new Arm();
this.body = new Body();
this.leg = new Leg();
this.name = name;
this.isGirl = isG;
}
public void print1() {
StringBuffer sb = new StringBuffer();
sb.append(head.name).append("#");
sb.append(arm.name).append("#");
sb.append(body.name).append("#");
sb.append(leg.name).append("#");
System.out.println(sb.toString());
}
public void print2() {
String str = (true == isGirl) ? "Girl" : "Boy";
str += (name + "#" + str);
System.out.println(str);
}
public static void main(String[] args) {
Person person = new Person("JingGang", false);
//如果我们在客户端只是调用了print2函数
//而不会调用print1函数,我们能否有更优化的方案?
person.print2();
}
}
在代码1中,我们定义了一个Person类,该类中包括了四个类对象成员:Head、Arm、Body、Leg,为客户端提供了两个公有方法print1与pirnt2。我们试想一下如下的业务场景,如果只是调用了print2方法,那么我们可以如何优化上述代码了?老铁们可以思考30秒,然后再往下读。
在print2方法中可以看见,该方法并没有调用四个类对象成员,但是我们在Person类构造函数中对该四个对象一齐进行了初始化。如果业务场景只是调用了print2方法,那么我们就没有必要在构造函数中构造如此多的对象(要知道构造一个对象可是一笔不小的性能开销)。为此,我们可以采用“延迟初始化”技术对上述代码进行优化。实现方式见代码2。
import java.awt.Color;
public class Person {
class Head {
private String name;
private Color color;
}
class Arm {
private String name;
private int length;
}
class Body {
private String name;
private int length;
}
class Leg {
private String name;
private int length;
}
private Head head; // 成员对象1
private Arm arm; // 成员对象2
private Body body; // 成员对象3
private Leg leg; // 成员对象4
private boolean isGirl;
private String name;
public Person(String name, boolean isG) {
this.name = name;
this.isGirl = isG;
}
public void print1() {
//head对象在真正需要使用时才初始化
if (head == null)
this.head = new Head();
//arm对象在真正需要使用时才初始化
if (arm == null)
this.arm = new Arm();
//body对象在真正需要使用时才初始化
if (body == null)
this.body = new Body();
//leg对象在真正需要使用时才初始化
if (leg == null)
this.leg = new Leg();
StringBuffer sb = new StringBuffer();
sb.append(head.name).append("#");
sb.append(arm.name).append("#");
sb.append(body.name).append("#");
sb.append(leg.name).append("#");
System.out.println(sb.toString());
}
public void print2() {
String str = (true == isGirl) ? "Girl" : "Boy";
str += (name + "#" + str);
System.out.println(str);
}
public static void main(String[] args) {
Person person = new Person("JingGang", false);
person.print2();
}
}
如代码2所示,“延迟初始化”的实现方式,就是把对象变量的初始化的时机设置在真正需要使用这些对象变量的时候,而不是放在构造函数中进行。
如果在业务场景中调用了print1方法,这个时候会首先对对象是否已经存在进行判断,如果不存在再进行初始化操作,如果存在则跳过初始化;其实,细心的老铁可能已经发现,如果调用了print1方法,是否使用“延迟初始化”技术并不能在性能与空间开销中有突出的优势,基本与代码1的性能效果相同。为此,该技术也是有一定的应用场景的,具体可参见注意事项。
是不是很简单?老铁们也可以结合一下自身的实际编程经验,是不是也是有意无意的使用了该方法,欢迎老铁们在留言区写下的您的感悟,大家共同讨论交流。
注意事项
敲黑板
- 只有当常用的代码路径不会初始化某个/些对象时,才去考虑延迟初始化这个/些对象:如果某个操作调用不是很频繁,那么延迟初始化是比较有益处的;如果某个操作被频繁调用,实际上没有节省内存(因为这些初始化操作总会被执行),而频繁调用又会有轻微的性能损失(如执行条件判断)。
- 尽早清理对象。从延迟初始化对象可以推导出另外一种行为即通过对不再使用的对象引用变量设置为null,以便告知JVM的垃圾收集器可以对其进行清理。
转载自公众号:代码荣耀