《Effective Java》解读第29条:优先考虑泛型

第29条:优先考虑泛型

Java泛型是增强代码类型安全性和可读性的核心工具。不使用泛型的代码会产生大量运行时强制转换,既不安全也容易出错。

一般来说,为集合声明参数化、jdk使用泛型方法都不太困难,很少会自己编写一个泛型,但这仍然值得我们花时间去学习。

优点

  • 类型安全:在编译期捕获类型错误,避免ClassCastException。
java 复制代码
// 非泛型 - 编译时无法发现类型错误
List list = new ArrayList();
list.add("string");
Integer i = (Integer) list.get(0); // 运行时ClassCastException

// 泛型 - 编译时发现错误
List<String> list = new ArrayList<>();
list.add("string");
Integer i = list.get(0); // 编译错误
  • 表达力强:代码清楚地声明了它所操作的数据类型。

  • 消除强制转换:使代码更简洁,减少"代码杂音"。

java 复制代码
// 使用泛型前
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制转换

// 使用泛型后
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动转换
  • 促进重用:泛型算法和数据结构可以安全地用于多种类型。

实践

例如编写一个泛型栈,我们一步步来实现。

实现一个简单的栈

java 复制代码
// 优先设计为泛型类
public class Stack<E> {
    private E[] elements;
    private int size = 0;

    public Stack(int initialCapacity) {
        elements = new E[initialCapacity];
    }

    public void push(E e) {
        elements[size++] = e;
    }

    public E pop() {
        E result = elements[--size];
        elements[size] = null; // 消除过时引用
        return result;
    }
}

因为泛型数组是不可具体化的。

两种解决方式:

  1. 方法1:创建Object数组强转为泛型数组
    不过,你要确保非受检的转换是安全的。
  2. 方法2:将elements改为Object数组
    同时在pop时强转为E。

    当然这也是一个非受检的警告。
    都可以通过@SuppressWarnings("unchecked")来消除。

    根据第27条的建议,我们只要在包含未受检转换的任务上禁止警告,而不是在整个pop方法上禁止就可以。

两种解决方式的区别:

  1. 方法1只会转换一次,方法2在每次pop时都会进行转换。
  2. 方法1的可读性会更强,但会造成堆污染,虽然在此情况下并无危害。

堆污染其实就是因为泛型擦除,泛型变量指向错误类型的对象。

有限制类型参数

java 复制代码
// 使用有界类型参数
public static <T extends Comparable<T>> T max(Collection<T> coll) {
    T candidate = null;
    for (T element : coll) {
        if (candidate == null || element.compareTo(candidate) > 0) {
            candidate = element;
        }
    }
    return candidate;
}

可以限制泛型类型,例如只能是Comparable的子类型。

在泛型参数声明中,extends用于同时表示"继承自类"和"实现接口"。

注意点

看过第28条建议的会疑惑这不是相矛盾了,第28条鼓励优先使用列表而非数组。实际上不可能总是或者总想在泛型中使用列表。Java并不是生来就支持列表,因此有些泛型如 ArrayList,必须在数组上实现。为了提升性能,其他泛型如HashMap也在数组上实现。

总结

总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更加容易。在设计新类型的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成是泛型的。只要时间允许,就把现有的类型都泛型化。这对于这些类型的新用户来说会变得更加轻松,又不会破坏现有的客户端。

相关推荐
qq_57039857几秒前
Three.js基础使用-案例
开发语言·javascript·ecmascript
Suxing96 分钟前
C语言基础分享:C语言文件操作:从“写日记”到“拍电影”的存盘指南
c语言·开发语言
右耳朵猫AI9 分钟前
Node.js周刊2026W36 | NestJS 12发布、Remix 3 RC、pnpm 12 Rust重写、Node.js 26.8.0
开发语言·rust·node.js
豆角焖肉20 分钟前
Spring Boot入门实战:从零搭建第一个应用
java·spring boot·后端·自动配置
统计学小王子27 分钟前
数学建模国赛倒计时 2 天 ——《异常值的处理(R语言)》
开发语言·数学建模·r语言
starzy19903 小时前
Flink基础之Per-Job-Cluster原理详解:被 Application 取代的模式
java·大数据·flink
z落落3 小时前
C# WinForm Socket 转串口设备服务器+Modbus CRC16校验+Socket 客户端
服务器·开发语言·c#
小灰灰搞电子9 小时前
Rust+Slint 实现动态消息提示框源码分享
开发语言·后端·rust
魏 无羡9 小时前
webclient
java·webclient
神仙别闹10 小时前
基于 C++ 实现两个有序链表序列的交集
java·c++·链表