Effective Java笔记第一章创建和销毁对象第二节遇到多个构造器参数时要考虑用构建器

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个或以上的参数时才使用。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值