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;
    }
}
相关推荐
百锦再2 小时前
Java重入锁(ReentrantLock)全面解析:从入门到源码深度剖析
java·开发语言·struts·spring·kafka·tomcat·intellij-idea
知识即是力量ol2 小时前
口语八股—— Spring 面试实战指南(终篇):常用注解篇、Spring中的设计模式
java·spring·设计模式·面试·八股·常用注解
yuezhilangniao2 小时前
win10环境变量完全指南:Java、Maven、Android、Flutter -含我的环境备份
android·java·maven
追随者永远是胜利者2 小时前
(LeetCode-Hot100)32. 最长有效括号
java·算法·leetcode·职场和发展·go
lifallen2 小时前
CDQ 分治 (CDQ Divide and Conquer)
java·数据结构·算法
笨蛋不要掉眼泪2 小时前
OpenFeign远程调用详解:声明式实现、第三方API集成与负载均衡对比
java·运维·负载均衡
yaoxin5211232 小时前
326. Java Stream API - 实现自定义的 toList() 与 toSet() 收集器
java·开发语言
追随者永远是胜利者2 小时前
(LeetCode-Hot100)31. 下一个排列
java·算法·leetcode·职场和发展·go
Cosmoshhhyyy2 小时前
《Effective Java》解读第40条:坚持使用Override注解
java·开发语言