Effective Java

目录


文章目录

  • Effective Java
  • 目录
  • 1、考虑使用静态方法代替工厂方法
  • 使用优点
  • 使用缺点
  • 2、参数过的多时使用建造者模式
  • 使用优点:
  • 缺点
  • 总结
  • 3、私有构造方法或枚举实现 Singleton 属性
  • 4、使用私有构造方法来定义纯静态的类
  • 使用优点:
  • 缺点:
  • 5、使用依赖注入来取代硬链接资源
  • 使用优点
  • 总结
  • 6、避免创建不必要的对象
  • 使用优点
  • 场景描述
  • 总结
  • 7、消除过期对象的引用
  • 好处


1、考虑使用静态方法代替工厂方法

使用优点

  • 静态方法相对于构造方法存在名字
  • 不需要每次调用的时候创建一个对象
  • 返回的对象可以是其的子类
  • 返回的对象可以是一个接口的子类 我们可以在方法定义的时候定义好其返回的子类随后使用接口对该子类进行调用
  • 好处:在我们以后扩展的时候只需要对该接口修改实现类即可。
  • 即只需要编写一个该接口的实现类就可以在不修改代码的情况下对业务进行修改。
  • 可以屏蔽底层子类的实现,降低切换和学习成本
  • 返回的类可以根据入参来进行区分1
  • 在编写该方法时,返回的类可以不存在
  • 因为子类的第一行代码会默认调用父类的构造参数 如果我们返回的接口/类 的构造函数不是 public 或者 protected 的话就会导致子类无法实例化,这也变相要求我们使用组合/复合2,减少使用继承的方式来进行解耦

使用缺点

  • 没有公共或者受保护的构造方法的类不能被实例化
  • 个人理解是:很难不通过接口直接访问对应的子类
  • 屏蔽了实现 程序员很难知道自己获取到的具体是哪一个类

2、参数过的多时使用建造者模式

使用优点:

  • 可以避免类类的构造方法参数过多导致程序员不易阅读
// Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;
        // Optional parameters - initialized to default values
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}


        /*
                NutritionFacts 类是不可变的,所有的参数默认值都在一个地方。builder 的 setter 方法返回 builder 本身,
                这样调用就可以被链接起来,从而生成一个流畅的 API。下面是客户端代码的示例:
                NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
                
                这个客户端代码很容易编写,更重要的是易于阅读。 Builder 模式模拟 Python 和 Scala 中的命名可选参数。
                为了简洁起见,省略了有效性检查。 要尽快检测无效参数,检查 builder 的构造方法和方法中的参数有效性。 在
                build 方法调用的构造方法中检查包含多个参数的不变性。为了确保这些不变性不受攻击,在从 builder 复制参数
                后对对象属性进行检查(条目 50)。 如果检查失败,则抛出 IllegalArgumentException 异常(条目 72),其
                详细消息指示哪些参数无效(条目 75)。
         */
  • 可以对build方法使用泛型进行抽象,子类继承抽象类以后实现build方法返回对应的子类,这样我们在使用该抽象的时候就不需要做强制类型转换了。

缺点

  • 在我们创建一个类的时候需要先创建他的 build 类 / 损耗性能。

总结

  • 当一个类的构造参数多个(4个或以上)的时候才 考虑将其转换成 build 建造者模式 可以使代码更容易读写

3、私有构造方法或枚举实现 Singleton 属性

单例对象可以使用反射 调用其私有化的构造函数创建出来

  • 使用公共方法 getInstance()来替代直接 Class.INSTANCE
  • 可以在方法内对实例就行修改3
  • 在类中定义readResolve()来强化单例 (防止在序列化的时候重新创建实例对象)
  • 因为在序列化的时候 若目标类存在readResolve()方法 就会通过反射调用这个方法返回一个实例,没有就通过反射调用构造方法(这里私有也能调用)赋值返回。
// readResolve method to preserve singleton property
    private Object readResolve() {
    // Return the one true Elvis and let the garbage collector
    // take care of the Elvis impersonator.
        return INSTANCE;
    }

4、使用私有构造方法来定义纯静态的类

纯静态的类:方法和属性都是静态的

使用优点:

  • 可以保证类不会被实例化
// Noninstantiable utility class
    public class UtilityClass {
        // Suppress default constructor for noninstantiability
        private UtilityClass() {
            // 保证类在任何时候都不会被实例化
            throw new AssertionError();
        }
    // Remainder omitted
    }

缺点:

  • 由于构造函数私有化 导致该类不能被其他类继承4

5、使用依赖注入来取代硬链接资源

使用依赖注入来对我们的工具类/实例 在创建的时候才指定需要依赖的资源/其他类

使用优点

  • 提高扩展性,提高底层资源的重复利用率

总结

  • 可以使用Supplier<? extends Class> 来限定传入的类的范围,大大提高可用性
// Dependency injection provides flexibility and testability
    public class SpellChecker {
        // 这个是我们依赖的类
        private final Lexicon dictionary;
	   // 这里使用泛型提高扩展能力
        public SpellChecker(Supplier<? extends  Lexicon> supplier) {
            this.dictionary = Objects.requireNonNull(supplier.get());
        }

        public boolean isValid(String word) { ...}

        public List<String> suggestions(String typo) { ...}
    }

6、避免创建不必要的对象

可以重用的对象重复进行创建

使用优点

  • 性能的飞快提高

场景描述

"".matches("表达式") // 这个方法会在调用的时候创建一个Pattern对象 多次重复调用的时候性能会很低。
   
    public static void main(String[] args) {
        System.out.println(new Date());
        // 这里使用了包装类型 在我们进行相加的时候会进行拆箱装箱操作,导致性能很低
    	// long sum = 0L; 改为基础类型之后可以大幅度提高性能
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++)
            sum += i;
        System.out.println(new Date());
        System.out.println(sum);
    }

总结

  • 局部变量尽量使用基本类型(属性变量和返回值除外)
  • 要考虑到维护单例对象的成本和性能 (避免单例对象性能过大导)

7、消除过期对象的引用

过期引用会导致无法垃圾回收导致内存泄漏 使用数组之后没有将数组置为null 导致数组维护者里面对象的过期引用(该数组存在引用)5

将错误引用置为null

好处

  • 下次我们在使用过期引用的对想的时候会抛出 NullPointerException 尽快发现程序的错误

  1. 返回的接口的实现类 可以根据我们传入参数的不通而进行动态的区分 ↩︎
  2. 接口 ↩︎
  3. 比如,返回他的每一个线程都是单独实例 ↩︎
  4. 子类会默认调用父类的构造参数 私有化构造函数会导致子类无法调用父类的构造参数 ↩︎
  5. 比如线程池的核心是一个数组,该数组保持着对所有线程的引用,当线程使用结束了以后没有将线程引用置为 null 的话就会导致过期引用问题,其他的池类设计都是这样子的。 ↩︎