首页 > 编程语言 > 详细

Java泛型

时间:2020-02-16 16:17:12      阅读:62      评论:0      收藏:0      [点我收藏+]

一、什么是泛型

泛型从字面上理解,是指一个类、接口或方法支持多种类型,使之广泛化、一般化和更加通用。Java中使用Object类来定义类型也 能实现泛型,但缺点是造成原类型信息的丢失,在使用中容易造成ClassCastException。

二、泛型的好处

  • 使得一个类或方法中的类型参数化,最终达到代码复用的效果。( 不使用泛型,你可能需要每种情况的类或方法都要定义一遍 )。
  • 实现类型检查的功能,避免ClassCastException。(这是相对于使用Object类型实现泛型而言。因为我可以每个类都定义一遍来实现所谓的类型检查)

三、泛型自定义:类、接口、方法

//定义泛型类,接口的定义和类一样
class G1<T> {
    T content;
}

// 定义泛型方法,方法的头部使用<T>声明,注意结构
class GMethod1 {

    // 一般泛型方法定义
    public static <T> void method1(T params) {

    }

    // 返回值也为泛型
    public static <E> E method2(E params) {
        E content = params;
        return content;
    }

}

// extends 的使用,限定泛型的范围,等于或者是extends的子类;只有extends,没有super,通配符才有extends和super
class G2<T extends Number> {
    T content;
}

四、通配符

Java中的泛型通配符分为以下三种:

  • <? extends T> 子类型限定通配符
  • <? super T> 超类型限定通配符
  • <?> 无限定通配符

1. 通配符的使用场景

通配符?只有在修饰一个变量或参数的时候会用到,在定义泛型类或泛型方法的时候是不能使用通配符的。

为了更好说明,我们用代码来解释。

首先我们创建一个类 A,是一个泛型类,里面保存一个变量 value。

public class A<T> {
    private T value;
    // 省略 get 和 set 方法
    // ......
}

我们再创建两个类 Father 和 Son,Son 是 Father 的子类

// Father.java
public class Father {
}

// Son.java
public class Son extends Father{
}

思考下面的代码:

public static void main(String[] args){
    A<Father> a1 = new A<Father>();
    A<Son> a2 = new A<Son>();
    test(a1);
    test(a2);  // 编译错误
}

public void test(A<Father> a){...}

Son 是 Father 的子类,但 test(A<Father> a) 方法却不能接收参数 a2,也就是说 A 不是 A 的子类。这个时候就可以使用通配符来解决,我们修改 test 方法

public static void main(String[] args){
    A<Father> a1 = new A<Father>();
    A<Son> a2 = new A<Son>();
    test(a1);
    test(a2);
}
public void test(A<? extends Father> a){...}

这样可以正常调用,说明类型 A<Son> 是 A<? extends Father> 的子类型。

2. <? extends T> 子类型限定通配符

我们上面的例子使用了子类型限定通配符,使用通配符很方便,但也带来了一些问题,我们接着看代码:

public void test(A<? extends Father> a){
    a.setValue(new Father()); //编译错误
    a.setValue(new Son()); //编译错误
    Father father = a.getValue();
}

你会发现我们根本不能调用 setValue 方法,假想一下 A<? extends Father> 类,里面的方法似乎是这样的

// 这不是真正的Java方法,只是为了说明
? extends Father getValue();
void setValue(? extends Father);

当我们调用 setValue 方法的时候,编译器只知道需要某个 Father 及其子类型,但不知道具体是什么类型,所以它拒绝传递任何的特定类型。

使用 getValue 就不会有问题,因为返回值肯定是 Father 及其子类型,所以我们把返回值赋给一个 Father 的引用完全合法。

3. <? super T> 超类型限定通配符

超类型限定通配符的行为与上面说的子类型限定通配符相反,可以为方法提供参数,但不能使用返回值。我们看下面的例子:

public void test2(A<? super Father> a){
    a.setValue(new Father());
    Father father = a.getValue(); // 编译错误
    Object object = a.getValue(); 
}

假想一下 A<? super Father> 类,里面的方法似乎是这样的

// 这不是真正的Java方法,只是为了说明
void setValue(? super Father)
? super Father getValue() 

setValue 方法不知道参数的具体类型,但是可以确定的是参数肯定是 Father 及其父类型,所以我们传递 Father 及其子类型是合法的。

调用 getValue 方法不能保证返回类型的对象,所以只能赋给一个 Object。

4. <?> 无限定通配符

无限定通配符 <?> 这种方式不能为方法提供参数,调用方法返回值也只能赋给 Object。

public void test3(A<?> a){
    Object object = a.getValue();
    a.setValue(new Object()); //编译错误
}

getValue 的返回值只能赋给一个 Object,setValue 方法不能被调用(**注意:**可以调用 setValue(null))。

所以感觉无限定通配符是集合了上面说的两种通配符的缺点,那我们为什么还要使用它呢?其实在某些简单的场景还是有用的,例如下面这种情况:

// 判断A类中的值是否为空,并不关心A类中值具体是什么类型
public Boolean isNull(A<?> a){
    return a.getValue == null;
}

5. 总结

  • <? extends T> 子类型限定通配符
    无法向其中设置值,但是可以进行正常的取出

  • <? super T> 父类型限定通配符

    可以设置 T 类型及其子类型的对象,但是取出的时候只能赋值给 Object

  • <?> 无限定通配符

    无法向其中设置值,取值的时候也只能赋值给 Object

从上面的总结可以看出,<? extends T> 通配符偏向于内容的获取,而 <? super T> 通配符更偏向于内容的存入。

PECS 原则(Producer Extends Consumer Super) 很好的解释了这两种通配符的使用场景。当然,如果你既想设置值又想取出值,那么就不适合使用通配符了。

 

Java泛型

原文:https://www.cnblogs.com/myitnews/p/12316483.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!