Java 泛型的学习笔记

什么是泛型?

泛型是在JDK5引入的新特性,使用"<数据类型>"符号表示泛型。使用泛型的接口、类、方法,会在编译阶段,要求编译器检查数据类型是否匹配,从而达到限制数据类型的目的。比如:

java 复制代码
public class Demo0 {  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<>(); // 使用泛型<String>,集合在编译阶段就被限定为只能存放String类型的元素。编译器会检查所有相关操作,确保类型匹配。
        list.add("hello"); // 添加的元素符合泛型限制的数据类型String, 所以可以添加  
        // list.add(new Dog("小黄", 1)); // 添加的Dog元素不符合泛型限制的数据类型,编译器会直接报错,代码无法通过编译。
    }  
}

上方代码中,泛型的数据类型指定为String类型,则在List对象中添加的元素,只能是String类型,而添加其他类型(比如Dog类型)的元素,会在编译阶段报错。

为什么要引入泛型?

在引入泛型之前,有一些有关数据类型的难题。

如果我要定义一个列表,假如说希望这个列表可以存放所有数据类型的元素,我只能将存储元素的数据结构体定义为Object。但是,现在产生了一个问题,由于存放的元素都是Object类型,对元素的操作就只能使用Object的方法,而不能用元素特定的方法。如果要使用特定数据类型的方法,则需要强制类型转换,这会抛出ClassCastException异常。

java 复制代码
// 如果没有泛型,那么只能用Object类型存储集合中的元素  
public class MyList1{ // 自定义一个指定容量的List类  
    private Object[] array;  
    private int size;  
  
    public MyList1(int capacity) {  
        array = new Object[capacity];  
    }  
  
    public void add(Object o) {  
        array[size] = o;  
        size++;  
    }  
  
    public Object get(int index) {  
        return array[index];  
    }  
  
    public int size() {  
        return size;  
    }  
}
java 复制代码
public class Demo2 {  
    public static void main(String[] args) {  
        // 创建自定义List对象  
        MyList1 list = new MyList1(10);  
        list.add("hello"); // 添加Stirng类型元素  
        list.add(100); // 添加int类型元素(自动装箱为Integer类型)  
        list.add(new Dog("小黄")); // 添加Dog类型元素  
  
        for(int i = 0; i < list.size(); i++) {  
            Object o = list.get(i);  
            System.out.println(o.toString()); // 只能使用Object类型的实例方法  
        }  
  
        // 强制类型转换,则会抛出ClassCastException异常  
        // for(int i = 0; i < list.size(); i++) {  
        //     Dog s = (Dog) list.get(i);        //     s.eat();        // }    }  
}

当然,这种情况下,如果列表都存储数据类型的元素,也可以强制做数据类型转换,之后即可调用该元素的数据类型的方法。比如:

java 复制代码
public class Demo3 {  
    public static void main(String[] args) {  
        MyList1 list = new MyList1(10); // 存放相同数据类型的元素  
        list.add("hello");  
        list.add("world");  
        list.add("java");  
  
        for(int i = 0; i < list.size(); i++) {  
            String s = (String) list.get(i); // 强制类型转换  
            System.out.println(s.length());   
        }  
    }  
}

也就是说,在没有泛型时,我们只能依赖代码编写时,程序员注意保持相同的数据类型,并且要手动做强制类型转换。这种很容易出错。

引入泛型,用"< T>",相当于引入了一个数据类型变量"T",让"T"作为存储元素的数据结构类型。

java 复制代码
public class MyList2<T> { // 自定义一个指定容量的List类  
    private T[] array;  
    private int size;  
  
    public MyList2(int capacity) {  
        array = (T[]) new Object[capacity];  
    }  
  
    public void add(T o) {  
        array[size] = o;  
        size++;  
    }  
  
    public T get(int index) {  
        return array[index];  
    }  
  
    public int size() {  
        return size;  
    }  
}

在编译阶段,编译器会根据<String>对类型T进行严格的检查和推断,所有使用T的地方都被视为String

在运行时(JVM层面),这个"替换"并没有发生。Java的泛型是通过 "类型擦除" 实现的。也就是说,在编译后的字节码中,T被替换成了它的上界(在你这里是 Object),MyList2<String>MyList2<Integer> 在运行时其实是同一个类。

java 复制代码
public class Demo4 {  
    public static void main(String[] args) {  
        MyList2<String> list = new MyList2<>(10); // 使用泛型类存放字符串元素  
        list.add("hello");  
        list.add("world");  
        // list.add(new Dog("小黄")); // 添加Dog类型元素,编译器会报错  
    }  
}

如上代码,在添加Dog类型元素时,编译器会报类型错误。因为编译器会认为你在调用add方法时,需要使用String类型,而不应该用Dog类型。

java 复制代码
public void add(String o) {  // T 已被替换成String
        array[size] = o;  
        size++;  
    }  

综上所述,引入泛型后,可以让编译器检查数据类型约束,减少错误。

初次之外,泛型还提供两个价值:

  1. 代码复用,使用泛型,就不需要为每个数据类型单独写一个接口、类或方法。比如,写了一个Collection< T >,就可以创建Collection< String >、Collection < Integer >等。
  2. 代码可读性。List<String> 一眼就能看出这个列表里放的是什么,比 List然后靠注释说明清晰得多。
相关推荐
2501_93786094几秒前
上篇:网络编程基础与UDP套接字编程
java·网络·计算机网络
邵奈一5 分钟前
【紧急处理ZCode事件】ZCode 工作区快照静默上传:检测方法与三层防护实践
人工智能·学习·大模型·国产
泡海椒6 分钟前
jquick-pdf 表格行列尺寸控制实战:单元格合并的可行边界
java·开发语言·pdf
Wang's Blog9 分钟前
Java 项目实战: 外卖平台-后台系统登录功能开发
java·服务器·redis
蒸蒸yyyyzwd14 分钟前
cpp 选手秋招学习笔记 day40
笔记·八股
有范先生15 分钟前
速食米饭:如何避开闷味、干硬的速食主食
笔记
海鸥-w16 分钟前
spingboot定义一个全局异常处理器
java
那年窗外下的雪.19 分钟前
AIDC 学习日志|第 27 天|EVPN 多归属收敛时间线与 Leaf2 接管验证
网络协议·学习·http·tcpdump
长葡萄的叶子27 分钟前
AI Agent 中的 Skill:从 Tool 调用到任务能力封装
笔记·skill
曹牧28 分钟前
Jackson 反序列化字段名不匹配
java