深入理解 Java 泛型:从为啥用到咋高级用

嘿,各位 Java 码农小伙伴们!今天咱来唠唠 Java 里那个有点神秘又超有用的玩意儿 ------ 泛型。你是不是有时候看着代码里那些尖括号 <> ,心里犯嘀咕:这到底是干啥的?别慌,今儿个就给你整得明明白白,从为啥要用泛型,一路杀到泛型的中高级玩法,代码案例管够,包你学个通透!

为啥要有泛型这玩意儿?

咱先从一个简单的例子说起。假设你要写一个能装各种东西的容器,一开始可能会这么干:

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class WithoutGeneric {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("Hello, World!");
        list.add(42);
        list.add(3.14);
        for (Object obj : list) {
            String str = (String) obj; // 这里会报错,因为不是所有元素都是String
            System.out.println(str);
        }
    }
}

这段代码乍一看没啥问题,咱往 List 里塞了字符串、整数还有浮点数。但是到了取数据的时候,就出幺蛾子了。你得手动把取出来的 Object 转成你想要的类型,而且要是转错了,运行时就给你抛个 ClassCastException ,这可太闹心了。

这时候泛型就闪亮登场啦!它就像是给你的容器贴上了标签,告诉编译器这个容器里装的到底是啥类型。

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class WithGeneric {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Hello, World!");
        // list.add(42); 这行代码会报错,因为List被指定为只能装String类型
        for (String str : list) {
            System.out.println(str);
        }
    }
}

瞧见没,用了泛型之后,编译器就会帮你检查类型,往 List 里塞错东西直接报错,根本不让你运行。这就好比你有个标着 "只放袜子" 的抽屉,你要是硬塞个鞋子进去,它就会喊:"嘿,不对啊,这不是放鞋子的地儿!" 是不是超省心?

泛型的中高级玩法

泛型类

除了在集合里用,咱还能自己定义泛型类。比如说,咱做一个简单的二元组类:

csharp 复制代码
public class Pair<T, U> {
    private T first;
    private U second;
    public Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }
    public T getFirst() {
        return first;
    }
    public U getSecond() {
        return second;
    }
}

这里的 <T, U> 就是泛型参数, T 和 U 可以是任何类型。咱可以这样用它:

arduino 复制代码
public class PairUsage {
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("Number of apples", 5);
        String description = pair.getFirst();
        Integer count = pair.getSecond();
        System.out.println(description + ": " + count);
    }
}

看,通过泛型,这个 Pair 类可以灵活地装不同类型的两个值,就像一个万能的小口袋,啥组合都能装,只要你告诉它装的是啥类型。

泛型方法

泛型可不只在类上能用,方法也能有泛型。假设你有一个工具类,里面有个方法能返回数组里最大的元素:

php 复制代码
public class GenericMethod {
    public static <T extends Comparable<T>> T max(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }
}

这里 <T extends Comparable> 表示 T 必须是实现了 Comparable 接口的类型,这样才能比较大小。咱可以这么调用这个方法:

ini 复制代码
public class GenericMethodUsage {
    public static void main(String[] args) {
        Integer[] intArray = {1, 5, 3, 9, 7};
        Integer maxInt = GenericMethod.max(intArray);
        System.out.println("Max int: " + maxInt);
        String[] stringArray = {"banana", "apple", "cherry"};
        String maxString = GenericMethod.max(stringArray);
        System.out.println("Max string: " + maxString);
    }
}

这个 max 方法就像一个超级裁判,不管你给它啥类型的数组(只要能比较大小),它都能给你找出最大的那个,是不是很厉害?

通配符

通配符也是泛型里一个很有用的东西。比如说,有个方法要打印一个 List 里的所有元素,但这个 List 可以是任何类型,这时候就可以用通配符 ? :

java 复制代码
import java.util.List;
public class WildcardExample {
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
}

