乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

泛型基础

"泛型不是语法糖,而是类型安全的基石。编译期的每一个警告,都是运行时潜在的炸弹——忽略它们,总有一天会在生产环境炸给你看。" —— 白歌,飞翔科技架构师


1. 没有泛型的黑暗时代

在JDK 1.4及之前,Java没有泛型。开发者使用Object作为"万能类型"来编写通用容器:

// JDK 1.4 时代的"万能"集合
List students = new ArrayList();
students.add(new Student("小崔", 22));
students.add(new Student("白歌", 28));
students.add("大翔");  // 不小心放入了一个 String!编译器毫无反应

// 遍历时需要强制类型转换
for (int i = 0; i < students.size(); i++) {
    Student s = (Student) students.get(i);  // 运行到第三个元素时崩溃!
    System.out.println(s.getName());
}

运行结果:

小崔
白歌
Exception in thread "main" java.lang.ClassCastException: java.lang.String 
    cannot be cast to com.feixiang.model.Student

核心问题:

  1. 编译器不检查集合中元素的类型,任何对象都能放入
  2. 取出时必须强制类型转换,代码冗长且不安全
  3. 类型错误在运行时才暴露,线上事故风险极高

2. 泛型:编译期类型安全

JDK 5引入泛型后,同样的代码变为:

// JDK 5+ 泛型集合 —— 类型安全
List<Student> students = new ArrayList<>();
students.add(new Student("小崔", 22));
students.add(new Student("白歌", 28));
// students.add("大翔");  // ❌ 编译错误!Type mismatch

// 遍历时无需强制转换
for (Student s : students) {
    System.out.println(s.getName());  // 类型安全,不会ClassCastException
}

泛型的核心价值:将类型检查从运行时提前到编译期,让Bug在编码阶段就被发现。


3. 泛型类

泛型类(Generic Class)是具有一个或多个类型参数的类。

定义和语法

语法元素说明示例
class ClassName<T>声明泛型类,T为类型参数class Box<T>
T field声明泛型字段private T value
T method()返回泛型类型的方法public T get()
void method(T t)接受泛型参数的方法public void set(T t)

完整示例:飞翔科技的数据容器

/**
 * 飞翔科技的通用数据包装器
 * <T> 类型参数:表示该容器可以盛放任意类型的对象
 */
public class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    /**
     * 检查值是否为null
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * 类型安全的操作:泛型类型在方法内部可以正常使用
     */
    public String describe() {
        if (value == null) {
            return "Box[empty]";
        }
        return "Box[" + value.getClass().getSimpleName() + ": " + value + "]";
    }
}
// 测试代码
public class BoxTest {
    public static void main(String[] args) {
        // 放入学生
        Box<Student> studentBox = new Box<>(new Student("小崔", 22));
        System.out.println(studentBox.describe());
        Student s = studentBox.getValue();  // 无需强制转换
        System.out.println("取出学生:" + s.getName());

        // 放入字符串
        Box<String> stringBox = new Box<>("飞翔科技");
        System.out.println(stringBox.describe());
        String name = stringBox.getValue();

        // 放入整数(自动装箱为Integer)
        Box<Integer> intBox = new Box<>(1024);
        System.out.println(intBox.describe());

        // 编译期类型检查 —— 下面这行无法通过编译
        // Student wrong = stringBox.getValue();  // ❌ 编译错误!
    }
}

运行输出:

Box[Student: Student{name='小崔', age=22}]
取出学生:小崔
Box[String: 飞翔科技]
Box[Integer: 1024]

类型参数命名约定

字母含义典型使用场景
EElement集合元素(如 List<E>、Set<E>)
KKey键(如 Map<K,V>)
VValue值(如 Map<K,V>)
TType通用类型(最常用,如 Box<T>、Repository<T>)
S/USecond/Third Type多参数场景的第二、第三个类型
RReturn方法返回类型(如 Function<T,R>)
NNumber数字类型

这些命名是约定俗成的,不是语法强制要求。你可以用任何合法标识符(如Box<A>),但遵循约定能提升代码可读性。


4. 泛型接口

泛型接口广泛应用于集合框架和DAO/Repository设计模式中。

