编写高效优雅 Java 程序
面向对象
1.构造器参数太多怎么办?
如果参数很多,会导致构造方法非常多,拓展性差,代码难编写,且难以看懂。
用 JavaBeans 模式, get 和 set 一行构造编程多行代码实现,需要使用额外机制确保一致性和线程安全。
用 builder 模式
- 5 个或者 5 个以上的成员变量
- 参数不多,但是在未来,参数会增加
Builder 模式
属于对象的创建模式,一般有
- 抽象建造者:一般来说是个接口,包含
1)建造方法,建造部件的方法(不止一个)
2)返回产品的方法 - 具体建造者
- 导演者,调用具体的建造者,创建产品对象
- 产品,需要建造的复杂对象
简单的Builder模式(建造者/构造者模式)
/**
* @author dujiayu
* @version V1.0
* @Package com.djy.demo.builder
* @date 2020/7/7 22:29
* @Copyright 简单构造者模式
*/
public class Computer {
private final String cpu;//必须
private final String ram;//必须
private final int usbCount;//可选
private final String keyboard;//可选
private final String display;//可选
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.usbCount = builder.usbCount;
this.keyboard = builder.keyboard;
this.display = builder.display;
}
public static class Builder {
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
public Builder(String cup, String ram) {
this.cpu = cup;
this.ram = ram;
}
public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Computer build() {
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
public static void main(String[] args) {
Computer computer = new Computer.Builder("因特尔", "三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2)
.build();
System.out.println(computer.toString());
}
}
标准的Builder模式(建造者/构造者模式)
产品类
/**
* @author dujiayu
* @version V1.0
* @Package com.djy.demo.builder
* @date 2020/7/7 22:35
* @Copyright 完整构造者模式
*/
public class Computer1 {
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
public Computer1(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public void setUsbCount(int usbCount) {
this.usbCount = usbCount;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public void setDisplay(String display) {
this.display = display;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
}
抽象构建者类
/**
* @author dujiayu
* @version V1.0
* @Package com.djy.demo.builder
* @date 2020/7/7 22:40
* @Copyright 抽象构建者类
*/
public abstract class ComputerBuilder1 {
public abstract void setUsbCount();
public abstract void setKeyboard();
public abstract void setDisplay();
public abstract Computer1 getComputer();
}
指导者类(Director)
/**
* @author dujiayu
* @version V1.0
* @Package com.djy.demo.builder
* @date 2020/7/7 22:47
* @Copyright 指导者类(Director)
*/
public class ComputerDirector1 {
public void makeComputer(ComputerBuilder1 builder) {
builder.setUsbCount();
builder.setDisplay();
builder.setKeyboard();
}
}
实体构建者类
/**
* @author dujiayu
* @version V1.0
* @Package com.djy.demo.builder
* @date 2020/7/7 22:43
* @Copyright 实体构建者类
*/
public class MacComputerBuilder1 extends ComputerBuilder1 {
private Computer1 computer;
public MacComputerBuilder1(String cpu, String ram) {
computer = new Computer1(cpu, ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(2);
}
@Override
public void setKeyboard() {
computer.setKeyboard("苹果键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("苹果显示器");
}
@Override
public Computer1 getComputer() {
return computer;
}
}
演示类
/**
* @author dujiayu
* @version V1.0
* @Package com.djy.demo.builder
* @date 2020/7/7 22:48
* @Copyright 演示
*/
public class BuilderMain {
public static void main(String[] args) {
ComputerDirector1 director = new ComputerDirector1();//1
ComputerBuilder1 builder = new MacComputerBuilder1("I5处理器", "三星125");//2
director.makeComputer(builder);//3
Computer1 macComputer = builder.getComputer();//4
System.out.println("mac computer:" + macComputer.toString());
}
}
2.不需要实例化的类应该构造器私有
如,一些工具类提供的都是静态方法,这些类是不应该提供具体的实例的。可以参考 JDK 中的 Arrays。 好处:防止使用者 new 出多个实例。
3.不要创建不必要的对象
- 避免无意中创建的对象,如自动装箱
- 可以在类的多个实例之间重用的成员变量,尽量使用 static。
性能对比。
是不要创建不必要的对象,而不是不要创建对象。 对象池要谨慎使用,除非创建的对象是非常昂贵的操作,如数据库的连接,巨型对象等等。
4.避免使用终结方法
finalizer 方法,jdk 不能保证何时执行,也不能保证一定会执行。如果有确定要释放的资源应该用 try/finally。
5.使类和成员的可访问性最小化
- 模块对外部其他模块来说,隐藏其内部数据和其他实现细节——封装
- 编写程序和设计架构,最重要的目标之一就是模块之间的解耦。使类和成员的可访问性最小化无疑是有效的途径之一。 类似于微服务
6.使可变性最小化
尽量使类不可变,不可变的类比可变的类更加易于设计、实现和使用,而且更不容易出错,更安全。
常用的手段:
- 不提供任何可以修改对象状态的方法;
- 使所有的域都是 final 的。
- 使所有的域都是私有的。
- 使用写时复制机制。
7.复合优先于继承
- 继承容易破坏封装性,而且会使子类的实现依赖于父类。
- 复合则是在类中增加一个私有域,引用类的一个实例,这样的话就避免了依赖类的具体实现。
- 只有在子类确实是父类的一个子类型时,才比较适合用继承。
- 继承需要开发者对父类的结构有一定了解。
- 实际使用,如果肯定是父类的子类,使用继承,如果不很肯定,使用复合。
8.接口优于抽象类
- 接口只有方法申明,抽象类可以写方法的实现。
- java 是个单继承的(不能继承多个抽象类),但是类允许实现多个接口。
- 所以当发生业务变化时,新增接口,实现接口只需要新曾接口即可。但是抽象类有可能导致不需要变化的类也不得不实现新增的业务方法。
- JDK 源码中常用的一种设计方法:定义一个接口,声明一个抽象的骨架类实现接口,骨架类类实现通用的方法,而实际的业务类可以同时实现接口又继承 骨架类,也可以只实现接口。
- 如 HashSet 实现了 implementsSet 接口 但是又 extends 类 AbstractSet,而 AbstractSet 本身也实现了 Set 接口。其他如 Map,List 都是这样的设计的。
方法
9.可变参数要谨慎使用
可变参数是允许传 0 个参数的
如果是参数个数在 1~多个之间的时候,要做单独的业务控制。
10.返回零长度的数组或集合,不要返回 null
方法的结果返回 null,会导致调用方的要单独处理为 null 的情况。返回零长度,调用方可以统一处理,如使用 foreach 即可。 JDK 中也为我们提供了 Collections.EMPTY_LIST 这样的零长度集合
11.优先使用标准的异常
要尽量追求代码的重用,同时减少类加载的数目,提高类装载的性能。
常用的异常:
- IllegalArgumentException – 调用者传递的参数不合适
- IllegalStateException – 接收的对象状态不对,
- NullPointerException
- UnsupportedOperationException –不支持的操作
通用程序设计
12.用枚举代替 int 常量
声明的一个枚举本质就是一个类,每个具体的枚举值就是这个枚举类的实例。
- 使用常量容易在写代码时写错
- 使用常量如果要使用描述时比较麻烦
- 其他类使用常量时,类编译时会把常量值直接写到字节码中,如果常量值有变化,所有相关的类需要重新编译,否则会不可预料的错误
枚举高级:
枚举和行为绑定
所谓枚举的本质就是一个类,而枚举中定义的每一个具体的枚举类型其实就是这个枚举类的一个实例。
13.将局部变量的作用域最小化
- 在第一次使用的地方进行声明
- 局部变量都是要自行初始化,初始化条件不满足,就不要声明 最小化的好处,减小局部变量表的大小,提高性能;同时避免局部变量过早声明导致不正确的使用。
14.精确计算,避免使用 float 和 double
float 和 double 在 JVM 存储的时候,有部分要做整数位,有部分要做小数位,所以存在精度上的问题 可以使用 int 或者 long 以及 BigDecimal
15.当心字符串连接的性能
在存在大量字符串拼接或者大型字符串拼接的时候,尽量使用 StringBuilder 和 StringBuffer