编写高质量代码:改善Java程序的151个建议(11)

  建议35:避免在构造函数中初始化其他类

  构造函数是一个类初始化必须执行的代码,它决定着类的初始化效率,如果构造函数比较复杂,而且还关联了其他类,则可能产生意想不到的问题,我们来看如下代码:

  

    publicclassClient{ publicstaticvoidmain(String[]args){ Sons=newSon(); s.doSomething(); } } //父类 classFather{ Father(){ newOther(); } }//子类 classSonextendsFather{ publicvoiddoSomething(){ System.out.println(“Hi,showmesomething”); } } //相关类 classOther{ publicOther(){ newSon(); } }

  这段代码并不复杂,只是在构造函数中初始化了其他类,想想看这段代码的运行结果是什么?是打印“Hi,show me something”吗?

  答案是这段代码不能运行,报StackOverflowError异常,栈(Stack)内存溢出。这是因为声明s变量时,调用了Son的无参构造函数,JVM又默认调用了父类Father的无参构造函数,接着Father类又初始化了Other类,而Other类又调用了Son类,于是一个死循环就诞生了,直到栈内存被消耗完毕为止。

  可能有读者会觉得这样的场景不可能在开发中出现,那我们来思考这样的场景:Father是由框架提供的,Son类是我们自己编写的扩展代码,而Other类则是框架要求的拦截类(Interceptor类或者Handle类或者Hook方法),再来看看该问题,这种场景不可能出现吗?

  那有读者可能要说了,这种问题只要系统一运行就会发现,不可能对项目产生影响。

  那是因为我们在这里展示的代码比较简单,很容易一眼洞穿,一个项目中的构造函数可不止一两个,类之间的关系也不会这么简单的,要想瞥一眼就能明白是否有缺陷这对所有人员来说都是不可能完成的任务,解决此类问题的最好办法就是:不要在构造函数中声明初始化其他类,养成良好的习惯。

  建议36:使用构造代码块精炼程序

  什么叫代码块(CodeBlock)?用大括号把多行代码封装在一起,形成一个独立的数据体,实现特定算法的代码集合即为代码块,一般来说代码块是不能单独运行的,必须要有运行主体。在Java中一共有四种类型的代码块:

  (1)普通代码块

  就是在方法后面使用“{}”括起来的代码片段,它不能单独执行,必须通过方法名调用执行。

  (2)静态代码块

  在类中使用static修饰,并使用“{}”括起来的代码片段,用于静态变量的初始化或对象创建前的环境初始化。

  (3)同步代码块

  使用synchronized关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。

  (4)构造代码块

  在类中没有任何的前缀或后缀,并使用“{}”括起来的代码片段。

  我们知道,一个类至少有一个构造函数(如果没有,编译器会无私地为其创建一个无参构造函数),构造函数是在对象生成时调用的,那现在的问题来了:构造函数和构造代码块是什么关系?构造代码块是在什么时候执行的?在回答这个问题之前,我们先来看看编译器是如何处理构造代码块的,看如下代码:

  

    publicclassClient{ { //构造代码块 System.out.println(“执行构造代码块”); } publicClient(){ System.out.println(“执行无参构造”); } publicClient(String_str){ System.out.println(“执行有参构造”); } }

  这是一段非常简单的代码,它包含了构造代码块、无参构造、有参构造,我们知道代码块不具有独立执行的能力,那么编译器是如何处理构造代码块呢?很简单,编译器会把构造代码块插入到每个构造函数的最前端,上面的代码与如下代码等价:

  

    publicclassClient{ publicClient(){ System.out.println(“执行构造代码块”); System.out.println(“执行无参构造”); } publicClient(String_str){ System.out.println(“执行构造代码块”); System.out.println(“执行有参构造”); } }

  每个构造函数的最前端都被插入了构造代码块,很显然,在通过new关键字生成一个实例时会先执行构造代码块,然后再执行其他代码,也就是说:构造代码块会在每个构造函数内首先执行(需要注意的是:构造代码块不是在构造函数之前运行的,它依托于构造函数的执行),明白了这一点,我们就可以把构造代码块应用到如下场景中:

  (1)初始化实例变量(Instance Variable)

  如果每个构造函数都要初始化变量,可以通过构造代码块来实现。当然也可以通过定义一个方法,然后在每个构造函数中调用该方法来实现,没错,可以解决,但是要在每个构造函数中都调用该方法,而这就是其缺点,若采用构造代码块的方式则不用定义和调用,会直接由编译器写入到每个构造函数中,这才是解决此类问题的绝佳方式。

  (2)初始化实例环境

  一个对象必须在适当的场景下才能存在,如果没有适当的场景,则就需要在创建对象时创建此场景,例如在JEE开发中,要产生HTTP Request必须首先建立HTTP Session,在创建HTTP Request时就可以通过构造代码块来检查HTTP Session是否已经存在,不存在则创建之。

  以上两个场景利用了构造代码块的两个特性:在每个构造函数中都运行和在构造函数中它会首先运行。很好地利用构造代码块的这两个特性不仅可以减少代码量,还可以让程序更容易阅读,特别是当所有的构造函数都要实现逻辑,而且这部分逻辑又很复杂时,这时就可以通过编写多个构造代码块来实现。每个代码块完成不同的业务逻辑(当然了,构造函数尽量简单,这是基本原则),按照业务顺序依次存放,这样在创建实例对象时JVM也就会按照顺序依次执行,实现复杂对象的模块化创建。

  建议37:构造代码块会想你所想

  上一个建议中我们提议使用构造代码块来简化代码,并且也了解到编译器会自动把构造代码块插入到各个构造函数中,那我们接下来看看编译器是不是足够聪明,能够为我们解决真实的开发问题。有这样一个案例:统计一个类的实例数量。可能你要说了,这很简单,在每个构造函数中加入一个对象计数器不就解决问题了吗?或者使用我们上一个建议介绍的,使用构造代码块也可以。确实如此,我们来看如下代码是否可行:

  

    publicclassClient{ publicstaticvoidmain(String[]args){ newBase(); newBase(“”); newBase(0); System.out.println(“实例对象数量:”+Base.getNumOfObjects()); } } classBase{ //对象计数器 privatestaticintnumOfObjects=0; { //构造代码块,计算产生对象数量 numOfObjects++; } publicBase(){ } //有参构造调用无参构造 publicBase(String_str){ this(); } //有参构造不调用其他构造 publicBase(int_i){ } //返回在一个JVM中,创建了多少个实例对象 publicstaticintgetNumOfObjects(){ returnnumOfObjects; } }

  这段代码是可行的吗?能计算出实例对象的数量吗?哎,好像不对呀,如果编译器把构造代码块插入到各个构造函数中,那带有String形参的构造函数可就有问题,它会调用无参构造,那通过它生成Base对象时就会执行两次构造代码块:一次是由无参构造函数调用构造代码块,一次是执行自身的构造代码块,这样的话计算可就不准确了,main函数实际在内存中产生了3个对象,但结果却会是4。不过真是这样的吗?Are you sure?我们运行一下看看结果:

  实例对象数量:3

  非常遗憾,你错了,实例对象的数量还是3,程序没有任何问题。奇怪吗?不奇怪,上一个建议是说编译器会把构造代码块插入到每一个构造函数中,但是有一个例外的情况没有说明:如果遇到this关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块,对于我们的例子来说,编译器在编译时发现String形参的构造函数调用了无参构造,于是放弃插入构造代码块,所以只执行了一次构造代码块—结果就是如此。

  那Java编译器为什么会这么聪明呢?这还要从构造代码块的诞生说起,构造代码块是为了提取构造函数的共同量,减少各个构造函数的代码而产生的,因此,Java就很聪明地认为把代码块插入到没有this方法的构造函数中即可,而调用其他构造函数的则不插入,确保每个构造函数只执行一次构造代码块。

  还有一点需要说明,读者千万不要以为this是特殊情况,那super也会类似处理了。其实不会,在构造代码块的处理上,super方法没有任何特殊的地方,编译器只是把构造代码块插入到super方法之后执行而已,仅此不同。

  注意:放心地使用构造代码块吧,Java已经想你所想了。

  相关链接:

  编写高质量代码:改善Java程序的151个建议(1)

  编写高质量代码:改善Java程序的151个建议(2)

  编写高质量代码:改善Java程序的151个建议(3)

  编写高质量代码:改善Java程序的151个建议(4)

  编写高质量代码:改善Java程序的151个建议(5)

  编写高质量代码:改善Java程序的151个建议(6)

  编写高质量代码:改善Java程序的151个建议(7)

  编写高质量代码:改善Java程序的151个建议(8)

  编写高质量代码:改善Java程序的151个建议(9)

  编写高质量代码:改善Java程序的151个建议(10)

一个人去旅行,而且是去故乡的山水间徜徉。

编写高质量代码:改善Java程序的151个建议(11)

相关文章:

你感兴趣的文章:

标签云: