Effective Java笔记第一章创建和销毁对象
第二节:遇到多个构造器参数时要考虑用构建器
关于this的作用可以看一下这篇文章this的作用,也许会对你理解这篇文章有些许帮助。
1.重叠构造器模式:提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,以此类推,最后一个构造器包含所有的可选参数。
public class Demo1 {
private final int a;
private final int b;
private final int c;
private final int d;
//两个参数的构造方法
public Demo1(int a, int b) {
//调用三个参数的构造方法
this(a, b, 0);
}
public Demo1(int a, int b, int c) {
//调用四个参数的构造方法
this(a, b, c, 0);
}
public Demo1(int a, int b, int c, int d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1(1, 2, 3, 4);
}
}
缺点:重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且较难阅读。
2.JavaBeans模式:调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要参数,以及每个相关的可选参数。
public class Demo2 {
private int a = 1;
private int b = 2;
private int c = 3;
private int d = 4;
public Demo2() {
}
public void setA(int a) {
this.a = a;
}
public void setB(int b) {
this.b = b;
}
public void setC(int c) {
this.c = c;
}
public void setD(int d) {
this.d = d;
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
demo2.setA(11);
demo2.setB(22);
demo2.setC(33);
demo2.setD(44);
}
}
缺点:
1)因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保持一致性。
使用构造器可以确认哪些属性是必须的,而如果采用set,你无法确认哪些属性是必须的,有可能创建的对象属性添加各不相同,从而达不到使用目的(在你只提供了一个无参构造器的情况下)。
//我们所需要创建对象的类
public class Person {
//我们所需要设置的属性
private String name = "";
private Integer age = 1;
private String sex = "";
//无参构造
public Person() {
}
//使用构造器可以确定哪些属性是必须的
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public static void main(String[] args) {
Person person=new Person("小明",10);
Person personA=new Person();
personA.setName("小明");
Person personB=new Person();
personB.setSex("男");
}
}
2)JavaBean模式阻止了把类做成不可变的可能,需要付出额外的努力确保线程安全。
首先不可变类是实例不能被修改的类,每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。
JavaBean模式我们会先创建一个对象,之后再为这个对象添加属性,违反了不可变类的条例。
public static void main(String[] args) {
//创建person对象
Person person=new Person();
//添加属性
person.setName("小明");
person.setAge(18);
person.setSex("男");
}
JavaBean模式在多线程的情况下可能会有以下的情况发生。我原本需要A线程创建一个Person对象,之后再通过B线程获取创建好的对象。但是有可能A线程还没把所有需要设置的属性设置完,B线程就先获取,导致以下的情况发生。
对象类
//我们所需要创建对象的类
public class Person {
//静态对象并保证唯一
private static final Person person = new Person();
//我们所需要设置的属性
private String name = "";
private Integer age = 1;
private String sex = "";
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
//静态工厂方法获取对象
public static Person getInstance() {
return person;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
创建并获取对象
public class PersonSet extends Thread {
@Override
public void run() {
super.run();
//重写run方法,设置一个person对象
Person person = Person.getInstance();
//我设置了每添加一个属性线程休息1秒,方便看清楚
person.setName("小明");
System.out.println("添加姓名:"+person);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
person.setAge(18);
System.out.println("添加年龄:"+person);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
person.setSex("男");
System.out.println("添加性别:"+person);
}
public static void main(String[] args) {
//我们的目的是先创建完对象,再获取创建好的对象
Person person = Person.getInstance();
new PersonSet().start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获取有姓名,年龄和性别的:"+person);
}
}
以上代码的结果:
添加姓名:Person{name=‘小明’, age=1, sex=’’}
添加年龄:Person{name=‘小明’, age=18, sex=’’}
获取有姓名,年龄和性别的:Person{name=‘小明’, age=18, sex=’’}
添加性别:Person{name=‘小明’, age=18, sex=‘男’}
很明显属性还没添加完就获得了未完成的对象,这是不可取的,对此我们要付出额外的努力,控制线程先创建完对象,之后再获取。
关于线程方面可以看一下这篇文章线程,也许会对你理解这篇文章有些许帮助。
3.Builder模式:让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似setter的方法,来设置每个相关的可选参数。最后客户端调用无参的build方法来生成不可变的对象。
public class NutritionFacts {
//成员变量
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//静态成员类
public static class Builder {
//Required parameters 必须的参数
private final int servingSize;
private final int servings;
//Optional parameters -initialized to default values 可选参数-初始化为默认值
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
//通过构造器为final修饰的变量赋值
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
System.out.println(this);
}
//每一个可选参数的方法
public Builder calories(int val) {
calories = val;
//在内部类和匿名类中,this指的是内部类和匿名类本身
System.out.println(this);
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public NutritionFacts build() {
//在内部类和匿名类中,this指的是内部类和匿名类本身
return new NutritionFacts(this);
}
}
//通过构造器为final修饰的变量赋值
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
NutritionFacts build = new NutritionFacts.Builder(10, 2).build();
NutritionFacts cal = new NutritionFacts.Builder(11, 22).calories(2).build();
System.out.println(cal);
}
}
优点:
1)bulider模式模拟了具名的可选参数。
2)builder像构造器一样,可以对参数强加约束条件。build方法可以检验约束条件,将参数从builder拷贝到对象中后,并在对象域而不是builder域中对他们进行检验。也可以通过多个setter方法对某个约束条件必须持有的所有参数进行检查,这种方式有个好处:一旦传递了无效参数,立刻就会发现约束条件失败,而不是等着调用build方法。
3)builder可以有多个可变参数,构造器就像方法一样,只能有一个可变参数。
缺点:
1)为了创建对象,必须先创建他的构造器,一般在有4个或以上的参数时才使用。