pursue wind pursue wind
首页
Java
Python
数据库
框架
Linux
中间件
前端
计算机基础
DevOps
项目
面试
书
关于
归档
MacOS🤣 (opens new window)
GitHub (opens new window)
首页
Java
Python
数据库
框架
Linux
中间件
前端
计算机基础
DevOps
项目
面试
书
关于
归档
MacOS🤣 (opens new window)
GitHub (opens new window)
  • EffectiveJava

    • 考虑使用静态工厂方法替代构造方法
    • 当构造方法参数过多时使用builder模式
    • 使用私有构造方法或枚类实现Singleton属性
    • 使用私有构造方法执行非实例化
    • 依赖注入优于硬连接资源(hardwiring resources)
    • 避免创建不必要的对象
    • 消除过期的对象引用
    • 避免使用Finalizer和Cleaner机制
    • 使用try-with-resources语句替代try-finally语句
    • 重写equals方法时遵守通用约定
    • 重写equals方法时同时也要重写hashcode方法
    • 始终重写 toString 方法
    • 谨慎地重写 clone 方法
    • 考虑实现Comparable接口
    • 使类和成员的可访问性最小化
    • 在公共类中使用访问方法而不是公共属性
    • 最小化可变性
    • 组合优于继承
    • 要么设计继承并提供文档说明,要么禁用继承
    • 接口优于抽象类
    • 为后代设计接口
    • 接口仅用来定义类型
    • 类层次结构优于标签类
    • 支持使用静态成员类而不是非静态类
    • 将源文件限制为单个顶级类
    • 不要使用原始类型
    • 消除非检查警告
    • 列表优于数组
    • 优先考虑泛型
    • 优先使用泛型方法
    • 使用限定通配符来增加API的灵活性
    • 合理地结合泛型和可变参数
    • 优先考虑类型安全的异构容器
    • 使用枚举类型替代整型常量
    • 使用实例属性替代序数
    • 使用EnumSet替代位属性
    • 使用EnumMap替代序数索引
    • 使用接口模拟可扩展的枚举
    • 注解优于命名模式
    • 始终使用Override注解
    • 使用标记接口定义类型
    • lambda表达式优于匿名类
    • 方法引用优于lambda表达式
    • 优先使用标准的函数式接口
    • 明智审慎地使用Stream
    • 优先考虑流中无副作用的函数
    • 优先使用Collection而不是Stream来作为方法的返回类型
    • 谨慎使用流并行
    • 检查参数有效性
    • 必要时进行防御性拷贝
    • 仔细设计方法签名
    • 明智审慎地使用重载
    • 明智审慎地使用可变参数
    • 返回空的数组或集合,不要返回 null
    • 明智审慎地返回 Optional
    • 为所有已公开的 API 元素编写文档注释
    • 最小化局部变量的作用域
    • for-each 循环优于传统 for 循环
    • 了解并使用库
    • 若需要精确答案就应避免使用 float 和 double 类型
    • 基本数据类型优于包装类
    • 当使用其他类型更合适时应避免使用字符串
    • 当心字符串连接引起的性能问题
    • 通过接口引用对象
    • 接口优于反射
    • 明智审慎地本地方法
    • 明智审慎地进行优化
    • 遵守被广泛认可的命名约定
    • 只针对异常的情况下才使用异常
    • 对可恢复的情况使用受检异常,对编程错误使用运行时异常
    • 避免不必要的使用受检异常
    • 优先使用标准的异常
    • 抛出与抽象对应的异常
    • 每个方法抛出的异常都需要创建文档
    • 在细节消息中包含失败一捕获信息
    • 保持失败原子性
    • 不要忽略异常
    • 同步访问共享的可变数据
    • 避免过度同步
    • executor 、task 和 stream 优先于线程
    • 并发工具优于 wait 和 notify
    • 文档应包含线程安全属性
    • 明智审慎的使用延迟初始化
    • 不要依赖线程调度器
    • 优先选择 Java 序列化的替代方案
    • 非常谨慎地实现 Serializable
    • 考虑使用自定义的序列化形式
    • 保护性的编写 readObject 方法
    • 对于实例控制,枚举类型优于 readResolve
    • 考虑用序列化代理代替序列化实例
  • On Java 8

  • 书
  • EffectiveJava