调用的时候:

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class WildcardUsage {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        WildcardExample.printList(intList);
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        stringList.add("World");
        WildcardExample.printList(stringList);
    }
}

这里的 List<?> 就像是一个万能的 List 接收器,不管你传进来的是 List 还是 List ,它都能照单全收,然后打印出来,是不是很方便?

还有更高级的,比如 <? extends Type> 和 <? super Type> ,这俩就像是给通配符加了限制。 <? extends Type> 表示这个类型是 Type 的子类或者就是 Type 本身, <? super Type> 则表示这个类型是 Type 的父类或者就是 Type 本身。

举个例子,假设你有个动物类 Animal ,还有它的子类 Cat 和 Dog :

scala 复制代码
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}

有个方法要处理所有 Cat 及其子类的 List ,就可以这么写:

java 复制代码
import java.util.List;
public class UpperBoundWildcard {
    public static void processCats(List<? extends Cat> catList) {
        for (Cat cat : catList) {
            // 这里可以对cat进行操作
        }
    }
}

调用的时候:

scala 复制代码
import java.util.ArrayList;
import java.util.List;
public class UpperBoundUsage {
    public static void main(String[] args) {
        List<Cat> catList = new ArrayList<>();
        catList.add(new Cat());
        UpperBoundWildcard.processCats(catList);
        // 假设还有个SiameseCat类继承自Cat
        class SiameseCat extends Cat {}
        List<SiameseCat> siameseCatList = new ArrayList<>();
        siameseCatList.add(new SiameseCat());
        UpperBoundWildcard.processCats(siameseCatList);
    }
}

而 <? super Type> 则相反,它允许传入 Type 的父类类型的 List 。比如有个方法要往 List 里添加 Cat ,就可以用 <? super Cat> :

java 复制代码
import java.util.List;
public class LowerBoundWildcard {
    public static void addCat(List<? super Cat> catList) {
        catList.add(new Cat());
    }
}

调用的时候:

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class LowerBoundUsage {
    public static void main(String[] args) {
        List<Cat> catList = new ArrayList<>();
        LowerBoundWildcard.addCat(catList);
        List<Animal> animalList = new ArrayList<>();
        LowerBoundWildcard.addCat(animalList);
    }
}

这样,通过通配符的上下界,你可以更精确地控制泛型的类型范围,就像给你的代码加了个灵活又精准的过滤器。

好啦,小伙伴们,今天关于 Java 泛型的从入门到中高级玩法就讲到这儿啦!希望你看完这篇文章,对泛型不再迷糊,能在代码里把它用得飞起!要是还有啥问题,评论区留言,咱一起唠唠。

相关推荐
栈与堆1 分钟前
LeetCode 19 - 删除链表的倒数第N个节点
java·开发语言·数据结构·python·算法·leetcode·链表
一路向北·重庆分伦3 分钟前
03-01:MQ常见问题梳理
java·开发语言
一 乐4 分钟前
绿色农产品销售|基于springboot + vue绿色农产品销售系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·宠物
lhrimperial10 分钟前
企业智能知识库助手落地实践:从RAG到Multi-Agent
java·spring cloud·微服务·系统架构·知识图谱
3***688415 分钟前
Spring Boot中使用Server-Sent Events (SSE) 实现实时数据推送教程
java·spring boot·后端
k***19518 分钟前
Spring 核心技术解析【纯干货版】- Ⅶ:Spring 切面编程模块 Spring-Instrument 模块精讲
前端·数据库·spring
C***u17619 分钟前
Spring Boot问题总结
java·spring boot·后端
上进小菜猪19 分钟前
基于 YOLOv8 的人体与行人检测智能识别实战 [目标检测完整源码]
后端
Elieal33 分钟前
5 种方式快速创建 SpringBoot 项目
java·spring boot·后端
c***693035 分钟前
Spring Boot实时推送技术详解:三个经典案例
spring boot·后端·状态模式