头图 | CSDN 下载自东方 IC

作者 | sowhat1412  责编 | 张文

来源 | sowhat1412(ID:sowhat9094)

话不多说直接上干货,你我共勉。


构造器参数太多怎么办

解决办法 :引入 Builder 模式场景:当构造器有 5 个或者以上的构造参数时或者目前参数不多但是以后会不断增多的时候。demo 如下:

public class Computer {    protected String mBoard;    protected String mDisplay;    protected String mOs;

    private Computer(Builder builder) {        this.mOs = builder.mOs;        this.mBoard = builder.mBoard;        this.mDisplay = builder.mDisplay;    }
    @Override    public String toString() {        return "Computer{" +                "mBoard='" + mBoard + '\'' +                ", mDisplay='" + mDisplay + '\'' +                ", mOs='" + mOs + '\'' +                '}';    }

    static class Builder {        protected String mBoard;        protected String mDisplay;        protected String mOs;
        public Builder setmOs(String mOs) {            this.mOs = mOs;            return this;        }
        public Builder setmBoard(String mBoard) {            this.mBoard = mBoard;            return this;        }
        public Builder setmDisplay(String mDisplay) {            this.mDisplay = mDisplay;            return this;        }
        public Computer build() {            return new Computer(this);        }    }}

调用的时候如下:

 public static void main(String[] args) {        Computer macbook = new Computer.Builder()                .setmBoard("board")                .setmDisplay("sowhat")                .setmOs("Mac")                .build();        System.out.println(macbook);    }


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

经常用到的 Utils 类,比如 Java 自带的 java.util.Arrays 这样的类,工具类都尽量不要实例化。

public class Arrays {    private static final int MIN_ARRAY_SORT_GRAN = 8192;    private static final int INSERTIONSORT_THRESHOLD = 7;    private Arrays() {    }}


不要创建不必要对象

  • 能用基本类型到时候尽量要用基本类型。比如我们做数字运算,如果定义称了个 Long 类型,会涉及到==自动装箱==。大大耗时。

  • 对于一些程序中共用的参数尽量设置为 static 类型变量。

  • 对于一些耗时性较大的对象比如数据库连接,尽量创建数据库连接池。

  • 对于一些占据内存较大的对象也尽量少创建。因为在 eden 区来回倒腾,它累啊!

public class Sum { public static void main(String[] args) {  long start = System.currentTimeMillis();  Long sum = 0L; //对象  for(long i=0;i<Integer.MAX_VALUE;i++) {   sum = sum+i;   //new 20多亿的Long的实例  }  System.out.println("spend time:"+(System.currentTimeMillis()-start)+"ms"); }}

耗时:7042ms

public class Sum { public static void main(String[] args) {  long start = System.currentTimeMillis();  long sum = 0L; //基本类型  for(long i=0;i<Integer.MAX_VALUE;i++) {   sum = sum+i;  }  System.out.println("spend time:"+(System.currentTimeMillis()-start)+"ms"); }}

耗时:820ms


避免使用终结方法

4.1 finalze方法:

  1. finalize() 是 Object 的 protected 方法,子类可以覆盖该方法以实现==资源清理==工作,GC 在回收对象之前调用该方法。

  2. finalize() 与 C++中的析构函数不是对应的,但 Java 中的 finalize 的调用具有==不确定性==。

  3. finalize 方法在垃圾回收器准备垃圾回收前被调用,但是==不一定==会被调用 