pursuewind
2020-11-22

优先使用泛型方法

# 30. 优先使用泛型方法

正如类可以是泛型的,方法也可以是泛型的。 对参数化类型进行操作的静态工具方法通常都是泛型的。 集合中的所有“算法”方法(如 binarySearch 和 sort)都是泛型的。

编写泛型方法类似于编写泛型类型。 考虑这个方法,它返回两个集合的并集:

// Uses raw types - unacceptable! [Item 26]
public static Set union(Set s1, Set s2) {
    Set result = new HashSet(s1);
    result.addAll(s2);
    return result;
}
1
2
3
4
5
6

此方法可以编译但有两个警告:

Union.java:5: warning: [unchecked] unchecked call to
HashSet(Collection<? extends E>) as a member of raw type HashSet
        Set result = new HashSet(s1);
                     ^
Union.java:6: warning: [unchecked] unchecked call to
addAll(Collection<? extends E>) as a member of raw type Set
        result.addAll(s2);
                     ^
1
2
3
4
5
6
7
8

要修复这些警告并使方法类型安全,请修改其声明以声明表示三个集合(两个参数和返回值)的元素类型的类型参数,并在整个方法中使用此类型参数。 声明类型参数的类型参数列表位于方法的修饰符和返回类型之间。 在这个例子中,类型参数列表是 <E>,返回类型是 Set<E>。 类型参数的命名约定对于泛型方法和泛型类型是相同的(详见第 29 和 68 条):

// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;

}
1
2
3
4
5
6
7

至少对于简单的泛型方法来说,就是这样。 此方法编译时不会生成任何警告,并提供类型安全性和易用性。 这是一个简单的程序来运行该方法。 这个程序不包含强制转换,并且编译时没有错误或警告:

// Simple program to exercise generic method
public static void main(String[] args) {
    Set<String> guys = Set.of("Tom", "Dick", "Harry");
    Set<String> stooges = Set.of("Larry", "Moe", "Curly");
    Set<String> aflCio = union(guys, stooges);
    System.out.println(aflCio);
}
1
2
3
4
5
6
7

当运行这个程序时,它会打印[Moe, Tom, Harry, Larry, Curly, Dick](输出中元素的顺序依赖于具体实现。)

union 方法的一个限制是所有三个集合(输入参数和返回值)的类型必须完全相同。 通过使用限定通配符类型( bounded wildcard types)(详见第 31 条),可以使该方法更加灵活。

有时,需要创建一个不可改变但适用于许多不同类型的对象。 因为泛型是通过擦除来实现的(详见第 28 条),所以可以使用单个对象进行所有必需的类型参数化,但是需要编写一个静态工厂方法来重复地为每个请求的类型参数化分配对象。 这种被称为泛型单例工厂(generic singleton factory)的模式,用于函数对象(function objects)(详见第 42 条),比如 Collections.reverseOrder 方法,偶尔也用于 Collections.emptySet 之类的集合。

假设你想写一个恒等方法分配器( identity function dispenser)。 类库提供了 Function.identity 方法,所以没有理由编写你自己的实现(详见第 59 条),但它是有启发性的。 如果每次要求的时候都去创建一个新的恒等方法对象是浪费的,因为它是无状态的。 如果 Java 的泛型被具体化,那么每个类型都需要一个恒等方法,但是由于它们被擦除以后,所以泛型的单例就足够了。 以下是它的实例:

// Generic singleton factory pattern
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
    return (UnaryOperator<T>) IDENTITY_FN;
}
1
2
3
4
5
6
7

