Java泛型入门:类型安全与上界约束

本文简介:从零理解 Java 泛型的核心概念,掌握泛型类的定义、使用与类型上界约束,写出类型安全、通用优雅的 Java 代码。

目录


一、什么是泛型

泛型的本质就是「参数化类型」 。

简单来说:把数据类型当作参数传递,让类、接口、方法可以通用处理多种数据类型,同时保留类型安全------ 编译期就检查类型,避免运行时才出类型转换错误。

通俗理解

打个比方:

  • 普通代码像专用杯子------ 只能装水,装咖啡会串味
  • 泛型像通用杯子------ 你指定装咖啡、茶还是可乐,它就只装对应东西,绝不会装错

一句话总结:泛型就是适用于许多许多类型的代码。从代码上讲,就是对类型实现了参数化。


二、为什么需要泛型(引出泛型)

传统做法的问题

我们来实现一个简单的 "数组容器" 类:内部维护一个数组,可以存放任意类型的数据,也可以根据下标取出元素。

复制代码
class MyArray {
    private Object[] array;
    public MyArray(int capacity) {
        array = new Object[capacity];
    }
    public void set(int index, Object value) {
        array[index] = value;
    }
    public Object get(int index) {
        return array[index];
    }
}

这样写的问题

用 Object 数组确实可以存任何类型,但带来了两个麻烦:

  1. 类型不安全:什么都能存,编译器不检查,一不小心就存错类型

  2. 使用麻烦 :取出来是 Object 类型,每次都要强制类型转换

    MyArray list = new MyArray(10);
    list.set(0, "hello");
    list.set(1, 123); // 居然也能存进去?
    String s = (String) list.get(0); // 要强转
    Integer i = (Integer) list.get(1); // 也要强转

泛型的解决方案

泛型的核心目的就是:指定当前容器要持有什么类型的对象,让编译器去做类型检查。

把类型作为参数传递,需要什么类型,就传入什么类型。这样既通用,又安全。


三、泛型类的语法格式

基本语法

复制代码
// 形式一:简单泛型类
class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}
// 示例
class ClassName<T1, T2, ..., Tn> {
    // ...
}

// 形式二:带继承关系的泛型类
class 泛型类名称<类型形参列表> extends 继承类 {
    // 这里可以使用类型参数
}
// 示例:父类本身也是泛型类
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
    // 可以只使用部分类型参数
}

用泛型改写上面的容器类

复制代码
class MyArray<E> {
    private Object[] array;
    public MyArray(int capacity) {
        array = new Object[capacity];
    }
    public void set(int index, E value) {
        array[index] = value;
    }
    public E get(int index) {
        return (E) array[index];
    }
}

📝 代码解释:

  • 类名后面的 <T>(或 <E>)是类型占位符,表示当前类是一个泛型类
  • E 不是具体类型,而是一个 "占位"------ 用的时候才指定是什么类型

📌 常见的类型参数命名约定

表格

符号 含义 适用场景
E Element 集合中的元素
K Key 键值对中的键
V Value 键值对中的值
N Number 数值类型
T Type 泛型通用类型
S, U, V... 第二、三、四个类型 多个类型参数时依次使用

四、泛型类的使用

1. 基本语法

复制代码
// 第一步:定义一个泛型类的引用
泛型类<类型实参> 变量名;

// 第二步:实例化一个泛型类对象
new 泛型类<类型实参>(构造方法实参);

两个关键概念:

  • 泛型类 :你自己定义的带类型参数的类(比如 MyArray<T>)
  • 类型实参 :创建对象时给类型参数指定的具体类型(比如 Integer、String)

两步可以合起来写:

复制代码
泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造参数);

2. 代码示例

复制代码
public class Main {
    public static void main(String[] args) {
        // 创建一个只能存 Integer 的 MyArray
        MyArray<Integer> list = new MyArray<Integer>(10);
        
        list.set(0, 100);
        list.set(1, 200);
        
        // 取出来直接就是 Integer 类型,不用强转
        Integer num = list.get(0);
        System.out.println(num);
        
        // list.set(2, "hello");  // 编译报错!不能存 String
    }
}

⚠️ 重点提醒:

泛型里不能直接写基本数据类型 (比如 int、double),必须用它们对应的包装类。

表格

基本类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

3. 类型推导(菱形操作符)