  4. finalize() 其实是用来释放==不是==通过 java 的 new 关键字分配的内存,比如说通过本地方法调用了 c 程序,该 c 程序 malloc 分配了内存,那么垃圾回收器就不能通过 java 语言来释放内存,只能在 finalize 方法内通过本地方法调用 c 程序进行释放内存。

一个对象要被回收要经过一次标记可达法已经两次 check 才算死亡。

总结来说:finalize() 并不是必须要执行的,它只能执行 1 次或者 0 次。如果在 finalize 中建立对象关联,则当前对象可以复活 1 次。

4.2 System.gc 

用 System.gc() 的时候,其实并不会马上进行垃圾回收,甚至不一定会执行垃圾回收。查看 System.gc() 的源码可以看到只有当 justRanFinalization=true 的时候系统才会真正 GC。如果真要回收查看源码知道 System.gc() 要跟System.runFinalization() 一起搭配使用才好。

类跟成员的可访问性最小化

这个说白了就是设计模式中的迪米特法则。定义:要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则(Least Knowledge Principle, LKP)。

意义:迪米特法则的意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系,日常最常见的比如成员变量私有化。


使可变性最小化



尽可能的使用 final 来修饰一些变量,这样的化线程操作就不需要考虑同步问题,同时也对于一个变量不同 setX 方法也 OK。

优先使用复合胜过继承

继承是实现代码重用的有力手段,但是使用不当会导致软件变得脆弱。在包的内部使用继承是非常安全的,子类和超类的实现都处在同一个程序员的控制之下。对于专门为了继承而设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。然而们对于进行跨越包边界的继承,则要非常小心。“继承”在这里特指一个类扩展另一个类。需要我们对父类十分对了解才可继承,只有当子类和超类之间==确实存在==父子关系时,才可以考虑使用继承。否则都应该用复合,包装类不仅比子类更加健壮,而且功能也更加强大。Effective Java


接口优于抽象类

简而言之,Java 只允许单继承但是允许实现多个接口。通过接口扩充方法很简单,这样也复合设计模式中的开闭原则。接口可以简单的理解比抽象类还要抽象的一层,是我们对外提供的接口。同时这里提一下==骨架抽象类==。

骨架类存在的意义是实现一个接口中设计者认为比较公用的方法,然后在具体类实现的时候,具体类继承自骨架类同时实现接口类的若干方法。

HashSet 源码

public class HashSet<E>    extends AbstractSet<E>    implements Set<E>, Cloneable, java.io.Serializable

看 Set 源码

public interface Set<E> extends Collection<E> {}

AbstractSet 源码

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> //

AbstractSet 抽象类有若干函数已经实现了。

HashSet 实现了 Set<E>的接口,同时它还继承自 AbstractSet<E>这个骨架类(类中有一些公用方法)


可变参数谨慎使用

JDK5 增加了可变参数方法(variable arity method),可变参数方法接受 0 个或多个指定类型的参数。

可变参数机制:先创建一个数组,数组大小为调用位置所传递的参数数量,然后将参数值传递到数组中,最后将数组传递给方法。弊端如下:

  1. 如果所传参数为 null,方法里有对参数的引用(比如 args[0])时,那么就会在运行时失败(编译时却检测不出错误);

  2. 在对性能有要求时,我们要慎重考虑是否使用可变参数。因为,可变参数方法的调用都会引起 array 的内存分配和初始化,这会给性能带来损耗。

  3. 当可变参数的使用发生变动时(比如:以前用可变参数方法,现在用普通方法),所有引用参数列表的类的 .class 都要重新生成,因为可变参数的 array 的分配和初始化是在编译期间完成的。

Effective Java 给我们的建议是:假设调用可变参数的方法中,有 95%只是调用参数个数小于 4,那么就可以将个数小于 4 的方法,用普通方法定义;剩余的 5%,调用可变参数方法。

public void foo() {}public void foo(int a1) {}public void foo(int a1, int a2) {}public void foo(int a1, int a2, int a3) {}public void foo(int a1, int a2, int a3, int... rest) {}


尽量不要返回NULL,尽量返回零数组或集合

函数中如果返回 NULL,那么代码还要去重新判断返回值。JDK 都主动给我们提供好了 Collections.EMPTY_LIST。当然如果是对象就只能是 NULL 了另当别论。阿里巴巴开发手册也是这样建议的哦。

  1. 返回值为 null 并不会有什么问题,但是在“解引用”(dereference)时,调用者没有对 null 进行判断就会出现 NullPointerException。

  2. 在返回值为数组或者集合时,尽量返回长度为零的数组或者集合,而不是null,这样在调用时就能简化代码,减少不必要的麻烦,并且不必担心NullPointer 异常(除非这对性能会造成很大的影响)。

优先使用标准异常

总结来说好处就是:追求代码的重用考虑,在装载类的性能上面考虑。具体细节如下:

  1. 它使你对官方 API 更加易于学习和使用,因为他与程序员已经熟悉的习惯用法是一致的。

  2. 对于用到这些 API 的程序而言,他们的可读性会更好,因为他们不会出现很多程序员不熟悉的异常。

  3. 异常类越少,意味着内存印迹就越小,装载这些类的时间开销也越少。

尽量使用枚举替换 int

枚举的本质就是一个类的具体实例。枚举比普通业务类型 int 类型的区别跟好处,以及策略枚举的使用,这些以前写给直接看==>花样玩枚举。


局部变量作用域最小化