飞翔科技场景:泛型Repository接口

/**
 * 飞翔科技通用数据访问接口
 * <T>  实体类型
 * <ID> 主键类型
 */
public interface Repository<T, ID> {

    T findById(ID id);

    List<T> findAll();

    int save(T entity);

    int update(T entity);

    int deleteById(ID id);
}
/**
 * Student实体的Repository实现
 */
public class StudentRepository implements Repository<Student, Long> {

    // 模拟数据库
    private Map<Long, Student> database = new HashMap<>();

    @Override
    public Student findById(Long id) {
        return database.get(id);
    }

    @Override
    public List<Student> findAll() {
        return new ArrayList<>(database.values());
    }

    @Override
    public int save(Student entity) {
        database.put(entity.getId(), entity);
        return 1;
    }

    @Override
    public int update(Student entity) {
        database.put(entity.getId(), entity);
        return 1;
    }

    @Override
    public int deleteById(Long id) {
        database.remove(id);
        return 1;
    }
}
// 测试代码
public class RepositoryTest {
    public static void main(String[] args) {
        StudentRepository repo = new StudentRepository();

        repo.save(new Student(1001L, "小崔", 22, "计算机科学与技术"));
        repo.save(new Student(1002L, "白歌", 28, "软件工程"));
        repo.save(new Student(1003L, "大翔", 35, "人工智能"));

        System.out.println("=== 所有学生 ===");
        for (Student s : repo.findAll()) {
            System.out.println(s);
        }

        System.out.println("=== 按ID查找 ===");
        Student found = repo.findById(1002L);
        System.out.println("ID=1002 -> " + found.getName());

        System.out.println("=== 删除后 ===");
        repo.deleteById(1003L);
        System.out.println("剩余数量:" + repo.findAll().size());
    }
}

运行输出:

=== 所有学生 ===
Student{id=1001, name='小崔', age=22, major='计算机科学与技术'}
Student{id=1002, name='白歌', age=28, major='软件工程'}
Student{id=1003, name='大翔', age=35, major='人工智能'}
=== 按ID查找 ===
ID=1002 -> 白歌
=== 删除后 ===
剩余数量:2

5. 泛型方法

泛型方法(Generic Method)是在方法声明中引入自己的类型参数,独立于类是否泛型。

定义和语法

语法形式说明
<T> T method(T t)返回值与参数类型相同
<T> void method(T t)无返回值的泛型方法
<T> List<T> method()返回泛型集合
<T extends Number> T method(T t)有界类型参数的泛型方法
static <T> T method(T t)静态泛型方法(不能使用类的类型参数)

完整示例:飞翔科技工具类

/**
 * 飞翔科技通用工具类
 * 泛型方法可以定义在普通类中
 */
public class FeiXiangUtils {

