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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,646评论 6 533
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,595评论 3 418
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,560评论 0 376
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,035评论 1 314
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,814评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,224评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,301评论 3 442
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,444评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,988评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,804评论 3 355
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,998评论 1 370
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,544评论 5 360
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,237评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,665评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,927评论 1 287
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,706评论 3 393
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,993评论 2 374

推荐阅读更多精彩内容

  • 目录 第二章 创建和销毁对象 1 考虑用静态工厂方法替代构造器 对于代码来说, 清晰和简洁是最重要的. 代码应该被...
    高广超阅读 1,460评论 0 12
  • 《Effective Java》读书笔记目录,这篇文章记录了我读这本书的一些想法和验证。 1. 考虑用静态工厂方法...
    noexceptionsir阅读 1,693评论 0 2
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 2,015评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,785评论 18 139
  • 和暖春晖经院角,高枝楚柚似灯笼。 照明夹缝纤疏草,跃立墙头舞嫩风。
    木木无纹阅读 222评论 0 7