Effective Java
一、 静态工厂方法
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
二、 创建对象的时候使用Builder
Builder 方式创建的对象,在调用 build() 方法之前是不会创建Request 对象的,所有的属性设置都必须在 build() 方法之前,而且创建了 Request 对象后就不可以更改其属性了,这就保证了对象状态的唯一性,而且代码的可读性也提高了。
public final class Request {
final HttpUrl url;
Request(Builder builder) {
this.url = builder.url;
}
public static class Builder {
HttpUrl url;
...
/*
* Request 对象创建器,想得到一个Request 对象必须使用build 方法,
* 在方法中增加对Builder参数的验证,并以异常的形式告诉给开发人员。
*/
public Request build() {
/**
* 比如下面判断如果 url 是null的话就会抛出异常
*/
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
三、 序列化和反序列化的概念
- 把对象转换为字节序列的过程称为对象的序列化。
- 把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
- 在网络上传送对象的字节序列。
// Serialize
try {
FileOutputStream fileOut = new FileOutputStream("out.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(singleton);
out.close();
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
singleton.setValue(2);
// Deserialize
Singleton singleton2 = null;
try {
FileInputStream fileIn = new FileInputStream("out.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
singleton2 = (Singleton) in.readObject();
in.close();
fileIn.close();
} catch (IOException i) {
i.printStackTrace();
} catch (ClassNotFoundException c) {
System.out.println("singletons.SingletonEnum class not found");
c.printStackTrace();
}
四、 用枚举创建单例
普通方法创建单例,构造函数不能是public
- 普通创建单例方法,解决反序列化造成新建对象的解决方法如下:
public class Singleton implements Serializable{
public static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
protected Object readResolve() {
return INSTANCE;
}
}
- 普通创建单例方法,通过发射原理可以新建对象
通过这种方式,不可访问的私有构造函数变得可访问,并且使类成为单例的整个想法中断。
Singleton singleton = Singleton.INSTANCE;
Constructor constructor = singleton.getClass().getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
Singleton singleton2 = (Singleton) constructor.newInstance();
if (singleton == singleton2) {
System.out.println("Two objects are same");
} else {
System.out.println("Two objects are not same");
}
singleton.setValue(1);
singleton2.setValue(2);
System.out.println(singleton.getValue());
System.out.println(singleton2.getValue());
}
- 用枚举创建单例
public enum Singleton {
INSTANCE;
}
上面的三行构成一个单例,不会出现上面提到的问题。由于枚举本质上是可序列化的,因此我们不需要使用可序列化的接口来实现它。反射问题也不存在。因此,100%保证JVM中只存在一个单例实例。因此,建议将此方法作为在Java中制作单例的最佳方法。
在序列化枚举时,字段变量不会被序列化。例如,如果我们对SingletonEnum 类进行序列化和反序列化 ,我们将丢失该int value 字段的值
Demo
public enum SingletonEnum {
INSTANCE;
int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public class EnumDemo {
public static void main(String[] args) {
SingletonEnum singleton = SingletonEnum.INSTANCE;
System.out.println(singleton.getValue());
singleton.setValue(2);
System.out.println(singleton.getValue());
}
}
五、创建不能被实例化的类
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
... // Remainder omitted }
六、使用依赖注入,不用硬编码
依赖是指一个对象持有其他对象的引用。依赖注入则是将这些依赖对象传递给被依赖对象,而不是被依赖对象自己创建这些对象。
依赖注入框架有Dagger等
七、及时消除对过时对象的引用
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
八、避免使用FINALIZERS和CLEANERS
九、优先使用try-with-resources而不是try-finally
详解:http://tutorials.jenkov.com/java-exception-handling/try-with-resources.html
int data = input.read();
while(data != -1){
System.out.print((char) data);
data = input.read();
}
}
注意方法内的第一行:
try(FileInputStream input = new FileInputStream(“file.txt”)){
这是try-with-resources构造。该FileInputStream 变量在try括号内声明,并分配给变量。
当try块完成时,FileInputStream将自动关闭,因为FileInputStream实现了Java接口java.lang.AutoCloseable。实现此接口的所有类都可以在try-with-resources构造中使用。
如果从try-with-resources块内部抛出异常,并且在FileInputStream关闭时close()(调用时),则try块内抛出的异常将抛出到外部世界。FileInputStream禁止关闭时抛出的异常。这与本文中第一个示例中发生的情况相反,使用旧样式异常处理(关闭finally块中的资源)。
使用多种资源
您可以在try-with-resources块中使用多个资源,并使它们全部自动关闭。这是一个例子:
private static void printFileJava7()throws IOException {
try(FileInputStream input = new FileInputStream(“file.txt”);
BufferedInputStream bufferedInput = new BufferedInputStream(input)
){
int data = bufferedInput.read();
while(data!= -1){
System.out.print((char)data);
data = bufferedInput.read();
}
}
}
该try-with-resources构造不仅适用于Java的内置类。您还可以java.lang.AutoCloseable在自己的类中实现接口,并将它们与try-with-resources构造一起使用。
十、如果重写Equals()方法,必须也重写HashCode()方法
Equal objects必须有相同的哈希码。
十一、不要使用原生态类型
原生态类型的泛型:不带任何实际参数的泛型名称,例如List<E>的原生态类型就是List
可以使用通配符?或者写成List<>
十二、消除unchecked警告
- 添加本地变量,以减少@SuppressWarnings范围
*每次使用@SuppressWarnings("unchecked")都应注解为什么
十三、使用列表(List<T>)而不使用数组(T[])
- 对于类型问题,列表在编译期间报错,数组在运行期间报错
- 集合功能多,数组效率高
十四、可变参数和泛型结合的注意点
详细内容:https://minei.me/archives/340.html
static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError(); // Can't get here
}
这个调用看起来也是没什么毛病,但是执行 toArray方法之后就会报 ClassCastException,因为 toArray的返回类型是由传入的参数决定的,
编译器是无法精确的预测具体类型,所以在 pickTwo方法中会统一返回 Object[] 数组,但是pickTwo方法传入的是String,所以返回也应该是String数组,
这里隐藏了一个类型转换并且转换失败了。
上面的例子说明了把可变参数传递到另一个方法是不安全的。
十五、存储多种类型数据可以考虑类型安全的异构容器
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
// Achieving runtime type safety with a dynamic cast
//public <T> void putFavorite(Class<T> type, T instance) {
//favorites.put(type, type.cast(instance));
//}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName());
}
十六、用实例域代替序数
//不建议使用
// Abuse of ordinal to derive an associated value - DON'T DO THIS
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET,
SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() {
return ordinal() + 1;
}
}
//建议使用
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) {
this.numberOfMusicians = size;
}
public int numberOfMusicians() {
return numberOfMusicians;
}
}
十七、使用EnumSet而不是位字段
位字段
// Bit field enumeration constants - OBSOLETE!
public class Text {
public static final int STYLE_BOLD
public static final int STYLE_ITALIC
public static final int STYLE_UNDERLINE
public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
// Parameter is bitwise OR of zero or more STYLE_ constants
public void applyStyles(int styles) { ... }
}
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
** EnumSet**
// EnumSet - a modern replacement for bit fields
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
十八、用EnumMap代替序数索引
private final String name;
private final Type type;
Herb(String name, Type type) {
this.name = name;
this.type = type;
}
@Override
public String toString() {
return name;
}
public static void main(String[] args) {
// 将集合放到一个按照类型的序数进行索引的数组中来实现 替换
Herb[] garden = {new Herb("一年生1", Type.ANNUAL), new Herb("一年生2", Type.ANNUAL),
new Herb("两年生1", Type.BIENNIAL), new Herb("两年生2", Type.BIENNIAL),
new Herb("多年生1", Type.PERENNTAL), new Herb("多年生2", Type.PERENNTAL)};
Set<Herb>[] herbsByType = (Set<Herb>[])new Set[Herb.Type.values().length];
for(int i = 0;i<herbsByType.length;i++){
herbsByType[i] = new HashSet<Herb>();
}
for(Herb h:garden){
herbsByType[h.type.ordinal()].add(h);
}
for (int i = 0; i < herbsByType.length; i++) {
System.out.printf("%s: %s%n", Herb.Type.values(), herbsByType[i]);
}
}
这种方法的确可行,但是隐藏着许多问题。因为数组不能与泛型兼容。程序需要进行未受检的转换,并且不能正确无误地进行编译。因为数组不知道它的索引代表着什么,你必须手工标注这些索引的输出。但是这种方法最严重的问题在于,当你访问一个按照枚举的序数进行索引的数组时,使用正确的int值就是你的职责了;int不能提供枚举的类型安全。你如果使用了错误的值,程序就会悄然地完成错误的工作,或者幸运的话就会抛出ArrayIndexOutOfBoundException异常。
Herb[] garden = {new Herb("一年生1", Type.ANNUAL), new Herb("一年生2", Type.ANNUAL),
new Herb("两年生1", Type.BIENNIAL), new Herb("两年生2", Type.BIENNIAL),
new Herb("多年生1", Type.PERENNTAL), new Herb("多年生2", Type.PERENNTAL)};
Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type,Set<Herb>>(Herb.Type.class);
for(Herb.Type t:Herb.Type.values()){
herbsByType.put(t, new HashSet<Herb>());
}
for(Herb h:garden){
herbsByType.get(h.type).add(h);
}
System.out.println(herbsByType);
}```
结果:{ANNUAL=[一年生1, 一年生2], PERENNTAL=[多年生1, 多年生2], BIENNIAL=[两年生1, 两年生2]}
这段程序更简短,更清楚,也更安全,运行速度方面可以与使用序数的程序相媲美。它没有不安全的转换;不必手工标注出这些索引的输出,因为映射键知道如何将自身翻译成可打印的字符串的枚举;计算数组索引时也不可能出错。EnumMap在运行速度方面之所以能与通过序数索引的数组相媲美,是因为EnumMap在内部使用了这种数组。但是它对程序员隐藏了这种思想细节,集Map的丰富功能和类型安全与数组的快速于一身。注意EnumMap构造器采用键类型的Class对象:这是一个有限制的类型令牌(bounded type token),它提供了运行时的泛型信息。
十九、使用接口模拟扩展枚举
public enum Operation {
PLUS("+") {
@Override
double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
@Override
double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
double apply(double x, double y) {
return x / y;
}
};
private String symbol;
public static final Map<String, Operation> OPERS_MAP = Maps.newHashMap();
static {
for (Operation op : Operation.values()) {
OPERS_MAP.put(op.toString(), op);
}
}
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
abstract double apply(double x, double y);
}
由于enum不支持继承,又由于Operation有可能你并不能修改代码,而是第三方提供的类库。该如何实现?此时,我们可以将enum实例域的方法提取成为接口,而enum是可以实现接口的,用户自定义的操作方法也只需实现该接口,只是确保在调用计算器方法的参数时使用的是接口类型即可。如:
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
@Override
double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
@Override
double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
double apply(double x, double y) {
return x / y;
}
};
private String symbol;
public static final Map<String, Operation> OPERS_MAP = Maps.newHashMap();
static {
for (Operation op : BasicOperation.values()) {
OPERS_MAP.put(op.toString(), op);
}
}
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
//客户端自定义操作
public enum ExtendOperation implements Operation {
EXP("^") {
@Override
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
@Override
public double apply(double x, double y) {
return x % y;
}
};
private String symbol;
public static final Map<String, Operation> OPERS_MAP = Maps.newHashMap();
static {
for (Operation op : ExtendOperation.values()) {
OPERS_MAP.put(op.toString(), op);
}
}
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}