深入理解 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 泛型的从入门到中高级玩法就讲到这儿啦!希望你看完这篇文章,对泛型不再迷糊,能在代码里把它用得飞起!要是还有啥问题,评论区留言,咱一起唠唠。

相关推荐
江节胜-胜行全栈AI19 分钟前
Java-腾讯云短信模板兼容阿里云短信模板-短信模板参数生成
java·阿里云·腾讯云
z263730561123 分钟前
springboot继承使用mybatis-plus举例相关配置,包括分页插件以及封装分页类
spring boot·后端·mybatis
追逐时光者3 小时前
分享一个纯净无广、原版操作系统、开发人员工具、服务器等资源免费下载的网站
后端·github
JavaPub-rodert4 小时前
golang 的 goroutine 和 channel
开发语言·后端·golang
TFHoney5 小时前
Java面试第十一山!《SpringCloud框架》
java·spring cloud·面试
ivygeek6 小时前
MCP:基于 Spring AI Mcp 实现 webmvc/webflux sse Mcp Server
spring boot·后端·mcp
日暮南城故里6 小时前
Java学习------初识JVM体系结构
java·jvm·学习
GoGeekBaird6 小时前
69天探索操作系统-第54天:嵌入式操作系统内核设计 - 最小内核实现
后端·操作系统
鱼樱前端7 小时前
Java Jdbc相关知识点汇总
java·后端
小嘚7 小时前
springCloud的学习
学习·spring·spring cloud