Java泛型详解,通俗易懂只需5分钟

Java泛型详解,通俗易懂只需5分钟

01 认识泛型

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        dealList(list);
    }

    public static void dealList(List list) {
        int result = 0;
        for (Object obj : list) {
            int num = (int) obj;
            result += num;
        }
        System.out.println(result);
    }
}

在该示例中,dealList方法用于计算一个集合中的元素和,当然,只有数字才能够参与运算,但是示例中的list是可以存放任意对象的,所以很可能会出现如下情况:

public static void main(String[] args) {
    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add("4");
    dealList(list);
}

因为字符串无法转为整型,所以程序会报错:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

这时候泛型的约束就显得尤为重要,在定义集合的时候对其进行类型的限定即可在编译期就避免这一类问题:

img

02 定义泛型类

泛型的用法十分多样,它可以作用在 类、方法 上实现各种功能,先来了解一下泛型类的定义。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Genericity {

    private String str1;
    private String str2;
}

以上是一个简单的Java类,我们想通过它计算其中两个属性值的和:

public static void main(String[] args) {
    Genericity genericity = new Genericity("1","1");   
    String str1 = genericity.getStr1();
    String str2 = genericity.getStr2();
    Integer num1 = Integer.valueOf(str1);
    Integer num2 = Integer.valueOf(str2);
    int result = num1 + num2;
    System.out.println(result);
}

但此时我觉得将属性定义为String是一个错误的决定,因为计算属性值的和还需要自己对类型进行转换,所以我将属性调整为了Integer类型:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Genericity {

    private Integer str1;
    private Integer str2;
}

当然了,这个例子可能有一些牵强,事实上不会有人这么做,但目的很明显,就是想让类中的属性类型可变,使得整个类更加灵活,基于此需求,我们可以使用泛型对其进行改造:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Genericity<T> {

    private T t1;
    private T t2;
}

这就是泛型类的定义,通过在类名后面添加<T>符号即可定义泛型类,而类中的属性类型均为T,这将导致类中的属性类型会跟随T的变化而变化,用法如下:

public static void main(String[] args) {
    Genericity<Integer> genericity = new Genericity<>(1, 1);
    Integer t1 = genericity.getT1();
    Integer t2 = genericity.getT2();
    int result = t1 + t2;
    System.out.println(result);
}

在创建泛型类对象时,同样在类名后添加<Integer>,此时Integer就作为了T符合的内容,所以类中的属性都将是Integer类型。如果类名后添加<String>,则类中的属性均为String类型。

实际上,泛型类可以定义多个泛型变量,比如:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Genericity<T,U> {

    private T t1;
    private U t2;
}

此时我们在创建对象时就可以传入两个类型:

public static void main(String[] args) {
    Genericity<Integer,String> genericity = new Genericity<>(1, "1");
    Integer t1 = genericity.getT1();
    String t2 = genericity.getT2();
}

03 定义泛型方法

泛型除了作用在类上,还可以作用在方法上,效果与泛型类是相似的。

public static <T> void method(T t) {
}

只需在调用方法时传入类型即可:

public static void main(String[] args) {
    Genericity.<Integer>method(1);
}

而事实上,你无需这么写,因为编译器能够自动推断出泛型类型T,所以调用泛型方法可以简写为:

public static void main(String[] args) {
    Genericity.method(1);
}

泛型方法的返回值也可以使用类型变量:

public static <T> T method(T t) {
    return t;
}

当然了,泛型方法也支持定义多个类型变量:

public static <T,U> T method(T t,U u) {
    return t;
}

04 泛型的限定符

来看下面的程序:

public static <T> T min(T[] t) {
    //接收 第0个参数,为最小值
    T minimum = t[0];
    for (int i = 0; i < t.length; i++) {
        //如果最小值, > 当前的数
        if (minimum.compareTo(t[i]) > 0) {
            //最小值 重新赋值
            minimum = t[i];
        }
    }
    return minimum;
}

能看得出来这段程序在干什么吗?是的,它能够取出数组t中的最小值,然而这段程序是有问题的,因为T可以是任意类型的对象,但不是什么对象都能够调用compareTo方法进行比较的,所以,我们需要对类型变量T进行限定,限定为实现了Comparable接口的对象,如下:

public static <T extends Comparable> T min(T[] t) {
    T minimum = t[0];
    for (int i = 0; i < t.length; i++) {
        if (minimum.compareTo(t[i]) > 0) {
            minimum = t[i];
        }
    }
    return minimum;
}

一个泛型方法也可以对类型变量进行多个限定:

public static <T extends Comparable & Serializable> T min(T[] t) {
    ......
}

05 泛型擦除

需要知道的是,泛型仅仅是在编译期间起作用,目的是让程序员在编译期间就避免发生一些类型不对应的问题,而在运行阶段,泛型是根本不存在的,因为虚拟机会对泛型进行擦除。

我们可以通过反射进行验证,因为反射是作用在运行阶段的:

public static void main(String[] args) throws Exception {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    
    Method addMethod = list.getClass().getDeclaredMethod("add",Object.class);
    
    addMethod.invoke(list,"2");
    addMethod.invoke(list,true);
    addMethod.invoke(list,3.2f);
    
    System.out.println(list);
}

若是通过反射能够将这些非Integer类型的值存入list,则说明在运行期间确实是不存在泛型检查的,运行结果如下:

[1, 2, true, 3.2]

泛型擦除也体现在泛型方法中,回顾之前的例子:

public static <T extends Comparable> T min(T[] t) {
    ......
}

当程序运行期间,泛型会被擦除,此时方法变为如下:

public static Comparable min(Comparable t) {
    ......
}

这也就是为什么类型限定能够生效的原因了,通过泛型擦除后,该方法就只能接收和返回Comparable接口的实现类。

06 泛型通配符

固定的泛型类型显然无法满足复杂多变的需求,为此,泛型设计者们还提供了泛型通配符,如:

Genericity<? extends Person>

它表示类型变量必须是Person类型的子类。当然了,泛型通配符还有超类型定义的情况:

Genericity<? super Person>

此时类型变量就必须是Person类的超类。

还有一种情况是无限定通配符:

Genericity<?>

它和Genericity<T>非常相似,但又有不同,

  • Genericity<?>的setter方法不能被调用,getter方法只能返回Object类型,不过这种方式的用法较少,可能会被用来判断空引用:
public static boolean isNull(Genericity<?> genericity){
    return genericity.getContent();
}

本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值