java中的泛型

文章目录


一、为什么需要泛型?

在泛型出现之前(JDK5之前),Java集合(比如HashMap、ArrayList)只能存储Object类型的对象,会带来两个致命问题:

  1. 类型不安全:可以往集合里存任意类型的对象,运行时可能抛出类型转换异常。
  2. 需要手动强制类型转换:取数据时必须手动转换成目标类型,代码繁琐且容易出错

反面例子(无泛型的HashMap):

java 复制代码
public static void main(String[] args) {
        //无泛型的HashMap:默认存object类型
        Map map = new HashMap();

        //1.可以存任意类型(字符串key + 整数value 、字符串key + 字符串Value)
        map.put("年龄",20);
        map.put("姓名","张三");
        //甚至能存不相关的类型(比如key是整数,Value是数组)
        map.put(100,new int[]{1,2,3});

        //2.取值时必须手动强转,且容易出错
        //正确转换:年龄是整数
        int age = (int) map.get("年龄");
        System.out.println("年龄:" + age);

        //错误转换:姓名是字符串,强转成整数会抛出ClassCastException
        //int name = (int) map.get("姓名"); //运行时报错
    }

问题总结:

  • 编译时无法检查类型错误(比如往存"年龄"的Map里存字符串),只有运行时才会报错
  • 每次取值都要强制类型转换,代码冗余且容易出错。

而泛型的核心作用 就是:在编译阶段限制集合/类的类型,保证类型安全,同时避免手动类型转换。


二、泛型的核心概念

2.1 定义

泛型(Generic)是JDK5引入的特性,允许在定义类、接口、方法时,使用"类型参数"(比如)来表示未知类型,使用时再指定具体类型(比如< String,Integer>)。

可以把泛型理解成"类型的占位符":

  • 定义时:用<T> <k,v>等符号占位置(T = type,K = key,V = value,只是约定俗称的命名,用其他的字母也可以)
  • 使用时:用具体的类型(比如String、Integer)替换占位符。

2.2 核心优势

优势 说明
类型安全 编译时检查类型,避免运行时ClassCastException
消除强制转换 取值时自动匹配类型,无需手动强转
代码复用 一套泛型代码可以适配多种类型,不用为每种类型写重复代码

三、泛型的核心用法

泛型的使用场景主要分为3类:泛型类/接口、泛型方法、通配符,其中前两类是新手必须掌握的核心

3.1 泛型类/接口

泛型类的定义格式:class 类名<类型参数> { ... }

泛型接口的定义格式: interface 接口名<类型参数> { ... }

示例1:自定义泛型类

模拟HashMap的核心逻辑,写一个简单的泛型键值对类

java 复制代码
class MyGenericMap<K,V> {
    //成员变量:类型为K和V(泛型类型)
    private K key;
    private V value;

    //构造方法:参数类型为K和V
    public MyGenericMap(K key,V value) {
        this.key = key;
        this.value = value;
    }

    //普通方法:返回值类型为K/V
    public K getKey() {
        return key;
    }
    public V getValue() {
        return value;
    }

    //重写toString,方便打印
    @Override
    public String toString() {
        return key + " = " + value;
    }
}
public class Test {
    public static void main(String[] args) {
        //使用泛型类:指定K = string,V = Integer(具体类型)
        MyGenericMap<String,Integer> map1 = new MyGenericMap<>("年龄",20);
        //取值时无需强转,直接是String/Integer类型
        String key1 = map1.getKey();
        Integer value1 = map1.getValue();
        System.out.println(map1); //输出:年龄=20

        //换一种类型:K = Integer,V = String
        MyGenericMap<Integer,String> map2 = new MyGenericMap<>(1001,"张三");
        Integer key2 = map2.getKey();
        String value2 = map2.getValue();
        System.out.println(map2);

        //编译类型检查:如果存错类型,编译直接报错(类型安全)
        Map<String,Integer> map3 = new HashMap<>();
        //map3.put("姓名",20.5); //编译报错,Integer类型不能赋值给Double
    }
}

关键说明:

  • 定义时<K,V>是"类型参数",代表未知类型;
  • 使用时<String,Integer>是"类型实参",指定具体类型
  • 编译时会检查类型,比如往Map<String,Integer>里存Double类型,直接编译报错,避免运行时异常。

示例2:泛型集合

以HashMap,ArrayList为例,演示泛型在集合中的使用:

java 复制代码
public static void main(String[] args) {
        //1.泛型ArrayList:限制只能存String类型
        List<String> strlist = new ArrayList<>();
        strlist.add("张三");
        strlist.add("李四");
        //strlist.add(123); //编译报错:不能存整数,只能存String
        //取值无需强转,直接是String类型
        String name = strlist.get(0);
        System.out.println("List第一个元素:" + name);

        //2.泛型HashMap:限制Key=String,value=Integer
        Map<String,Integer> scoreMap = new HashMap<>();
        scoreMap.put("语文",90);
        scoreMap.put("数学",95);
        //scoreMap.put("英语","95"); //编译报错:value必须是Integer
        //取值无需强转,直接是Integer类型
        int mathScore = scoreMap.get("数学");
        System.out.println("数学成绩:" + mathScore);
    }

