编写高效优雅Java程序
面向对象
构造器参数太多怎么办?
如果参数很多,会导致构造方法非常多,拓展性差,代码难编写,且难以看懂。
用JavaBeans模式,
get和set
一行构造编程多行代码实现,需要使用额外机制确保一致性和线程安全。
用builder模式,
1、5个或者5个以上的成员变量
2、参数不多,但是在未来,参数会增加
Builder模式:
属于对象的创建模式,一般有
1、 抽象建造者:一般来说是个接口,包含1)建造方法,建造部件的方法(不止一个),2)返回产品的方法
2、 具体建造者
3、 导演者,调用具体的建造者,创建产品对象
4、 产品,需要建造的复杂对象
对于客户端,创建导演者和具体建造者,并把具体建造者交给导演者,然后由客户端通知导演者操纵建造者进行产品的创建。
在实际的应用过程中,有时会省略抽象建造者和导演者。
优势:如果当大多数参数是可选时,代码易于阅读和编写,比JavaBean更加安全。
示例:
public class BoxBuilder {
//required必须参数
private final String Name;//礼盒名称
private final int Price;//礼盒价格
//optional可选参数
private int zz;//粽子
private int xyd;//咸鸭蛋
private int ldg;//绿豆糕
private int yb;//月饼
private int jg;//坚果
//具体建造者
public static class Builder{
//required必须参数
private final String Name;//礼盒名称
private final int Price;//礼盒价格
//optional可选参数
private int zz;//粽子
private int xyd;//咸鸭蛋
private int ldg;//绿豆糕
private int yb;//月饼
private int jg;//坚果
//构造方法
public Builder(String name, int price) {
this.Name = name;
this.Price = price;
}
//建造方法
public BoxBuilder builder(){
return new BoxBuilder(this);
}
public Builder zz(int value){
this.zz=value;
return this;
}
public Builder xyd(int value){
this.xyd=value;
return this;
}
//......
}
private BoxBuilder (Builder builder){
Name = builder.Name;
Price =builder.Price;
zz =builder.zz;
xyd =builder.xyd;
ldg=builder.ldg;
yb=builder.yb;
jg=builder.jg;
}
public static void main(String[] args) {
BoxBuilder box1 = new Builder("端午节礼盒1",120)
.zz(8)
.xyd(4)
.builder();
}
}
不需要实例化的类应该构造器私有
如,一些工具类提供的都是静态方法,这些类是不应该提供具体的实例的。可以参考JDK中的Arrays。
好处:防止使用者new出多个实例。
不要创建不必要的对象
避免无意中创建的对象,如使用Long、Integer等。
可以在类的多个实例之间重用的成员变量,尽量使用static。
对象池要谨慎使用,除非创建的对象是非常昂贵的操作,如数据库的连接,巨型对象等等。
避免使用终结方法
finalizer方法,jdk不能保证何时执行,也不能保证一定会执行。如果有确实要释放的资源应该用try/finally。
使类和成员的可访问性最小化
模块对外部其他模块来说,隐藏其内部数据和其他实现细节——封装
编写程序和设计架构,最重要的目标之一就是模块之间的解耦。使类和成员的可访问性最小化无疑是有效的途径之一。
类似于微服务,
使可变性最小化
尽量使类不可变,不可变的类比可变的类更加易于设计、实现和使用,而且更不容易出错,更安全。
常用的手段:
不提供任何可以修改对象状态的方法;
使所有的域都是final的。
使所有的域都是私有的。
使用写时复制机制。
复合优先于继承
继承容易破坏封装性,而且会使子类的实现依赖于父类。
复合则是在类中增加一个私有域,引用类的一个实例,这样的话就避免了依赖类的具体实现。
只有在子类确实是父类的一个子类型时,才比较适合用继承。
继承需要开发者对父类的结构有一定了解。
实际使用,如果肯定是父类的子类,使用继承,如果不很肯定,使用复合。
接口优于抽象类
接口只有方法申明,抽象类可以写方法的实现。
java是个单继承的(不能继承多个抽象类),但是类允许实现多个接口。
所以当发生业务变化时,新增接口,实现接口只需要新增接口即可。但是抽象类有可能导致不需要变化的类也不得不实现新增的业务方法。
JDK源码中常用的一种设计方法:定义一个接口,声明一个抽象的骨架类实现接口,骨架类类实现通用的方法,而实际的业务类可以同时实现接口又继承骨架类,也可以只实现接口。
如HashSet实现了Set接口继承AbstractSet,而AbstractSet本身也实现了Set接口。其他如Map,List都是这样的设计的。
方法
可变参数要谨慎使用
可变参数是允许传0个参数的
如果是参数个数在1~多个之间的时候,要做单独的业务控制。
具体代码不优雅。
返回零长度的数组或集合,不要返回null
方法的结果返回null,会导致调用方的要单独处理为null的情况。返回零长度,调用方可以统一处理,如使用foreach即可。
JDK中也为我们提供了Collections.EMPTY_LIST这样的零长度集合
优先使用标准的异常
要尽量追求代码的重用,同时减少类加载的数目,提高类装载的性能。
常用的异常:
IllegalArgumentException – 调用者传递的参数不合适
IllegalStateException – 接收的对象状态不对,
NullPointerException
UnsupportedOperationException –不支持的操作
通用程序设计
用枚举代替int常量
声明的一个枚举本质就是一个类,每个具体的枚举值就是这个枚举类的实例。
- 使用常量容易在写代码时写错
- 使用常量如果要使用描述时比较麻烦
- 其他类使用常量时,类编译时会把常量值直接写到字节码中,如果常量值有变化,所有相关的类需要重新编译,否则会不可预料的错误
枚举高级:
枚举和行为绑定
将局部变量的作用域最小化
- 在第一次使用的地方进行声明
- 局部变量都是要自行初始化,初始化条件不满足,就不要声明
最小化的好处,减小局部变量表的大小,提高性能;同时避免局部变量过早声明导致不正确的使用。
精确计算,避免使用float和double
float和double在JVM存储的时候,有部分要做整数位,有部分要做小数位,所以存在精度上的问题
可以使用int或者long以及BigDecimal
当心字符串连接的性能
在存在大量字符串拼接或者大型字符串拼接的时候,尽量使用StringBuilder和StringBuffer
控制方法的大小
方法不要写太多行
参考:King——笔记,JVM(2)