《Effective Java》

《Effective Java》

(一)创建 & 销毁对象

public构造方法 -> 静态工厂方法

使用静态工厂方法代替构造方法返回类的实例,避免在没有static工厂方法的情况下,提供public构造方法。如:

1
2
3
4
//传入boolean对象,返回Boolean实例
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean:FALSE;
}

优点

  1. 构造方法全部同名,静态工厂可以拥有有意义的名称
  2. 构造方法每次都需要创建一个新对象返回,而静态工厂可以避免重复创建对象
  3. 构造方法每次返回的都是同样的类对象,而静态工厂可以根据传入参数的不同返回不同的对象,也可以返回子类的对象

缺点

没有public/protected构造方法的类无法被继承,但是这是因祸得福,鼓励程序员使用implements代替extends

构造方法参数过多时使用Builder模式

单例模式

避免创建不必要的对象

在应该重用对象时,不要新建对象

  1. 不要使用new String("")
1
2
3
4
String s1 = "yingying";
String s2 = new String("yingying");
//s1和s2的值是一致的
//但是s2比起s1来,多创建了一个无用的堆对象String
  1. 优先使用基本类型,避免无意义的自动拆箱装箱
1
2
3
4
5
Long sum = 0;
for(long i = 0; i < 100; i++) {
sum += i;
}
//由于sum为Long,每次都会包含一个无意义的自动装箱拆箱

5.消除过期引用

StackArrayList这种可以用数组实现的类中,会见到elementData[--size] = null;这样的语句,既然Java存在自动GC,理论上我们--size即可,为什么还要把元素置为null呢?
因为即使将数组长度缩小,原有对象也会因为隐式引用的存在不会被GC,最终导致OOM,所以最好显示将对象置为null,通知GC回收

6.不要使用Finalizer和Cleaner

Java中Finalizer和Cleaner都是不保证何时会执行的,即使是System.gc()也不会就是增加其执行的几率,所以如果对时间敏感,最好不要使用。

7.try-with-resources取代try-finally

当一个外部资源的句柄对象实现了AutoCloseable接口,JDK7中便可以利用try-with-resource语法(try(...) {})更优雅的关闭资源。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//JDK7之前 try-finally,在finally中关闭连接
try{
fis = new FileInputStream(new File("test") );
System.out.println(fis.read() );
}catch(IOException e) {
//...
}finally{
if(fis != null) {
try{
fis.close();
}catch(IOExceptio e) {
//...
}
}
}

//JDK7 try-with-resources,在try()中自动关闭连接
try(FileInputStream fis = new FileInputStream(
new File("test") ) ) {
System.out.println(fis.read() );
}catch(IOException exception) {}

(二)所有对象的通用方法

重写equals()必须重写hashCode()

必须保证:两个对象equals()返回true,则其hashCode()相等;两个对象hashCode()相等,equals()不一定返回true


(三)类和接口

1.使类和成员的可访问性最小

让每个类或成员尽可能地不可访问

  1. private:只能在类中访问
  2. default:默认,可以被类所属包中的任何类访问
  3. proteceted:可以被类所属包中的任何类、包外的子类访问
  4. public:任何地方都可以访问

2.最小化可变性

不可变类:

  1. 不要提供修改对象状态的方法
  2. 确保类不可被继承(final
  3. 将所有属性设置为final
  4. 将所有属性设置为private
  5. 确保对可变组件的互斥访问

不可变对象是线程安全的,因为它们不需要同步

3.使用implements代替extends

接口优于抽象类

4.尽量使用静态成员类

可以用于[]构造器模式和单例模式](http://www.dragonbaby308.com/Design-Pattern/)。


(四)合理搭配 泛型 & 可变参数

可变参数会创造一个数组保存,所以该数组一定是可见的实际类型,与泛型冲突,可变参数泛型同时使用会导致编译器警告。


(五)枚举

http://www.dragonbaby308.com/JavaBase-Enum/


(六)注解

@Retention(RetentionPolicy.xxx):注解保留策略

  1. RetentionPolicy.SOURCE源码有效,编译时抛弃
  2. RetentionPolicy.CLASS默认行为 —— 编译时有效,运行时抛弃
  3. RetentionPolicy.RUNTIME运行时保留注解,可以通过反射获取

@Target({ElementType.xxx}):目标

  1. ElementType.TYPE对类/接口/枚举/注解都有效
  2. ElementType.PACKAGE对包有效
  3. ElementType.PARAMETER对参数有效
  4. ElementType.LOCAL_VARIABLE对局部变量有效
  5. ElementType.FIELD对成员变量有效
  6. ElementType.CONSTRUCTOR对构造器有效
  7. ElementType.METHOD对成员方法有效
  8. ElementType.ANNOTATION_TYPE对注解有效

@Documented:说明注解会被包含在javadoc

@Inherited:子类可以继承父类的该注解

Demo

  1. @Autowired
1
2
3
4
5
6
7
8
9
10
11
//@Autowired对构造器、成员方法、参数、成员变量、注解有效
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})

