声明中具有一个或者多个类型参数(type parameter)的类或者接口,就是泛型(generic)类或者接口。
每个泛型都对应一个原生态类型(raw type),即不带任何实际类型参数的泛型名称。例如,List
对应的原生态类型是List。
如果使用List这种原生态类型,就会失掉类型安全性,但是如果使用像 List
这样的参数化类型,则不会。
本条规则的两个例外:
(1)在类文字(class literal)中必须使用原生态类型。
List.class、String[].class等都是合法的。但是List
和 List>.class
不合法
(2)由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof是非法的。
利用泛型来使用instanceof的首选方法:
if (o instanceof Set) {
Set> m = Set> o;
}
如果无法消除告警,同时又可以证明引起告警的代码是类型安全的,只有在这种情况下才可以用一个@SuppressWarnings("unchecked")
注解来禁止这条警告。
应该在尽可能小的范围中使用@SuppressWarnings
注解。这个注解应该用在变量声明、或者是非常简短的方法或者构造器上面。永远不要在整个类上使用@SuppressWarnings
注解,这么做可能会掩盖了重要的警告。
使用@SuppressWarnings
注解的时候,都要注释一下为什么这里的代码是类型安全的。
数组是协变的(covariant):如果Sub是Super的子类型,那么Sub[]就是Super[]的子类型。
泛型是不可变的(invariant):对于任意两个不同的类型Type1和Type2,List
既不是List
的子类型,也不是List
的超类型。
数组是具体化的(reified):数组是在运行时才知道并检查元素的类型。
泛型是通过擦除(erasure)来实现的:泛型只是在编译时强化类型信息,并在运行时丢弃类型信息。擦除使得使用泛型的代码可以合没有使用泛型的代码随意进行互用。
创建泛型数组是非法的:因为它不是类型安全的。如果合法,编译器在其他正确的程序中发生的转换就会在运行时失败,抛出ClassCastException。
不可具体化的(non-reifiable)类型是指其运行时表示法包含的信息比它编译时表示法包含的信息更少的类型。唯一可具体化的参数化类型是无限制的通配符类型,如List>
和Map,?>
静态工具方法尤其适合泛型化。Collections中的所有“算法方法(如binarySearch和Sort)”都泛型化了。
当一个类的字面文字(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));
}
}
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);
}
}
}
永远不要根据枚举的序数导出与它关联的值,而是要将其保存在一个实例域中。
public class Text {
public enum Style {BOLD, ITALIC, UNDERLINE, STRIKENTHROUGH};
public void applyStyles(Set