将 IDENTITY_FN 转换为 (UnaryFunction<T>) 会生成一个未经检查的强制转换警告,因为 UnaryOperator<Object> 对于每个 T 都不是一个 UnaryOperator<T>。但是恒等方法是特殊的:它返回未修改的参数,所以我们知道,使用它作为一个 UnaryFunction<T> 是类型安全的,无论 T 的值是多少。因此,我们可以放心地抑制由这个强制生成的未经检查的强制转换警告。 一旦我们完成了这些,代码编译没有错误或警告。

下面是一个示例程序,它使用我们的泛型单例作为 UnaryOperator<String> 和 UnaryOperator<Number>。 像往常一样,它不包含强制转化,编译时也没有错误和警告:

// Sample program to exercise generic singleton
public static void main(String[] args) {
    String[] strings = { "jute", "hemp", "nylon" };
    UnaryOperator<String> sameString = identityFunction();

    for (String s : strings)
        System.out.println(sameString.apply(s));

    Number[] numbers = { 1, 2.0, 3L };
    
    UnaryOperator<Number> sameNumber = identityFunction();

    for (Number n : numbers)
        System.out.println(sameNumber.apply(n));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

虽然相对较少,类型参数受涉及该类型参数本身的某种表达式限制是允许的。 这就是所谓的递归类型限制(recursive type bound)。 递归类型限制的常见用法与 Comparable 接口有关,它定义了一个类型的自然顺序(详见第 14 条)。 这个接口如下所示:

public interface Comparable<T> {
    int compareTo(T o);
}
1
2
3

类型参数 T 定义了实现 Comparable<T> 的类型的元素可以比较的类型。 在实际中,几乎所有类型都只能与自己类型的元素进行比较。 所以,例如,String 类实现了 Comparable<String>,Integer 类实现了 Comparable<Integer> 等等。

许多方法采用实现 Comparable 的元素的集合来对其进行排序,在其中进行搜索,计算其最小值或最大值等。 要做到这一点,要求集合中的每一个元素都可以与其中的每一个元素相比,换言之,这个元素是可以相互比较的。 以下是如何表达这一约束:

// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);
1
2

限定的类型 <E extends Comparable <E >> 可以理解为「任何可以与自己比较的类型 E」,这或多或少精确地对应于相互可比性的概念。

这里有一个与前面的声明相匹配的方法。它根据其元素的自然顺序来计算集合中的最大值,并编译没有错误或警告:

// Returns max value in a collection - uses recursive type bound
public static <E extends Comparable<E>> E max(Collection<E> c) {
  if (c.isEmpty())
    throw new IllegalArgumentException("Empty collection");
  E result = null;
  for (E e : c)
    if (result == null || e.compareTo(result) > 0)
      result = Objects.requireNonNull(e);
  return result;
}
1
2
3
4
5
6
7
8
9
10

请注意,如果列表为空,则此方法将引发 IllegalArgumentException 异常。 更好的选择是返回一个 Optional<E>(详见第 55 条)。

递归类型限制可能变得复杂得多,但幸运的是他们很少这样做。 如果你理解了这个习惯用法,它的通配符变体(详见第 31 条)和模拟的自我类型用法(详见第 2 条),你将能够处理在实践中遇到的大多数递归类型限制。

总之,像泛型类型一样,泛型方法比需要客户端对输入参数和返回值进行显式强制转换的方法更安全,更易于使用。 像类型一样,你应该确保你的方法可以不用强制转换,这通常意味着它们是泛型的。 应该泛型化现有的方法,其使用需要强制转换。 这使得新用户的使用更容易,而不会破坏现有的客户端(详见第 26 条)。

Last Updated: 2023/01/30, 11:01:00
优先考虑泛型
使用限定通配符来增加API的灵活性

← 优先考虑泛型 使用限定通配符来增加API的灵活性→

Theme by Vdoing | Copyright © 2019-2023 pursue-wind | 粤ICP备2022093130号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
  • 飙升榜
  • 新歌榜
  • 云音乐民谣榜
  • 美国Billboard榜
  • UK排行榜周榜
  • 网络DJ