java中的泛型(二)

java中的泛型(二)

    上一节我介绍了java中泛型的基本原理和使用,今天我介绍java中泛型类型参数的限定和通配符。在java中,泛型是通过类型擦除实现的,泛型是java编译器的概念,java在运行时对与泛型是一无所知的,了解这一点有助于理解java中泛型的一些令人混淆的地方和局限。

    在上一篇文章中我们提到了一个词叫做类型参数。关于这个参数我们了解的不多知识把它看成一个Object对象,在java中支持限定这个参数的上界,也就是说参数必须是给定上界的子类或就是给定的上界类型。这个限定通过关键字extends实现。这个上界可以是某个具体的类或者接口。例如:

public class Generic<K extends Number,V extends Number>{
    K one;
    V two;
    public Generic(K one, V two){
    this.one = one;
    this.two = two;
    }
    public K getOne() {
    return one;
    }
    public V getTwo() {
    return two;
    }
    
    
    public static void main(String[] args) {
        Generic<Integer, Double> generic = new Generic<>(2,2.3);
        Integer one = generic.getOne();
        Double two = generic.getTwo();
        System.out.println(one);
        System.out.println(two);
         
    }
}

另一中比较常见的是限定上界为一个接口,类型参数必须实现这个接口。在泛型方法中有一种场景就是限定的类型必须实现Comparable接口:

public static <T extends Comparable<T>> T max(List<T> arr) {
        T max = arr.get(0);
        for (int i = 1; i < arr.size(); i++) {
            if (arr.get(i).compareTo(max) > 0) {
                max = arr.get(i);
            }
        }
        return max;
    }

这个max方法是计算list集合中的最大值,所以需要在list集合中的元素实现Comparable接口。上面介绍的上界都是具体存在的类和接口,在java中允许也上界为一个类型参数。首先我们需要先定义一个容器类:

public class SimpleArrayList<E> {
    private static final int DEFAULT_CAPACITY = 20;
    private int size;
    private Object[] element;

    public SimpleArrayList() {
        this.element = new Object[DEFAULT_CAPACITY];
    }

    private void ensureCapacity(int minCapacity) {
        int oldCapacity = element.length;
        if (oldCapacity >= minCapacity) {
            return;
        }
        int newCapacity = oldCapacity * 2;
        if (newCapacity < minCapacity){
            newCapacity = minCapacity;
        }
        element = Arrays.copyOf(element, newCapacity);
    }

    public void add(E e) {
        ensureCapacity(size + 1);
        element[size++] = e;
    }

    public E get(int index) {
        return (E) element[index];
    }

    public int size() {
        return size;
    }
}

这个容器类是使用数组来存放数据的,当存放的数据数量大于数组长度是就会进行扩容。假如现在我们需要在这个容器类中添加一个addAll的方法,我们可以这样来写:

