当前位置: 首页 > 科技观察

Java中如何创建优雅的对象来提高程序性能

时间:2023-03-12 22:52:26 科技观察

其实这个问题已经有很多人回答过了,很多朋友在Java四大名著之一《Effective Java》的1-5节中了解过。不过,我还是想根据自己的理解,总结总结一下这个问题,说说为什么我们最后选择builder来达到我们的目的。在Java中创建对象的方法有很多种。总结起来主要有以下四种方式:普通创建:通过new操作符反射创建:调用Class或java.lang.reflect.Constructor的newInstance()方法克隆创建:调用已有对象的clone()方法被序列化:调用java.io.ObjectInputStream的getObject()方法反序列化Java对象。本文仍然采用上述方法创建对象,所以本文只能说是构建对象,不能说是创建对象。假设有这样一个场景,现在你要构建一个大对象,这个对象包含了很多参数,有些参数是必须的,有些是可选的。那么如何优雅安全的构造这个对象呢?01单构造器通常,我们最先能想到的就是单构造器方法。直接用new构造,通过构造函数传递参数,见如下代码:;//研究生院(可选)privateStringschool;//爱好(可选)privateStringhobby;publicPerson(Stringname,intage,inheight,Stringschool,Stringhobby){this.name=name;this.age=age;this.height=height;this.school=school;this.hobby=hobby;}}上面的构造方法有缺点如下:有些参数是可选的(比如身高,学校),必须要传入可能不需要的参数。现在上面只有5个参数,构造函数已经很长了。如果是20个参数,构造函数可以直接上天!构建此类对象非常容易出错。客户端必须参考Javadoc或参数名传入实参对应位置。如果参数都是String类型,一旦传错参数,编译不会报错,但是运行结果会出错。02多个构造函数对于第一个问题,我们可以通过重载构造函数来解决。看下面代码:/****多个构造函数*/publicclassPerson{//姓名(必填)privateStringname;//年龄(必填)privateintage;//身高(选填)privateinheight;//毕业学校(选填)privateStringschool;//爱好(可选)privateStringhobby;publicPerson(Stringname,intage){this.name=name;this.age=age;}publicPerson(Stringname,intage,inheight){this.name=name;this.age=age;this.height=height;}publicPerson(Stringname,intage,inheight,Stringschool){this.name=name;this.age=age;this.height=height;this.school=school;}publicPerson(Stringname,intage,Stringhobby,Stringschool){this.name=name;this.age=age;this.hobby=hobby;this.school=school;}}上面的方法确实可以在一定程度上减少构造函数的长度,但是却存在以下缺陷:类太长。这种方法将导致Person类的构造函数呈阶乘增长。按道理来说,应该写的构造函数的个数就是可选成员变量的组合个数(实际上并没有那么多,原因见第2点)。如果我叫这样的班,我心里一定会说xx!!某些参数组合无法重构。因为Java中的重载是有限制的,方法签名相同的方法不能构成重载,不能在编译时传递。例如包含(name,age,school)和(name,age,hobby)的构造函数不能被重载,因为shcool和hobby都是String类型。Java只识别变量的类型,而不管变量是什么意思。03上面的JavaBean方法不行,别着急!还有一个法宝——JavaBean。一个对象是通过多种方法构造的。直接看下面代码:publicclassPerson{//姓名(必填)privateStringname;//年龄(必填)privateintage;//身高(选填)privateinheight;//毕业学校(选填)privateStringschool;//兴趣爱好(选填)privateStringhobby;publicPerson(Stringname,intage){this.name=name;this.age=age;}publicvoidsetHeight(intheight){this.height=height;}publicvoidsetSchool(Stringschool){this.school=school;}publicvoidsetHobby(Stringhobby){this.hobby=hobby;}}客户端使用这个对象的代码如下:publicclassClient{publicstaticvoidmain(String[]args){Personperson=newPerson("james",12);person.setHeight(170);person.setHobby("reading");person.setSchool("xxxuniversity");}}这似乎完美解决了Person对象构造问题,而且使用起来非常优雅方便。的确,这在单线程环境下确实是一种非常好的构造对象的方法,但是在多线程环境下它仍然有其致命的缺陷。在多线程环境中,不能安全地构造此对象,因为它不是不可变的。一旦构造了Person对象,我们就可以随时通过setXXX()方法改变对象的内部状态。假设一个线程正在执行与Person对象相关的业务方法,另一个线程改变了它的内部状态,从而得到莫名其妙的结果。由于线程运行不规律,该问题可能无法复现。这时候,我真的只能哭了。04Builder方法为了完美解决这个问题,下面介绍本文的主角(等等,等等!)。我们使用Builder优雅而安全地构建Person对象。废话不多说,直接代码:/***待构造对象。该对象的特点:*

    *
  1. 需要用户手动传入多个参数,多个参数可选且顺序随机
  2. *
  3. 该对象是不可变的(所谓不可变的意思是对象一旦创建,其内部状态就不能改变,更通俗地说,就是它的成员变量不能改变)。*不可变对象本质上是线程安全的。
  4. *
  5. 对象所属的类不是为继承而设计的。
  6. *
