刚开始看见这个标题的时候,我想到了python可以选择初始化参数的语法,C++、C#能有默认参数。

为什么Java什么都没有~~

好吧,我们是使用构造器来实现它。

1.当一个类的构造函数需要很多构造函数的时候,编程人员往往容易混淆弄错,而且很多情况并不需要这么多的构造函数。

因此:

1)是用默认构造函数构造这个对象,并在之后使用setter方法给这个对象赋值。

但是对于多线程中线程安全考虑来说,这样做排除了使类不可变的可能性,并且想要排除这种bug也是十分困难的

(注:由于代码量的原因,并没有深刻理解到为什么会有这样的问题,但是大概的意思可能是由于多线程的问题,有些对对象属性的访问可能会在setter构造之前调用

这种方式就好像生产车一样。先通过默认构造函数生产出来一个车的外壳,然后通过setter添加轮子,方向盘什么的。

但是在多线程里面,由于没有保证生产出来外壳后,接下来就一定是添加轮子,方向盘。有可能有人就直接拿着车的外壳上路了。

因此这种方式在多线程的环境下不推荐使用

2)使用Builder内置构造器

/**
* @Author dengchengchao
* @Time 2018/5/10
* @Description
*/
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 { /**
* 必要的参数
*/
private final int servingSize;
private final int servings; /**
* 可选参数
*/
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0; public Builder(int servingSize, int servings) {
this.servings = servings;
this.servingSize = servingSize;
} public Builder calories(int val) {
this.calories = val;
return this;
} public Builder fat(int val) {
this.fat = val;
return this;
} public Builder sodium(int val) {
this.sodium = val;
return this;
} public Builder carbohydrate(int val) {
this.carbohydrate = val;
return this;
} } private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
} }

可以看出来NutritionFacts使用了一个内置的Builder来够着函数,这样我们就可以使用:

   NutritionFacts nutritionFacts=new Builder(240,8).calories(100).carbohydrate(35).build();

很方便的选择初始化那些参数了。

PS1:这样使用很简单,但是在写这个Builder的时候,简直不要再麻烦。推荐使用idea的插件:Builder Generator 能快速构造。

PS2:可以看到示例代码很好的习惯,不会改变的属性都是用final,只平时写代码的时候,除了定义常量使用final,基本都没使用过,应该多思考思考,不过final在JavaBean中作为Json反序列对象得小心使用,不然很可能序列化失败。

2.Builder模式也非常适合层次结构,可以使用平行层次的builder,每个嵌套在相应的类中,就好像披萨类,内部可以包含各种芝士构造器,大小构造器等。

public abstract class Pizza {
public enum Topping{
HAM,MUSHROOM,ONION,PEPPER,SAUSAGE
}
final Set<Topping> toppings; abstract static class Builder<T extends Builder<T>>{
EnumSet<Topping> toppings=EnumSet.noneOf(Topping.class); public T addTopping(Topping topping){
toppings.add(Objects.requireNonNull(topping));
return self();
} abstract Pizza build(); protected abstract T self();
} Pizza(Builder<?> builder){
toppings=builder.toppings.clone();
}
}
public class NyPizza extends Pizza {

    public enum Size{SMALL,MEDIUM,LARGE}

    private final Size size;

    public static class  Builder extends Pizza.Builder{
private final Size size; public Builder(Size size){
this.size= Objects.requireNonNull(size);
} @Override
public NyPizza build(){
return new NyPizza(this);
} @Override
protected Builder self(){
return this;
}
} private NyPizza(Builder builder){
super(builder);
size=builder.size;
}
}

public class Calzone extends Pizza {

    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false; public Builder sauceInside() {
sauceInside = true;
return this;
} @Override
public Calzone build() {
return new Calzone(this);
} @Override
protected Builder self() {
return this;
} } private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
} public static void main(String[] args) {
Calzone calzone = new Builder().addTopping(Topping.MUSHROOM).build();
}
}

