其实这个问题已经有很多人回答过了,很多朋友在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对象。废话不多说,直接代码:/***待构造对象。该对象的特点:*
- *
- 需要用户手动传入多个参数,多个参数可选且顺序随机 *
- 该对象是不可变的(所谓不可变的意思是对象一旦创建,其内部状态就不能改变,更通俗地说,就是它的成员变量不能改变)。*不可变对象本质上是线程安全的。 *
- 对象所属的类不是为继承而设计的。 *
- *
- 无需编写多个构造函数,创建对象更方便 *
- 创建对象的过程是线程安全的 *
- *
- 成员变量的初始化。final类型的变量必须初始化,否则无法编译成功 *
- 私有构造函数可以保证无法从外部创建对象,无法继承Person类 *
- *
- 必须是Person的内部类。否则,由于Person的构造函数是private的,无法通过new创建Person对象。 *
- 必须是静态类。由于无法从外部创建Person对象,如果不是静态类,则无法从外部引用Builder对象。 *