//@Autowired注解在运行时会保留,可以通过反射获取
@Retention(RetentionPolicy.RUNTIME)

//@Autowired注解会被包含在javadoc中
@Documented
public @interface Autowired {
boolean required() default true;
}
  1. @Qualifier
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//@Qualifier对成员变量、成员方法、参数、类、接口、枚举、注解有效
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})

//@Qualifier注解在运行时会保留,可以通过反射获取
@Retention(RetentionPolicy.RUNTIME)

//子类可以继承父类的@Qualifier注解
@Inherited

//@Qualifier注解会被包含在javadoc中
@Documented

public @interface Qualifier {
String value() default "";
}

(七)Lambda & Stream

Lambda优于匿名类,方法引用(::)优于Lambda。
forEach操作应该仅仅用于报告流操作的结果,而不适用于执行计算


(八)方法

返回空的数组/集合,不要返回null

返回null意味着必须在调用方对null进行判空处理,否则就可能抛出NullPointerException,而调用方很可能会忘记进行判空处理,所以应该返回一个空的数组/集合,而不是null

1
2
3
4
5
//不要直接返回null
//return chessesInStock.isEmpty() ? null : new ArrayList<>(chessInStock);

//返回空的数组/集合
return chessesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(chessInStock);

谨慎使用Optional

如果发现自己编写的方法不是总能返回值,并且该方法的用户在每次调用时考虑这种情况很重要,那么可以返回一个Optional<T>,但是,要注意到:返回Optional<T>会带来实际的性能后果,性能关键的方法最好是抛出null或异常,最后,除了返回值中,不应该在任何地方使用Optional<T>


(九)通用编程

1.长数字下划线方便阅读

从JDK7开始,合法的下划线对数字字面量的值没有影响,但会使它更容易阅读。比如public static final double AVOGADROS_NUMBER = 6.022_140_857e23;等价于public static final double AVOGADROS_NUMBER = 6.022140857e23;但是前者更容易阅读。

2.需要精确的结果时避免float/double

在进行货币计算时,使用BigDecimalint/long代替float/double,因为后者的精度不够。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
System.out.println("true");
}else{
System.out.println("false");
}
//输出:false
//因为float精度丢失,所以想要使用精确计算不能使用float

//===========================================================

//即使使用Float包装器,也无法保证精度
Float fa = Float.valueOf(1.0f - 0.9f);
System.out.println(fa);
Float fb = Float.valueOf(0.9f - 0.8f);
System.out.println(fb);
if (fa.equals(fb)) {
System.out.println("true");
}else{
System.out.println("false");
}
//输出:
//0.100000024
//0.099999964
//false

//===========================================================

//想要精确计算可以使用BigDecimal
BigDecimal bda = new BigDecimal(1.0f - 0.9f);
BigDecimal bdb = new BigDecimal(0.9f - 0.8f);
if (bda.compareTo(bdb) >= 0) {
System.out.println("true");
}else{
System.out.println("false");
}
//输出:true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//BigDecimal新建:构造函数
final BigDecimal TEN_CENTS = new BigDecimal(".10");
BigDecimal funds = new BigDecimal("1.00");

int cnt = 0;

//BigDecimal比较大小:compareTo()
//BigDecimal加法:add()
//BigDecimal减法:subtract()
for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS) ) {
funds = funds.subtract(price);
cnt++;
}
System.out.println("可以买的件数:" + cnt);
System.out.println("剩余的资金:" + funds);

3.警惕隐式自动装箱/拆箱

比如使用泛型导致的隐式装箱/拆箱

4.使用StringBuilder而不是String操作大量字符串

可以参考String连接和StringBuilder.add()的JMH性能测试结果


(十)异常

如果使用了异常,最好在注释中通过@throws标记。

常见异常:

1
2
3
4
5
6
IllegalArgumentException        不合适的非null参数值
IllegalStateException 方法调用状态不和的对象
NullPointerException 禁止使用null时参数为null
IndexOutOfBoundsException 索引参数越界
ConcurrentModificationException 在禁止并发修改对象的地方检测到并发修改
UnsupportedOperationException 不支持的操作

(十一)并发

1.使用线程池代替单个线程

1
2
3
4
ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.execute(runnable);
//优雅关闭,已提交的线程会执行完
threadPool.shutdown();

2.优先使用并发应用程序替代wait/notify

wait/notify的使用是很困难的,应该使用更高级别的并发应用程序,比如CountDownLatchsynchronized


(十二)序列化

其他实现如gson要优于Java本身的序列化。

fastJson频发bug,不建议使用。

-------------本文结束感谢您的阅读-------------

本文标题:《Effective Java》

文章作者:DragonBaby308

发布时间:2019年08月01日 - 22:35

最后更新:2020年04月13日 - 23:49

原始链接:http://www.dragonbaby308.com/effective-java/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

急事可以使用右下角的DaoVoice,我绑定了微信会立即回复,否则还是推荐Valine留言喔( ఠൠఠ )ノ
0%