PS:在写这段代码的时候,我发现了这种类声明方式:

abstract static class Builder<T extends Builder<T>>

刚开始我一直在想为什么需要<T extends Builder<T>>,Java 面向对象最大的特点之一不是多态么?何必这么麻烦的声明类型。

经过仔细研究这段代码和根据后续代码来看:

1.Builder类中包含了一个self方法,需要各个子类自己实现并返回this。

2.从后面的代码来看有些子类包含了一些父类不存在的方法。

3.如果不加<T extends Builder<T>>.那么父类有的通用的方法addTopping应该返回什么。

想到这些问题后,再想想解决方法就明白书上为什么要这样写了:

因为后面的子类包含了一些父类不存在的方法,而子类的每一个set都需要返回this.那么在调用父类中通用的方法addTopping()后,就无法再调用子类的方法了,应为addTopping()在编译的时候,是返回的父类Pizza类型。

也就是说:

Pizza calzone=new Calzone.Builder().addTopping(Toppig.MUSHROOM).sauceInside().build(); 

是编译不过的,因为addTopping()返回的是Pizza类型,Pizza没有sauceInside()方法。

同样的道理。探讨下如果不加<T extends Builder<T>> 用来指定真正的类型,那么

Calzone calzone=new Calzone.Builder().addTopping(Toppig.MUSHROOM).sauceInside().build(); 

也是无法编译成功的,仔细看看,

Calzone.Builder().addTopping(Topping.MUSHROOM)

  在编译阶段是返回父类Pizza类型的,那么它所调用的build()也是返回的Pizza类型,而左边确实它的子类型:Calzone,如果不强制转换,那么是无法从范围小的类型自动转换为范围大的类型的。

这样的代码,对于使用Pizaa类,Calzone类的用户来说,是非常疑惑的。

因此,我们可以定义一个self,让子类型告诉这个方法的具体返回类型。

3.对于上面的疑问,Effective也进行了解释,只是如果不认真的思考,是想不到为什么么,感觉作者只是一笔带过。

具体的技巧为:

1.在定义这个父类的时候,添加一个递归类型参数的泛型类型。例如Bulider<T extend Builder<T>>

2.定义一个self()方法,让子类实现self方法并返回this。

3.在父类所有需要返回this的地方使用self()方法

这种技巧称为 模拟自我类型

可以看见buid()方法返回的具体的子类类型。NyPizza.Builder返回NyPizza类型,Calzone.Builder返回Calzone类型。这种一个子类方法被声明为返回在超类中声明的返回类型的子类型,称为 协变返回类型

协变返回类型这种技巧可以应用在父类需要返回this

4.构造器方法也会带来一定的性能影响和使构造方法更加冗长的缺点,

因此最好在只有足够的参数的时候才使用它,比如4个或则更多。但是值得注意的是可能你以前不是builder而是后来发现参数越来越多的时候再转换为builder模式的时候,过时的构造方法或静态工厂就会不好处理,因此,如果采用builder模式,最好一开始就考虑好,

终止,当参数过多的时候,使用builder是一个不错的选择。特别是在许多参数是可选的的情况下。

 