public void addAll(SimpleArrayList<E> c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

这样写看起来并没有什么错误,我们也可以把集合全部添加进去:

public static void main(String[] args) {
        SimpleArrayList<Number> list = new SimpleArrayList<>();
        SimpleArrayList<Number> list1 = new SimpleArrayList<>();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list.addAll(list1);
        
    }

可是如果仔细观察会发现list和list1传递进来的参数类型都是Number类型的,如果我们把list1的改成这样:

public static void main(String[] args) {
        SimpleArrayList<Number> list = new SimpleArrayList<>();
        SimpleArrayList<Integer> list1 = new SimpleArrayList<>();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list.addAll(list1);
    }

那么addAll方法会在编译期间报错。这是因为addAll需要的参数类型是SimpleArray<Number>类型的不是SimpleArray<Integer>类型,即使Integer是Number类型的子类也是不行的。可是我们需求是没有错的,Number的集合当然可以存放Integer,所以我们需要修改一下addAll方法。

public <T extends E> void addAll(SimpleArrayList<T> c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

我们修改addAll方法,其中T是addAll的类型参数,E是SimpleArrayList的类型参数,T的上限是E,这样addAll方法就不会报错了。

    在java泛型中还有一个不太好理解的知识点叫做通配符。在上面举得这个例子中如果addAll不使用通配符写难免有一些臃肿,使用通配符后方法就会变得简洁一点。

public void addAll(SimpleArrayList<? extends E> c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

其中?表示通配符<? extends E>叫做有限定通配符,可以匹配E或者E的子类。<T extends E>和<? extends E>有什么区别吗?<T extends E>用来定义类型参数,它声明了一个类型参数T;<? extends E>用来实例化类型参数,它用于实例化泛型变量的类型参数,只是这个实例化的类型是未知的。当使用通配符来实例化类型参数时会有一些限制,例如:

public static void main(String[] args) {
       List<?> list = new ArrayList<>();
       list.add("1");
    }

上面的代码会报错,因为使用<?> 和使用<? extends E>来实例化类型参数后集合只能读,不能写。这是因为java无法确保类型的安全性所以只能禁止这样操作。

转载于:https://www.cnblogs.com/suyang-java/p/10740646.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/100971.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • c语言全局变量和局部变量的区别的代码_函数的形参都属于全局变量

    c语言全局变量和局部变量的区别的代码_函数的形参都属于全局变量局部变量和全局变量的区别背景:上午看书阅及这两对概念,有很多相似之处,故记之。一.局部变量&amp;全局变量1局部变量:“在函数内定义的变量”,即在一个函数内部定义的变量,只在本函数范围内有效。2全局变量:“在函数外定义的变量”,即从定义变量的位置到本源文件结束都有效。目的:增加函数间数据联系的渠道。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变…

    2025年8月3日
    3
  • 进程调度原理「建议收藏」

    进程调度原理「建议收藏」 Linux进程调度的目标    1.高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效;    2.加强交互性能:在系统相当的负载下,也要保证系统的响应时间;    3.保证公平和避免饥渴;    4.SMP调度:调度程序必须支持多处理系统;    5.软实时调度:系统必须有效的调用实时进程,但不保证一定满足其要求;Lin…

    2022年9月29日
    5
  • k8s基础知识_lable

    k8s基础知识_lable文章目录第三章 资源管理(续)15 命令式对象管理16 命令式对象配置17 声明式对象配置18 资源管理小结第四章 实战入门19 Namespace4.1.1 **查看**4.1.2 **创建**4.1.3 **删除**4.1.4 **使用配置文件(.yaml)进行管理**20 Pod4.2.1 创建并运行4.2.2 查看pod信息4.2.3 访问Pod4.2.4 删除指定Pod4.2.5 配置操作21 Lable4.3.1 命令方式4.3.2 配置方式22 Deployment4.4.1 命令操作4.4.

    2022年8月11日
    9
  • sklearn库安装_sklearn简介[通俗易懂]

    sklearn库安装_sklearn简介[通俗易懂]Scikitlearn也简称sklearn,是机器学习领域当中最知名的python模块之一。sklearn包含了很多机器学习的方式:Classification分类Regression回归Clustering非监督分类Dimensionalityreduction数据降维ModelSelection模型选择Preprocessing数据与处理使用sklea…

    2022年10月9日
    1
  • dos的批量copy命令

    dos的批量copy命令1、主要是有需求,采取百度的,发现挺好的,还是帮作者推广一下。来自https://www.cnblogs.com/xiykj/archive/2004/01/13/13299548.html“Dos命令复制所有目录下同类型文件”2、需求:想拷贝文件夹内部的同类型文件(比如*.jpg),但是这个jpg文件在不同的文件夹下面,因此copy起来还是不方便,因此需要寻求命令代码解决3、命令是:for/r%iin(.jpg)*docopy“%i”/yd:\img其中,加粗的是变成自己需

    2022年7月18日
    21
  • Qt实现抽奖程序

    Qt实现抽奖程序一、简介该程序命名为Lucky,实现的功能如下:1.加载抽奖人员名单,并保存加载路径;2.单击左键或者点击ctrl+s开始抽奖,并滚动显示人员名单,显示的人员名单格式为部门-姓名。3.

    2022年7月3日
    24

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号