Effective Java

2018-11-02  本文已影响8人  liboxiang
一、 静态工厂方法
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警告
十三、使用列表(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;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读