对于java攻城狮来说,创建一个java对象是家常便饭,每天都不知道要创建多少个对象,几年下来,创建的对象都数以万计。但是你真的会创建java对象吗?创建java对象究竟有哪些方式?不同的场景该采用什么方式创建对象呢?
假设有一个如下的用户类User,用户名(name)可以区分一个唯一的用户。手机号(phone)和邮箱(email)是可选的。
public class User {
/**用户名*/
private String name;
/**手机号*/
private String phone;
/**邮箱*/
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
公有构造器创建对象
对于上面的User类,创建对象最简单的方式就是使用构造器,你可能会在User类中添加构造器代码:
/**
* 默认构造器
*/
public User() {
super();
}
/**
* 用户名参数构造器
* @param name
*/
public User(String name) {
super();
this.name = name;
}
这种场景创建一个对象非常简单,下面的代码分别使用无参数和带参数的构造器构造对象:
//无参数构造方法创建一个对象后用set方法设置属性值
User userA = new User();
userA.setName("A");
//带参数构造方法直接创建一个用户对象
User userB = new User("B");
大多数情况下,使用构造器构造一个对象是没有什么毛病的。现在我们对User进行分类,假设后台管理用户Admin是强制使用邮箱注册的,普通用户(消费者)Consumer是强制使用手机号注册的。如果我们添加一个构造方法如下:
public User(String name, String phone, String email) {
super();
this.name = name;
this.phone = phone;
this.email = email;
}
那么创建一个后台用户则是:
User admin = new User("admin", null, "admin@lovecto.cn");
创建一个普通用户则是:
User consumer = new User("consumer", "135xxxxxxxx", null);
会不会感觉很奇怪,创建一个后台用户却要强制传一个手机号参数null,创建一个普通用户却要强制传一个邮箱参数null。这种构造器很容易被错误的使用,调用方完全不知道什么时候该传null,什么时候不该传null。而且构造方法的名字和类名是一样的,像下面的两个构造方法是不允许同时出现的:
public User(String name, String phone) {
super();
this.name = name;
this.phone = phone;
}
public User(String name, String email) {
super();
this.name = name;
this.email = email;
}
静态工厂法创建对象
候静态方法创建对象可以很好的解决构造器的不足。java是面相对象的语言,我们在研发过程中也要使用面相对象的思想。我们创建两个类Admin和Consumer,他们都继承自User:
public class Admin extends User{
public Admin(String name, String email) {
super(name);
setEmail(email);
}
}
public class Consumer extends User{
public Consumer(String name, String phone) {
super(name);
setPhone(phone);
}
}
为了便于区分User的类型,我们在User类内部增加一个枚举Type,用于区分是管理员还是普通用户,并实现静态方法创建对象的代码:
public class User {
/**用类型*/
public enum Type{
ADMIN,
CUSTOMER;
}
/**
* 创建一个用户
* @param name
* @param type
* @param id
* @return
*/
public static User newInstance(String name, Type type, String id){
if(type.equals(Type.ADMIN)){
return new Consumer(name, id);
}else if(type.equals(Type.CUSTOMER)){
return new Admin(name, id);
}
return new User(name);
}
/**
* 创建一个管理员用户
* @param name
* @param email
* @return
*/
public static User newAdminInstance(String name, String email){
return new Admin(name, email);
}
/**
* 创建一个普通用户
* @param name
* @param phone
* @return
*/
public static User newConsumerInstance(String name, String phone){
return new Consumer(name, phone);
}
//......其他代码省略
}
接下来创建对象就像下面的代码这样简单明了:
Admin admin = (Admin) User.newAdminInstance("admin", "admin@lovecto.cn");
Consumer consumer = (Consumer) User.newConsumerInstance("consumer", "135xxxxxxxx");
User user = User.newInstance("admin", User.Type.ADMIN, "admin@lovecto.cn");
从可读性和可维护性上来讲,使用静态工厂法创建对象会比直接使用构造器创建对象要优雅。
总结起来说,使用静态工厂法创建对象有以下好处:
1. 静态工厂法的静态方法可以根据需要命名,构造器的方法名不能修改。
2. 有时候使用静态工厂创建对象不必每次都创建对象,例如JDK中的java.lang.Boolean.valueOf(boolean)。
3. 静态工厂方法创建对象返回的对象类型可以是原返回类型的任何子类型,例如这里可以返回User的子类Admin或Consumer。
4. 静态工厂方法使代码更加简洁优雅,可读性和可维护性更高。
静态方法有些惯用的名称如valueOf、of、getInstance、newInstance、getType、newType等。所以在设计一个类的时候,我们在提供构造器方法的同时也可以考虑一下使用静态工厂方法。
构建器模式创建对象
静态工厂法有其优势,但是如果一个类的属性变多时,问题也会凸显出来。我们在编码过程中,一个方法的参数个数一般不允许超过5个。还是回到User这个类上来,假设产品经理出了新需求,用户上需要增加年龄(age)、性别(sex)、地址(address)等属性,后期还可能增加国籍、身高、体重等。我们在User类内部增加一个静态的Builder类,Builder类里面的属性和User属性相同,每个属性的设置方法都返回对象本身,Builder类提供一个无参的build方法用于构造User对象,User的构造器不再是一长串的参数列表,而是一个Builder对象:
public class User {
/** 用户名 */
private String name;
/** 手机号 */
private String phone;
/** 邮箱 */
private String email;
/** 年龄 */
private Integer age;
/** 性别 */
private String sex;
/** 地址 */
private String address;
public User(Builder builder) {
this.address = builder.address;
this.age = builder.age;
this.email = builder.email;
this.name = builder.name;
this.phone = builder.phone;
this.sex = builder.sex;
}
public static class Builder {
/** 用户名 */
private String name;
/** 手机号 */
private String phone;
/** 邮箱 */
private String email;
/** 年龄 */
private Integer age;
/** 性别 */
private String sex;
/** 地址 */
private String address;
//设置属性值后返回对象本身
public Builder name(String name) {
this.name = name;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder age(Integer age) {
this.age = age;
return this;
}
public Builder sex(String sex) {
this.sex = sex;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
这样,构造一个User对象可以使用如下的代码,而且需要设置什么属性都是调用方可选择的:
//不设置手机号
User userA = new User.Builder().name("name").email("xxx@xxx.com")
.age(20).sex("保密").build();
//不设置邮箱
User userB = new User.Builder().name("name").phone("135xxxxxxxx")
.age(20).sex("保密").build();
构建器模式(Builder模式)创建对象在参数越来越多的情况下是个不错的选择,尤其是在有些参数是可选的时候,构建器模式使代码简介且易于阅读。在构建对象过程中,在调用build方法之前可以进行一些数据校验,如果校验失败者放弃创建对象。
总结
创建对象大概有3种方式:构造器、静态工厂法、构建器模式。构造器适合创建简单的对象;静态工厂方法适合创建特殊用途的对象;构建器适合创建属性较多且属性可选性较多的对象。每种方式都有可取之处,在日常的研发过程中,我们可以根据不同的场景选择合适的方式来设计类,选择合适的方式来创建对象。