    /**
     * 交换数组中两个位置的元素
     * <T> 在返回值前声明,表示这是一个泛型方法
     */
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    /**
     * 查找元素在数组中的索引(泛型比较)
     */
    public static <T> int indexOf(T[] array, T target) {
        for (int i = 0; i < array.length; i++) {
            if (array[i].equals(target)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 将单个元素包装为List
     * 静态方法可以使用自己的类型参数,不能使用类的类型参数
     */
    public static <T> List<T> singletonList(T item) {
        List<T> list = new ArrayList<>();
        list.add(item);
        return list;
    }

    /**
     * 合并两个List(泛型方法配合通配符)
     */
    public static <T> List<T> merge(List<T> list1, List<T> list2) {
        List<T> result = new ArrayList<>(list1);
        result.addAll(list2);
        return result;
    }
}
// 测试代码
public class UtilsTest {
    public static void main(String[] args) {
        // 测试swap —— 字符串数组
        String[] names = {"大翔", "白歌", "小崔"};
        System.out.println("交换前:" + Arrays.toString(names));
        FeiXiangUtils.swap(names, 0, 2);
        System.out.println("交换后:" + Arrays.toString(names));

        // 测试swap —— 整数数组
        Integer[] nums = {10, 20, 30, 40};
        FeiXiangUtils.swap(nums, 1, 2);
        System.out.println("整数交换:" + Arrays.toString(nums));

        // 测试indexOf
        int idx = FeiXiangUtils.indexOf(names, "白歌");
        System.out.println("白歌在索引:" + idx);

        // 测试singletonList —— 自动推断泛型类型
        List<Student> single = FeiXiangUtils.singletonList(
            new Student(2001L, "测试员", 20, "测试专业")
        );
        System.out.println("单元素列表:" + single);

        // 测试merge
        List<String> list1 = Arrays.asList("A", "B", "C");
        List<String> list2 = Arrays.asList("D", "E");
        List<String> merged = FeiXiangUtils.merge(list1, list2);
        System.out.println("合并结果:" + merged);
    }
}

运行输出:

交换前:[大翔, 白歌, 小崔]
交换后:[小崔, 白歌, 大翔]
整数交换:[10, 30, 20, 40]
白歌在索引:1
单元素列表:[Student{id=2001, name='测试员', age=20, major='测试专业'}]
合并结果:[A, B, C, D, E]

类型推断

在JDK 7及之后,Java编译器可以通过上下文自动推断泛型类型,这称为类型推断(Type Inference):

// JDK 6 及之前:必须显式指定泛型类型
List<String> list6 = new ArrayList<String>();

// JDK 7+:钻石操作符(Diamond Operator)
List<String> list7 = new ArrayList<>();  // 编译器推断出<String>

// 泛型方法的类型推断
List<Student> single = FeiXiangUtils.singletonList(new Student(...));
// 编译器自动推断 <T> = Student,无需写 FeiXiangUtils.<Student>singletonList(...)

6. 原始类型

原始类型(Raw Type) 是泛型类的非泛型版本——即不带任何类型参数的泛型类。

// Box<T> 的原始类型
Box rawBox = new Box("hello");  // 编译警告:unchecked

// 等价于(经过类型擦除后)
Box rawBox = new Box(new Object());  // T 被替换为 Object

为什么存在原始类型?

向后兼容。JDK 5引入了泛型,但JDK 1.4的代码没有泛型也能运行。Java允许泛型代码与非泛型的遗留代码共存:

// 遗留代码(没有泛型)
public class LegacyLibrary {
    public static List getStudents() {
        List list = new ArrayList();
        list.add(new Student("小崔", 22));
        return list;
    }
}

// 新代码(使用泛型)
@SuppressWarnings("unchecked")
public class NewCode {
    public static void main(String[] args) {
        List<Student> students = LegacyLibrary.getStudents();  // 警告:unchecked conversion
        for (Student s : students) {
            System.out.println(s.getName());
        }
    }
}

原始类型的危害

// ⚠️ 原始类型绕过编译检查 —— 运行时灾难!
Box<String> stringBox = new Box<>("安全数据");
Box rawBox = stringBox;          // 赋值给原始类型,编译通过
rawBox.setValue(12345);          // 放入 Integer!编译通过,仅警告

// 此时 stringBox 内部已经是 Integer 了
String s = stringBox.getValue(); // ❌ ClassCastException: Integer cannot be cast to String

运行时输出:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer 
    cannot be cast to java.lang.String

准则:永远不要在新代码中使用原始类型。如果遇到遗留代码的原始类型,使用@SuppressWarnings("unchecked")抑制警告,并确保类型安全。


7. 泛型的编译期类型检查

泛型的核心优势在于编译期类型安全。下面通过对比来说明:

// ======== 无泛型:运行时才发现错误 ========
List legacy = new ArrayList();
legacy.add("飞翔科技");
legacy.add(2024);               // 可以放入任何类型
legacy.add(new Date());

for (Object obj : legacy) {
    String s = (String) obj;    // ❌ 运行到第二个元素 ClassCastException
    System.out.println(s.length());
}

// ======== 有泛型:编译期直接报错 ========
List<String> generic = new ArrayList<>();
generic.add("飞翔科技");
// generic.add(2024);           // ❌ 编译错误:incompatible types
// generic.add(new Date());     // ❌ 编译错误:incompatible types

for (String s : generic) {
    System.out.println(s.length());  // 无需强制转换,类型已被保证
}

泛型类型安全的多层次保障

编译器在编译泛型代码时,会自动在必要处插入checkcast字节码指令。这意味着即使运行时没有泛型信息,JVM仍能保证类型安全:

// 源代码
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);

// 编译后的等效字节码(反编译后)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);  // 编译器自动插入强制转换

可以用javap -c验证这一点(详见下一章《类型擦除》)。


8. 易错场景

反例一:泛型与基本类型不兼容

// ❌ 错误:泛型类型参数不能是基本类型
List<int> intList = new ArrayList<>();        // 编译错误!
Map<String, double> map = new HashMap<>();    // 编译错误!
Box<char> charBox = new Box<>('A');           // 编译错误!

原因:泛型经过类型擦除后变为Object,基本类型不能赋值给Object。必须使用包装类:

// ✅ 正确:使用包装类
List<Integer> intList = new ArrayList<>();    // 基本类型int -> 包装类Integer
Map<String, Double> map = new HashMap<>();    // double -> Double
Box<Character> charBox = new Box<>('A');      // char -> Character

反例二:混淆泛型类与泛型方法

public class ConfusedExample {
    // ❌ 错误:静态方法不能使用类的类型参数
    // 编译错误:non-static type variable T cannot be referenced from a static context
    public static T processData(T data) {
        return data;
    }
}

原因:类的类型参数T在实例化时才确定,静态方法属于类级别,此时类型参数尚无确定值。

// ✅ 正确:静态方法必须声明自己的类型参数
public class CorrectExample {
    public static <T> T processData(T data) {  // <T> 是方法自己的类型参数
        return data;
    }
}

反例三:用instanceof检查泛型类型

// ❌ 错误:不能对泛型类型使用instanceof
if (obj instanceof List<String>) {  // 编译错误:illegal generic type for instanceof
    // ...
}

// 原因:类型擦除后,运行时只有List,没有List<String>
// ✅ 正确:使用无界instanceof或通配符
if (obj instanceof List) {    // 使用原始类型(不推荐但合法)
    // ...
}

if (obj instanceof List<?>) { // 使用无界通配符(推荐)
    // ...
}

9. 面试考点

Q1:什么是泛型?为什么要使用泛型?

泛型(Generics)是JDK 5引入的语言特性,允许在定义类、接口和方法时使用类型参数。它在编译期提供类型安全检查,消除强制类型转换,使代码更加健壮和可读。核心优势:① 编译期类型检查,将运行时ClassCastException消灭在编码阶段;② 消除强制类型转换,代码更简洁;③ 支持通用算法,一份代码适配多种类型。

Q2:泛型类、泛型接口、泛型方法的区别?

泛型类/接口在类名/接口名后声明类型参数(如class Box<T>),作用于整个类/接口的所有实例方法。泛型方法在方法返回值前声明独立的类型参数(如<T> T method(T t)),可以在普通类中定义。静态方法只能使用泛型方法形式(因为静态上下文获取不到类的类型参数)。

Q3:为什么泛型类型参数不能是基本类型?

泛型在编译后会进行类型擦除(Type Erasure),类型参数被替换为Object或其边界类型。基本类型(int、double等)不是Object的子类,无法赋值给Object引用。因此只能使用对应的包装类(Integer、Double等)。幸运的是,JDK 5引入的自动装箱/拆箱让这个过程对开发者几乎透明。

Q4:原始类型是什么?可以安全使用吗?

原始类型是泛型类不带类型参数的版本(如List而非List<String>),只是为了向后兼容JDK 1.4代码。在新代码中使用原始类型会绕过编译期类型检查,导致运行时ClassCastException。唯一"安全"使用场景是与遗留接口交互,且必须使用@SuppressWarnings("unchecked")并确保类型安全。

Q5:List<Object>和原始类型List有什么区别?

List<Object>明确告诉编译器"这个List可以放任何Object",编译器会检查你放入的对象类型,取出时返回Object。原始类型List则不进行任何泛型类型检查——你可以将其赋值给List<String>而不报错(仅警告),编译器会放弃对该引用的泛型检查。简言之:List<Object>参与了泛型类型系统,List完全退出了泛型检测。

上一页
章节导读
下一页
通配符与PECS原则