核心总结:

集合使用泛型后,相当于给集合加了"类型过滤器",只能存指定类型的元素,编译时就能发现类型错误,取值也不需要进行强转。

3.2 泛型方法

泛型方法是指方法本身带有类型参数,即使所在的类不是泛型类,也可以定义泛型方法

定义格式:修饰符<类型参数>返回值类型 方法名(参数列表) {...}

java 复制代码
    public static <T> List<T> arrayToList(T[] array) {
        List<T> list = new ArrayList<>();
        for (T element : array) {
            list.add(element);
        }
        return list;
    }
    //自定义Person类
    static class Person{
        private String name;
        private int age;

        public Person(String name,int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Person{" + "name =  ' " + name + " ',age = " + age + "}";
        }
    }
    public static void main(String[] args) {
        //测试1:整数数组转List<Integer>
        Integer[] intAaay = {1,2,3};
        List<Integer> intlist = arrayToList(intAaay);
        System.out.println("整数List:" + intlist); //输出【1,2,3】

        //测试2:字符串数组转List<String>
        String[] strArray = {"a","b","c"};
        List<String> strlist = arrayToList(strArray);
        System.out.println("字符串List:" + strlist); //输出:【a,b,c】

        //测试3:自定义类型数组转List
        Person[] people = {new Person("张三",20),new Person("李四",25)};
        List<Person> personList = arrayToList(people);
        System.out.println("personList:" + personList);
    }

关键说明:

  • 泛型方法的<T>必须写在static之后,返回值之前(这是语法规则)
  • 同一个泛型方法可以适配多种类型(整数、字符串、自定义Person),实现代码复用
  • 调用时无需手动指定<T>,java会自动根据传入的参数类型推断(比如传入Integer数组,T自动就是Integer)

3.3 泛型通配符

泛型通配符用于 "灵活匹配泛型类型",解决 "泛型不协变" 的问题(比如 List<String> 不是 List<Object> 的子类)。新手先掌握核心用法:

java 复制代码
//1.无界通配符:打印任意类型的List
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.println(obj + " ");
        }
        System.out.println();
    }

    //2.上界通配符:计算数字List的总和(只能是Number及其子类)
    public static double sumlist(List<? extends Number> list){
        double sum = 0;
        for (Number num : list) {
            sum += num.doubleValue();
        }
        return sum;
    }

    public static void main(String[] args) {
        //测试无界通配符
        List<String> strlist = new ArrayList<>();
        strlist.add("a");
        strlist.add("b");
        printList(strlist); //输出:a b

        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        printList(intList); //输出1 2


        //测试上界通配符
        List<Integer> numlist = new ArrayList<>();
        numlist.add(10);
        numlist.add(20);
        System.out.println("总和:" + sumlist(numlist));//输出:30.0

        List<String> stringList = new ArrayList<>();
        stringList.add("c");
        stringList.add("d");
        System.out.println("总和: " + sumlist(stringList)); //编译报错,String不是number的子类,为了保证类型安全,编译报错
    }

四、泛型的核心规则

  1. 泛型只在编译阶段有效(类型擦除)

    Java 泛型是 "编译时语法糖",运行时会把泛型类型擦除成 Object(或上界类型)。比如:

    • 编译时 List 和 List 是不同类型;
    • 运行时两者都是 List 类型,无法通过反射区分。
  2. 不能用基本类型作为泛型参数

    泛型参数只能是引用类型(比如 Integer、String),不能是基本类型(int、char、double)。

    ❌ 错误:List list = new ArrayList<>();

    ✅ 正确:List list = new ArrayList<>();

  3. 泛型类的静态成员不能使用泛型参数

    静态成员属于类,而泛型参数属于对象(创建对象时才指定),所以静态方法 / 变量不能用类的泛型参数:

java 复制代码
class MyClass<T> {
    // 错误:静态变量不能用T
    // private static T value;

    // 正确:如果静态方法需要泛型,定义成泛型方法
    public static <E> E getValue(E e) {
        return e;
    }
}
相关推荐
一 乐6 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长7 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn7 小时前
Spring 注解使用详解
java·spring
一条小小yu7 小时前
Spring IoC的理解
java·后端·spring
落魄实习生7 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈8 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白9 小时前
核心功能(Service接口)
java·mybatis
乌暮10 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习
大猫和小黄10 小时前
深入理解 Java Optional:从设计哲学到工程实战
java