  1. 从系统 GC 的角度考虑(一个变量的周期越短整个 gc 过程越快);

  2. 从栈桢中的局部变量表的可重用性来看,作用域越小系统的栈桢空间利用了越大;

  3. 可以增强代码的可读性和可维护性,并降低出错的可能性。

应该:

  1. 在第一次使用某个局部变量的地方进行声明。

  2. 如果你还没有足够的前置信息来对一个变量进行有意义的初始化,就应该推迟这个声明,直到可以初始化为止。

  3. 尽量将方法小而集中。方法的功能尽量单一。


对于精度技术不用 float 或 double

Java 在允许 float 或者 double 类之间计算的时候会有误差。

double a = 0.2;double b = 0.1;double c = a+ b;System.out.println(c);-----0.30000000000000004

要用:BigDecimal 或者 FloatDecimal。具体类用法自行百度。


字符串操作少用 String

这点操作是大家都知道的,String 定义为 private final byte[] value 是不可变的。StringBuilder 和 StringBuffer 都继承于:AbstractStringBuilder 。他们的底层使用的是没有用 final 修饰的字符数组:char[]

  1. 如果要操作少量的数据用 String;

  2. 多线程操作字符串缓冲区下操作大量数据 StringBuffer;

  3. 单线程操作字符串缓冲区下操作大量数据 StringBuilder。


对资源的 close 建议分开操作

比如说我们有这样的一个 close 方法,

try{ a.close() b.close()}catch(Exception e){ ...}

上面这样写一旦 a 关闭的时候出错了,b 的关闭也会出错。尽量分开来关闭。

try{ a.close()}catch(Exception e){ ...}
try{ b.close()}catch(Exception e){ ...}


数据类型转换

基本数据类型转换为 String 的时候注意性能优化,比如 Integer 类型数据转换为String 一般有三种方法:

  1. Integer.toString 方法,首先推荐。速度最好

  2. String.valueOf() ,该方法底层调用的是 Integer.toString 方法,速度中等。

  3. i + "" 这种方法,底层是用 StringBuilder 实现,先用 append 方法拼接,再用 toString 方法获取字符串。速度最慢。


不用的对象记得置 NULL

我们不用一个空间对象后而没有将其置 NULL,JDK 底层代码对用不到的对象都会立马置空,如果不这样容易造成内存泄露,比如我自己实现了一个栈

public class Stack {  public  Object[] elements; private int size = 0; //指示器,用来指示当前栈顶的位置
    private static final int Cap = 16;
    public Stack() {     elements = new Object[Cap];    }    //入栈    public void push(Object e){     elements[size] = e;     size++;    }    //出栈    public Object pop(){     size = size-1;     Object object = elements[size];     elements[size] = null; //  这里很重要 JDK底层都是这样实现的,不用了,即使置NULL        return object;    }}


if 判断常量在前

if(i==1) 跟 if(1==i)看起来没有差别,但是从容错性上面来考虑,如果手误写成了 if(i=1)就是赋值语句了,而 if(1=i)则不会出现这样的错误。因此 if 判断建议常量在前,变量在后。


字符串变量比较的时候

str.equal("sowhat") 跟 “sowhat”.equal(str)功能上看是一样的,但是从代码的健壮性来看推荐后者,因为你无法确实 str 一定是非空,可以避免空指针异常。


同步方法跟同步方法块

尽量使用同步方法块而不是同步方法,这点在多线程模块中的 synchronized 锁 方法块文章中已经讲得很清楚了,除非我们能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。

方法要尽可能小

一个方法要尽量实现单一指责,方法编译后字节码越小越可能会引发 JIT 的方法内联,

public void SetAge(int age){   this.age = age}-------JIT 优化后直接就是 如下,避免类方法调用。this.age = age 


一定记得写注释

代码写的再好也要记得写类跟方法的大概注释,不然接收你工作的人绝对分分钟化身祖安玩家!

以上。

更多精彩推荐
☞Windows 游戏之父 Eric Engstrom 意外去世,享年55岁

☞苹果 M1 芯片预示着 RISC-V 完全替代 ARM?
☞管理大型共享数据库,做到这几点不再头疼!
☞弃用 Cookie!

☞前端诸神大战,Vue、React 依旧笑傲江湖
☞计算机巨星陨落!图灵奖得主 Edmund Clarke 因感染“新冠”逝世

☞Github 超 20000 Star,最火开源视频库 FFmpeg 这 20 年!

点分享点点赞点在看
Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