《Effective Java》 读书笔记(二) 在构造参数过多的时候优先考虑使用构造器的更多相关文章

  1. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  2. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  3. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  4. java读书笔记二

    这是我的一些读书笔记: 我研究了一下面向对象: 面向对象符合人类看待事物的一般规律,对象的方法的实现细节是包装的,只有对象方法的实现者了解细节 我觉得面向过程是由过程.步骤.函数组成,过程是核心,面向 ...

  5. Effective Java 读书笔记之六 方法

    一.检查参数的有效性 1.考虑参数有哪些限制,把限制写到文档中,在方法的开头处通过显式地检查来实施这些限制. 二.必要时进行保护性拷贝 1.如果类具有从客户端得到或者返回的可变组件,类就必须考虑保护性 ...

  6. Effective Java 读书笔记

    创建和销毁对象 >考虑用静态工厂方法替代构造器. 优点: ●优势在于有名称. ●不必再每次调用他们的时候都创建一个新的对象. ●可以返回原返回类型的任何子类型的对象. ●在创建参数化类型实例的时 ...

  7. Effective Java读书笔记--创建和销毁对象

    1.优先考虑用静态工厂方法代替构造器2.遇到多个构造器参数时要考虑使用构建器Builder解决参数过多,不可变类型.私有构造方法,静态类的构造方法提供必要参数,剩下可选.new xxx.build() ...

  8. Effective Java 读书笔记之二 对于所有对象都通用的方法

    尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1. ...

  9. [Effective Java 读书笔记] 第三章类和接口 第二十-二十一条

    第二十条 用函数对象表示策略 函数指针(JAVA的函数指针,是指使用对象的引用来作为参数,传递给另一个对象的方法)主要用来实现策略模式,为了在JAVA中实现这种模式,要申明一个接口来表示该策略,并为每 ...

随机推荐

  1. ng service(服务)

    ng service(服务) 创建服务命令:ng g service services/+服务名 使用服务的注意事项: 使用(services)服务需要在app.,module.ts(根模块)中引用并 ...

  2. SpringMvc问题记录-Controller对于静态变量的访问分析

    问题描述 在于朋友的讨论中分析到一种场景,即:Controller对于一个类中的静态变量进行访问时,如果第一个接口修改该静态变量的数据,另外一个接口获取该静态变量的数据,那么返回的结果是什么? 操作步 ...

  3. 【原创】go语言学习(二)数据类型、变量量、常量量

    目录 1.标识符.关键字2. 变量量和常量量3. 数据类型4. Go程序基本结构 标识符.关键字 1.标识符是⽤用来表示Go中的变量量名或者函数名,以字⺟母或_开头.后⾯面跟着字⺟母 ._或数字2. ...

  4. 整理一些大厂的开源平台及github,向他们看齐...

    有人苦恼,该如何突破技术的局限性... 有人羡慕,技术上你怎么懂得这么多... 有人哀叹,唉,我已经学不动了... 我的总结(纯属个人想法):身处IT,就得不断学习和积累,才不会被狠狠地甩在身后.什么 ...

  5. 超详细的FreeRTOS移植全教程——基于srm32

    ### 准备 在移植之前,我们首先要获取到FreeRTOS的官方的源码包.这里我们提供两个下载链接: > 一个是官网:http://www.freertos.org/ > 另外一个是代码托 ...

  6. B-微积分-sign(符号)函数

    目录 sign(符号)函数 一.sign函数概述 二.python实现sign函数 更新.更全的<机器学习>的更新网站,更有python.go.数据结构与算法.爬虫.人工智能教学等着你:h ...

  7. python+selenium自动化框架

    ---恢复内容开始--- 主要使用的模块: selenium/webdriver模块(须准备Chrome驱动),主要用于调用浏览器实现自动点击. unittest模块,主要用于整合测试用例. xlrd ...

  8. Windows 10 更新后VMware Workstation pro无法运行 (无需卸载原版本VM)

    问题 描述:当前Windows版本是win10-1903,VMware版本比较老旧是VMware Workstation Pro 15.0.4:国庆节后微软推送了一个新的更新补丁,10月10日更新之后 ...

  9. Python爬取猫眼电影100榜并保存到excel表格

    首先我们前期要导入的第三方类库有; 通过猫眼电影100榜的源码可以看到很有规律 如: 亦或者是: 根据规律我们可以得到非贪婪的正则表达式 """<div class ...

  10. WeCenter3.1.7 blind xxe 分析

    xxe漏洞危害大,可以查看任意文件,执行系统命令,进行ddos等,但是本次漏洞有一条件,需要后台登录,所以危害降低了,下面是详细分析 在models/weixin.php public functio ...