*满足以上特征的对象的构造可以使用下面的Build方法来构造。通过这种方式构建对象有以下优点:*
    *
  1. 无需编写多个构造函数,创建对象更方便
  2. *
  3. 创建对象的过程是线程安全的
  4. *
*@authorxiaoyu*@date2020-10-25*/publicclassPerson{//name(必填),最终修饰的名字一经初始化就不可更改,保证了对象的不变性。privatefinalStringname;//年龄(必填)privatefinalintage;//身高(选填)privatefinalintheight;//毕业学校(选填)privatefinalStringschool;//爱好(选填)privatefinalStringhobby;/***这个私有构造函数的作用:*
    *
  1. 成员变量的初始化。final类型的变量必须初始化,否则无法编译成功
  2. *
  3. 私有构造函数可以保证无法从外部创建对象,无法继承Person类
  4. *
*/privatePerson(Stringname,intage,intheight,Stringschool,Stringhobby){this.name=name;this.age=age;this.height=height;this.school=school;this.hobby=hobby;}/***待执行的动作*/publicvoiddoSomething(){//TODOdowhatyouwant!!}/***builder.为什么Builder是一个内部静态类?*
    *
  1. 必须是Person的内部类。否则,由于Person的构造函数是private的,无法通过new创建Person对象。
  2. *
  3. 必须是静态类。由于无法从外部创建Person对象,如果不是静态类,则无法从外部引用Builder对象。
  4. *
*注意:Builder的内部成员变量要和Person的成员变量保持一致。*@authorxiaoyu**/publicstaticclassBuilder{//姓名(必填)。注意:这不能是finalprivateStringname;//age(required)privateintage;//height(optional)privateintheight;//graduateschool(optional)privateStringschool;//hobby(optional)privateStringhobby;publicBuilder(Stringname,intage){this.name=name;this.age=age;}publicBuildersetHeight(intheight){this.height=height;returnthis;}publicBuildersetSchool(Stringschool){this.school=school;returnthis;}publicBuildersetHobby(Stringhobby){this.hobby=hobby;returnthis;}/***buildobject*@return返回要构建的对象*/publicPersonbuild(){returnnewPerson(name,age,height,school,hobby);}}}clientbuildobject方法见如下代码:/***ClientusingPersonobject*@authorxiaoyu*@date2020-10-25*/publicclassClient{publicstaticvoidmain(String[]args){/**通过链式调用创建Person对象,非常优雅!*/Personperson=newPerson.Builder("james",12).setHeight(170).setHobby("reading").build();person.doSomething();}}如果不想看代码,你可以看到下面的代码Summary:UseprivatePerson(..)tomakePersonclassnon-inheritable.将Person类的成员变量设置为final类型,使其不可变。使用Person内部的静态Builder类来构建Person对象。通过在Builder类内部设置setXXX()方法返回Builder类型本身实现链式调用构建Person对象至此,我们比较完美的解决了这类对象的创建问题!总结一下本文要创建的对象的主要特点:需要用户手动传入多个参数,且多个参数可选,顺序任意。不可变对象所属的类不是为继承而设计的。即不能继承类顺序使用的对象构造方法:单构造器、多构造器、JavaBean方法、Builder方法最后通过比较,最合适的方案是Builder方法。