Java 7 以后,编译器可以根据左边的声明推导出右边的类型,右边的 <> 里可以省略不写:

复制代码
MyArray<Integer> list = new MyArray<>();  // 右边 <> 里不用再写 Integer

右边的 <> 被称为 "菱形操作符" ,简化了代码,效果和写全 new MyArray<Integer>() 完全一样。

💡 为什么要这样设计?

左边已经写了 <Integer>,编译器当然能推导出右边 new MyArray 也应该是 Integer 类型 ------ 何必重复写两遍呢?


五、泛型类的上界

为什么需要类型上界?

泛型默认是 "无界" 的,也就是 E 可以是任何类型。

但有时候我们希望泛型只能接受特定类型或它的子类,比如:

  • 只允许数值类型 (Number 及其子类:Integer、Double 等)
  • 只允许实现了某个接口 的类型(比如 Comparable,支持比较)

这时就需要用 extends 关键字来设置类型上界,给类型变量加上约束。

1. 语法规则

复制代码
class 泛型类名称<类型形参 extends 类型边界> {
    // ...
}

规则说明:

  • extends 类型边界:表示这个类型形参只能接收 类型边界本身 ,或它的子类 / 实现类
  • 如果类型边界是类:只能用它的子类
  • 如果类型边界是接口:只能用实现了它的类

📝 小补充 :如果不写 extends,默认相当于 extends Object------ 也就是所有类都可以。

2. 代码示例

复制代码
// 要求 E 必须是 Number 或它的子类
public class MyArray<E extends Number> {
    private E[] array;
    public MyArray(int capacity) {
        array = (E[]) new Object[capacity];
    }
    public void set(int index, E value) {
        array[index] = value;
    }
    public E get(int index) {
        return array[index];
    }
}

使用时的类型检查:

复制代码
MyArray<Integer> l1;    // ✅ 正常:Integer 是 Number 的子类
MyArray<Double> l2;    // ✅ 正常:Double 是 Number 的子类
MyArray<String> l3;     // ❌ 编译错误:String 不是 Number 的子类

Java 中 Number 是所有数值包装类的父类,它的子类包括:

Integer、Long、Double、Float、Byte、Short

⚠️ 编译器报错意味着什么?

编译器会直接报错,提示你 String 不在 E extends Number 的类型边界内。这就是泛型类型上界的价值 ------在编译期就拦住类型不匹配的问题,而不是等到运行时才炸。


六、总结

表格

知识点 核心要点
泛型是什么 参数化类型,把类型当参数传递,实现通用且类型安全的代码
为什么用泛型 避免强制类型转换,编译期类型检查,减少运行时错误
泛型类定义 class 类名<T> { ... },T 是类型占位符
泛型类使用 类名<具体类型> 对象 = new 类名<>(构造参数);
注意事项 不能用基本类型,必须用包装类
类型推导 菱形操作符 <>,右边类型可省略
类型上界 <T extends 边界>,约束 T 必须是边界类型或其子类

🎓 写在最后 :泛型是 Java 集合框架的基石,也是学习后续 ArrayList、HashMap、泛型方法、通配符 ? 的前提。把这篇文章的概念吃透,后面的路会好走很多

相关推荐
Jsundoku1 小时前
第七章 深度学习 -- Transformer 架构
人工智能·深度学习·transformer·1024程序员节
凉茶钱1 小时前
stack、queue、priority_queue、容器适配器完整梳理
开发语言·c++·queue·1024程序员节·stack
编程的一拳超人1 小时前
【元宝搜索开发者征文大赛】四语实战:Java/Python/Go/Node.js 构建 AI 搜索应用全攻略
java·python·golang·node.js·agent
FriendshipT1 小时前
Vim 完整指南:从入门到高效编辑
linux·人工智能·编辑器·vim·1024程序员节
小淮AI1 小时前
AI 能生成网页吗?从三类工具看当前的实际能力
1024程序员节
思麟呀1 小时前
嵌入式入门(四)USART 串口深度解析:单片机第一次与电脑对话
c语言·单片机·嵌入式硬件·1024程序员节
猛码象限1 小时前
【模型观察】K3.1 Pro 后训练加码,自评完成度 65.7%
人工智能·业界资讯·1024程序员节
天蒙蒙亮1 小时前
React Native 打包流程(Android 为主)
1024程序员节
小大宇1 小时前
Vue知识点-setup语法糖
1024程序员节