编写高效优雅的Java程序

1.面向对象

1.1 构造器参数太多怎么办?

FoodNormal.java
对于多个成员变量的类构造函数,对于不同参数个数的构造函数一般需要定义多个构造函数,比较麻烦。

FoodJavaBean.java
对于JavaBean模式,使用set/get方式可能会使得得到的对象,某个成员变量没有初始化就使用了该成员变量,会有问题。

FoodBuilder.java
比较好的初始化方式是用Builder构造者模式。初始化直接采用链式初始化的方式进行初始化。

用builder模式,用在
1、5个或者5个以上的成员变量
2、参数不多,但是在未来,参数会增加

Builder模式:
builder模式参考代码
属于对象的创建模式,一般有
1、 抽象建造者:一般来说是个接口,包含
 1)建造方法,建造部件的方法(不止一个)
 2)返回产品的方法
2、 具体建造者
3、 导演者,调用具体的建造者,创建产品对象
4、 产品,需要建造的复杂对象
对于客户端(Mingyun.java),创建导演者和具体建造者,并把具体建造者交给导演者,然后由客户端通知导演者操纵建造者进行产品的创建。
在实际的应用过程中,有时会省略抽象建造者和导演者。

编写高效优雅的Java程序_第1张图片

1.2 不需要实例化的类应该构造器私有

一些工具类提供的都是静态方法,这些类是不应该提供具体的实例的。可以参考JDK中的Arrays。

1.3 不要创建不必要的对象

1、 避免无意中创建的对象,如自动装箱


编写高效优雅的Java程序_第2张图片

2、 可以在类的多个实例之间重用的成员变量,尽量使用static。


编写高效优雅的Java程序_第3张图片

修改为如下,只创建一次,对象间共享:

    private static final Date Begin;
    //private static final Date End;
    
    static {
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        cal.set(1990, Calendar.JANUARY,1,0,0,0);
        Begin = cal.getTime();
        //.....
    }

但是,要记住,是不要创建不必要的对象,而不是不要创建对象。
对象池要谨慎使用,除非创建的对象是非常昂贵的操作,如数据库的连接,巨型对象等等。

1.4 避免使用终结方法

finalizer方法,jdk不能保证何时执行,也不能保证一定会执行。如果有确实要释放的资源应该用try/finally。

1.5 使类和成员的可访问性最小化

编写程序和设计架构,最重要的目标之一就是模块之间的解耦。使类和成员的可访问性最小化无疑是有效的途径之一。

1.6 使可变性最小化

尽量使类不可变,不可变的类比可变的类更加易于设计、实现和使用,而且更不容易出错,更安全。
常用的手段:
不提供任何可以修改对象状态的方法;
使所有的域都是final的。
使所有的域都是私有的。
使用写时复制机制。带来的问题:会导致系统产生大量的对象,而且性能有一定的影响,需要在使用过程中小心权衡。

1.7 优先使用复合

继承容易破坏封装性,而且会使子类的实现依赖于父类。
复合则是在类中增加一个私有域,引用类的一个实例,这样的话就避免了依赖类的具体实现。
只有在子类确实是父类的一个子类型时,才比较适合用继承。

1.8 接口优于抽象类

java是个单继承的,但是类允许实现多个接口。
所以当发生业务变化时,新增接口,并且需要进行业务变化的类现新接口即可。但是抽象类有可能导致不需要变化的类也不得不实现新增的业务方法。
在JDK里常用的一种设计方法是:定义一个接口,声明一个抽象的骨架类实现接口,骨架类类实现通用的方法,而实际的业务类可以同时实现接口又继承骨架类,也可以只实现接口。
如HashSet实现了implements Set接口 但是又extends 类AbstractSet,而AbstractSet本身也实现了Set接口。其他如Map,List都是这样的设计的。

2.方法

2.1 可变参数要谨慎使用

可变参数是允许传0个参数的
如果是参数个数在1~多个之间的时候,要做单独的业务控制。

    //可能很多 0~很多
    static int sum(int... args) {
        int sum = 0;
        for (int arg : args)
            sum += arg;
        return sum;
    }
    
    //要求参数的个数,是1~多个
    //
    static int sum1(int... args) {
        if(args.length==0) {
            //做点异常处理
        }
        if(args[0]==100) {

        }
        for(int i=1;i

2.2 返回零长度的数组或集合,不要返回null

方法的结果返回null,会导致调用方的要单独处理为null的情况。返回零长度,调用方可以统一处理,如使用foreach即可。
JDK中也为我们提供了Collections.EMPTY_LIST这样的零长度集合

2.3 优先使用标准的异常

要尽量追求代码的重用,同时减少类加载的数目,提高类装载的性能。
常用的异常:
IlegalAraumentException -- 调用者传递的参数不合适
lllegalStateException – 接收的对象状态不对,
NullPoint
UnsupportedOperationException –不支持的操作

3.通用程序设计

3.1 用枚举代替int常量

声明的一个枚举本质就是一个类,每个具体的枚举值就是这个枚举类的实例。
SampleIntConst.java
常量会带来一些小问题,所以优先使用枚举:

  • 1)常量传播导致的修改不一致问题
  • 2)常量值修改变动的问题
  • 3)打印时,需要配套一个字符串说明

DepotEnum.java
枚举实际上是类,可以定义自己的方法和成员变量。

ActiveEnum.java
对于算术运算类型的枚举类,相比于使用switch来区分,可以定义一个oper的抽象类,然后每一个运算符都覆盖该oper。

PayDay.java
BetterPayDay.java
相比如使用switch区分不同的枚举,可以使用嵌套枚举类作为策略枚举,对外层枚举进行区分。
这个在javac编译器中有使用。参考 javac编译器框架1——词法和语法分析器 中对Token类型的定义

3.2 将局部变量的作用域最小化

1、 在第一次使用的地方进行声明
2、 局部变量都是要自行初始化,初始化条件不满足,就不要声明
最小化的好处,减小局部变量表的大小,提示性能;同时避免局部变量过早声明导致不正确的使用。

3.3 精确计算避免使用float和double

可以使用int或者long以及BigDecimal。

3.4 当心字符串连接的性能

在存在大量字符串拼接或者大型字符串拼接的时候,尽量使用StringBuilder和StringBuffer。
参考StringUnion.java
+拼接字符串的性能很低,所以如果能省掉拼接过程,会大幅提高性能。也间接证明+性能很低。
程序运行结果:

直接打印模式,次数:100000000:spend time :7668ms
先判断再打印模式,次数:100000000:spend time :4ms

3.5 控制方法的大小

参考

  • 1)享学课堂Mark老师笔记

你可能感兴趣的:(编写高效优雅的Java程序)