Java基础:泛型

1. 泛型的出现

为什么要有泛型?

  • 实现不同类型的同一个方法时,泛型的出现解决了重载问题,通过泛型只需要复用同一个方法即可
  • 泛型的出现解决了元素存储的安全性问题 如:任何元素都可以加入集合中会导致类型不安全
  • 泛型的出现解决了获取元素时需要强制转换的问题 如:从集合中取出Object类型元素需要强转为所需类型的对象

泛型主要优点是能够在编译时而不是运行时检测错误

即Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。

如何理解Java中的泛型是伪泛型?

Java泛型是在JDK1.5后引入的,为了兼容之前的版本,Java泛型采用伪泛型策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的 “类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

泛型的声明与实例化

  • interface List<T>中的 T 表示类型,可以使用任意字母
  • 在类名后面指定类型参数的值 如:List<String> list = new ArrayList<String>();
  • 注意T 只能是类,不能使用基本数据类型填充,但可以使用包装类填充

泛型在继承上的体现

  • 如果B是A的一个子类型,而G是具体泛型的声明的类,G<B>并不是 G<A>的子类型
  • 如:StringObject的子类,但是 List<String>并不是 List<Object> 的子类。

2. 自定义泛型结构

自定义泛型结构 ①泛型类 ②泛型接口 ③泛型方法

注意事项:

  1. 泛型类可以有多个参数,如 Map<K, V>
  2. 泛型要么一开始就不使用,要么使用就要一直用
  3. JDK1.7后泛型可简化为 List<String> list = new ArrayList<>();
  4. 静态方法中不能使用泛型作为参数类型 如:public static void add(T t) 是不允许的
  5. 异常类不能是泛型的
  6. 不能使用new E[],但可以使用 E[] elements = (E[]) new Object[];
  7. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型 (保留泛型可以是全部保留也可以是部分保留, 指定泛型类型可以是直接擦除(没有类型)也可以是指定具体类型)

第4条静态方法中不能使用类的泛型 因为T是非静态的


2.1 自定义泛型类

/**
 * @author 兴趣使然的L
 * @date 2022/12/11
 */
public class Node<T>{
    // val的类型由T指定
    private T val;

    public Node(T val) {
        this.val = val;
    }

    private T getVal() {
        return val;
    }

    public void setVal(T val) {
        this.val = val;
    }
}
复制代码

注意:可以看到自定义泛型类的构造器并不是 public Node<>(){} 而是 public Node(){}


2.2 自定义泛型接口

/**
 * @author 兴趣使然的L
 * @date 2022/12/11
 */
interface INode<T>{
    // 返回类型由T指定
    public T getVal();
}

public class Node<T> implements INode<T> {
    private T val;

    public Node(T val){
        this.val = val;
    }

    public void setVal(T val) {
        this.val = val;
    }

    @Override
    public T getVal() {
        return null;
    }
}
复制代码

2.3 自定义泛型方法

语法: [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常

/**
 * 自定义泛型方法
 * @param clazz 泛型对象
 * @param <E> 声明一个泛型为E的泛型方法
 * @return 泛型对象声明
 */
public <E> E getInstance(Class<E> clazz) throws InstantiationException, IllegalAccessException {
    E e = clazz.newInstance();
    return e;
}
复制代码


3. 通配符的区别

常用的通配符有 TEKV

任意字母都可以作为通配符,只是上述的这些通配符更见名知意一点

  1. T (type) 表示具体的一个java类型
  2. K V (key value) 分别代表java键值中的Key Value
  3. E (element) 代表Element

无界通配符 ? 表示不确定的java类型

通过List<?> list = new ArrayList<>();理解通配符

  1. 读取List<?>的对象list的元素永远是安全的,因为不管list的真实类型是什么都包含Object
  2. 无法写入list中的元素,会报错,因为不知道元素类型不能向其添加对象
  3. 但是可以写入null,因为null是所有类型的成员

通配符使用注意点:

通配符 ?使用场景

对于不确定或不关心实际操作的类型,可以使用无界通配符?表示无限制类型,或者使用泛型上界限制使用一些类型,如下面的query1方法

class Father {
    String name;

    public Father(String name) {
        this.name = name;
    }
}

class Son extends Father{
    String name;

    public Son(String name) {
        super(name);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Son> sonList = new ArrayList<>();
        query1(sonList);
        query2(sonList);
    }

    public static void query1 (List<? extends Father> fathers) {
        for (Father father : fathers) {
            System.out.println(father.name);
        }
    }

    public static void query2 (List<Father> fathers){
        for (Father father : fathers) {
            System.out.println(father.name);
        }
    }
}
复制代码

对于使用query2会导致报错,因为类型限制只能使用Father


3.1 上界/下界通配符

上界通配符 <? extends E>:使用时指定的类型必须是继承E或E自身(即只允许泛型是E及E的派生类)

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,如果传入的类型不是E或E的子类,编译不成功

上界通配符 <? extends E>的特点:不能往容器中存东西,只能(有限制的)往外取东西

/**
 * @author 兴趣使然的L
 * @date 2022/12/12
 */
class Father { }

class Son extends Father{ }

public class Main {
    public static void main(String[] args) {
        List<? extends Father> list = new ArrayList<>();
        list.add(new Son()); // 报错
        list.add(new Father()); // 报错
        
        Object o = list.get(0);
        Father f = list.get(0);
        
        Son s = list.get(0);    // 报错
    }
}
复制代码

可以看到上界通配符对于存任何数据都会报错, 但是如果是存null的话就不会报错

取数据也有要求,因为上界通配符的限制是E及E的派生类(子类),所以容器里自然就是E及E的派生类的对象,取的自然不能使用E以下的派生类接收,接收数据只能是E及E的基类(父类)接收


下界通配符 < ? super E>:用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object类

下界通配符 < ? super E>的特点:不影响存数据,但取数据只能使用Object接收

/**
 * @author 兴趣使然的L
 * @date 2022/12/12
 */
class Father { }

class Son extends Father{ }

public class Main {
    public static void main(String[] args) {
        List<? super Father> list = new ArrayList<>();
        list.add(new Son());
        list.add(new Father());
        list.add(new Object()); // 报错
        list.add(null);

        Object o = list.get(0);
        
        Father f = list.get(0); // 报错
        Son s = list.get(0);    // 报错
    }
}
复制代码

分析: 下界通配符在容器中存的都是E及E的基类(父类),取出来的数据自然也都是E及E的基类,具体并不清楚是哪个类,所以只能使用Object接收

PECS原则

  1. 频繁往外读取数据的适合:上界通配符<? extends T>
  2. 频繁往里插入数据的适合:下界通配符<? super T>

3.2 ? 与 T的区别

T 是一个 确定 的类型,通常用于泛型类和泛型方法的定义,是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

通过 T 来确保泛型参数的一致性

使用多重限定:T 可以 而 ? 不可以

使用超类限定:? 可以 而 T 不可以

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值