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

相关推荐
bobz96518 分钟前
tcp/ip 中的多路复用
后端
bobz96528 分钟前
tls ingress 简单记录
后端
皮皮林5512 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友2 小时前
什么是OpenSSL
后端·安全·程序员
bobz9652 小时前
mcp 直接操作浏览器
后端
前端小张同学4 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook4 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康5 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在5 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net
文心快码BaiduComate6 小时前
文心快码入选2025服贸会“数智影响力”先锋案例
前端·后端·程序员