Effective Java 第二版 第5章 泛型、第6章 枚举和注解、第7章 方法

文章目录

  • 5 泛型
    • 第23条: 请不要在新代码中使用原生态类型
    • 第24条: 消除非受检告警
    • 第25条: 列表优先于数组
    • 第26条: 优先考虑泛型
    • 第27条: 优先考虑泛型方法
    • 第28条:利用有限制通配符来提升API的灵活性
    • 第29条:优先考虑类型安全的异构容器
  • 6 枚举和注解
    • 第30条: 用enum代替int常量
    • 第31条: 用实例域代替序数
    • 第32条: 用EnumSet代替位域
    • 第33条: 用EnumMap代替序数索引
    • 第34条: 用接口模拟可伸缩的枚举
    • 第35条: 注解优先于命名模式
    • 第36条: 坚持使用Override注解
    • 第37条: 用标记接口定义类型
  • 7 方法
    • 第38条: 检查参数的有效性
    • 第39条: 必要时进行保护性拷贝
    • 第40条: 谨慎地设计方法签名
    • 第41条:慎用重载
    • 第42条:慎用可变参数
    • 第43条: 返回长度为0的数组或者集合,而不是null
    • 第44条: 为所有导出的API元素编写文档注释

5 泛型

第23条: 请不要在新代码中使用原生态类型

声明中具有一个或者多个类型参数(type parameter)的类或者接口,就是泛型(generic)类或者接口。

每个泛型都对应一个原生态类型(raw type),即不带任何实际类型参数的泛型名称。例如,List 对应的原生态类型是List。

如果使用List这种原生态类型,就会失掉类型安全性,但是如果使用像 List 这样的参数化类型,则不会。

本条规则的两个例外:
(1)在类文字(class literal)中必须使用原生态类型。
List.class、String[].class等都是合法的。但是List.classList.class不合法
(2)由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof是非法的。
利用泛型来使用instanceof的首选方法:

if (o instanceof Set) {
	Set m = Set o;
}

第24条: 消除非受检告警

如果无法消除告警,同时又可以证明引起告警的代码是类型安全的,只有在这种情况下才可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告。

应该在尽可能小的范围中使用@SuppressWarnings注解。这个注解应该用在变量声明、或者是非常简短的方法或者构造器上面。永远不要在整个类上使用@SuppressWarnings注解,这么做可能会掩盖了重要的警告。

使用@SuppressWarnings注解的时候,都要注释一下为什么这里的代码是类型安全的。

第25条: 列表优先于数组

数组是协变的(covariant):如果Sub是Super的子类型,那么Sub[]就是Super[]的子类型。
泛型是不可变的(invariant):对于任意两个不同的类型Type1和Type2,List既不是List的子类型,也不是List的超类型。

数组是具体化的(reified):数组是在运行时才知道并检查元素的类型。
泛型是通过擦除(erasure)来实现的:泛型只是在编译时强化类型信息,并在运行时丢弃类型信息。擦除使得使用泛型的代码可以合没有使用泛型的代码随意进行互用。

创建泛型数组是非法的:因为它不是类型安全的。如果合法,编译器在其他正确的程序中发生的转换就会在运行时失败,抛出ClassCastException。

不可具体化的(non-reifiable)类型是指其运行时表示法包含的信息比它编译时表示法包含的信息更少的类型。唯一可具体化的参数化类型是无限制的通配符类型,如ListMap

第26条: 优先考虑泛型

第27条: 优先考虑泛型方法

静态工具方法尤其适合泛型化。Collections中的所有“算法方法(如binarySearch和Sort)”都泛型化了。

第28条:利用有限制通配符来提升API的灵活性

第29条:优先考虑类型安全的异构容器

当一个类的字面文字(class)被用在方法中,来传达编译时和运行时的类型信息,就被称作type token。

public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        if (type == null) {
            throw new NullPointerException("Type is null");
        }
        favorites.put(type, instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

6 枚举和注解

第30条: 用enum代替int常量

java枚举可以将常量和一些数据关联起来。

特定于常量的方法实现(constant-specific method implementation):

public enum Operation {
    PLUS {double apply(double x, double y) {return x + y;}},
    MINUS {double apply(double x, double y) {return x - y;}},
    TIMES {double apply(double x, double y) {return x * y;}},
    DIVIDE {double apply(double x, double y) {return x / y;}};

    abstract double apply(double x, double y);
}

特定于常量的方法实现通常可以合特定于常量的数据结合起来使用:

public enum Operation {
    PLUS("+") {double apply(double x, double y) {return x + y;}},
    MINUS("-") {double apply(double x, double y) {return x - y;}},
    TIMES("*") {double apply(double x, double y) {return x * y;}},
    DIVIDE("/") {double apply(double x, double y) {return x / y;}};

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    abstract double apply(double x, double y);

    @Override
    public String toString() {
        return symbol;
    }

    public static void main(String[] args) {
        double x = 1;
        double y = 2;

        for (Operation op : Operation.values()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}


output:
1.000000 + 2.000000 = 3.000000
1.000000 - 2.000000 = -1.000000
1.000000 * 2.000000 = 2.000000
1.000000 / 2.000000 = 0.500000

策略枚举的使用:

/**
 * 每当添加一种枚举常量时,就会强制选择一种加班报酬策略。
 * 如果多个常量枚举同时共享相同的行为,则考虑策略枚举。
 *
 * @since 2022-12-13
 */
public enum PayrollDay {
    MONDAY(PayType.WEEKDAY),
    TUESDAY(PayType.WEEKDAY),
    WEDNESDAY(PayType.WEEKDAY),
    THURSDAY(PayType.WEEKDAY),
    FRIADAY(PayType.WEEKDAY),
    SATURDAY(PayType.WEEKEND),
    SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayrollDay(PayType payType) {
        this.payType = payType;
    }

    double pay(double hoursWorked, double payRate) {
        return payType.pay(hoursWorked, payRate);
    }

    private enum PayType() {
        WEEKDAY {
            @Override
            double overTimePay(double hrs, double payRate) {
                return hrs <= HOURS_PER_SHIFT ? 0 : (hrs - HOURS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            @Override
            double overTimePay(double hrs, double payRate) {
                return hrs * payRate / 2;
            }
        };

        private static final int HOURS_PER_SHIFT = 8;

        abstract double overTimePay(double hrs, double payRate);

        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overTimePay(hoursWorked, payRate);
        }
    }
}

第31条: 用实例域代替序数

永远不要根据枚举的序数导出与它关联的值,而是要将其保存在一个实例域中。

第32条: 用EnumSet代替位域

public class Text {
    public enum Style {BOLD, ITALIC, UNDERLINE, STRIKENTHROUGH};

    public void applyStyles(Set