Java泛型机制详解;这些你都知道吗,如何用一段代码证明JVM加载类是懒加载模式【附源码】_程序员lqs

    } } return max;

}

 max是泛型类型T的数组的对应下标的值,不过这么编写代码的话,会被编译器警告,因为Comparable接口本身也是个泛型接口,所以我们写的时候建议也去指定Comparable接口的泛型上界,修改如下:  ```java public static <T extends Comparable<T>> T max(T[] arr){ ................... }

此种方式可以实现泛型类型的递归类型限制传递

上界为具体类

还记得我们上面的实例Pair类使用的泛型类型,我们可以实现一个子类:

public class NumberPair<U extends Number, V extends Number> extends Pair<U, V> {     public NumberPair(U first, V second) {         super(first, second);     } }   

当我们限制了对应的类型范围后,我们就可以把first和second变量作为Number类型进行处理了,比如我们内部有一个求和的方法:

public double plus(){     return getFirst().doubleValue() + getSecond().doubleValue(); }

所以当我们定义完后,我们的使用即为如下这样:

NumberPair<Integer, Double> pair = new NumberPair<>(10, 12.34); double sum = pair.plus();

可以看出来,限制了泛型类型范围后,编译器检查的会更严格,如果类型不对直接会报错,并且泛型擦除的时候转换的类型则为指定的范围上界的类型

泛型的通配符

上面我们提到了一些例子,就是使用了参数类型作为范围上界,但是这种写法比较繁琐,有木有更简化的写法呢?当然有,泛型支持通配符形式,可以简化范围上界的泛型写法,一个简单的通配符泛型如下:

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

可以看到当前的写法中c的类型是DynamicArray<? extends E>类型,?表示通配符,<?extends E>表示有限定通配符,具体需要匹配泛型E或者E的子类型即可,至于具体是什么类型,完全可以未知,当我们使用的时候,代码如下:

DynamicArray<Number> numbers = new DynamicArray<>(); DynamicArray<Integer> ints = new DynamicArray<>(); ints.add(100); ints.add(34); numbers.addAll(ints);

这里E是Number类型的时候,?可以匹配为DynamicArray<Integer>,那么通配符和范围上界指定的效果一样,这两者有什么区别呢?

1.<T extends E>写法仅限于用于定义类型参数,申明了一个类型参数T(使用的时候必须指定泛型类型)

2.<? extends E>用于实例化类型参数,可以用于实例化泛型变量中的类型参数,只是当前类型可以是未知的,只需要知道范围上限,即属于泛型E的子类即可(使用的时候可以不指定泛型类型,或者直接传递子类类型即可)

那么我们什么时候使用通配符,什么时候需要定义类型参数范围呢?首先我们先来认知下通配符分类以及各类通配符的用法

无限定通配符

在泛型中,除了上述的有限定通配符以外,还有无限定通配符超类型通配符,我们首先来了解无限定通配符,使用无限定通配符实现一个简单的DynamicArray中查找元素,代码如下:

public static int indexOf(DynamicArray<?> arr, Object elm){     for(int i=0; i<arr.size(); i++){         if(arr.get(i).equals(elm)){             return i;         }     }     return -1; }

可以看到上述的泛型即使用了无限定通配符,当然此通配符也可以使用泛型类型T来代替,效果是相同的,不过无限定通配符使用起来更简洁,当然无论是上述的哪一种通配符,都有一个限制–只能读,不可以写入,我们先看例子:

DynamicArray<Integer> ints = new DynamicArray<>(); DynamicArray<? extends Number> numbers = ints; Integer a = 200; numbers.add(a); //代码错误,不允许添加 numbers.add((Number)a); //代码错误,不允许添加 numbers.add((Object)a); //代码错误,不允许添加

可以看到这三种方法,都尝试在泛型类型未确定的时候尝试插入操作,无一例外都失败了,这里就是Java对与泛型的类型检查的优化,无论是?通配符,还是<? extends E>方式的泛型,这里的泛型类型都是不确定的,所以允许插入后就会有类型安全的问题。当然除了这一点以外,如果返回值依赖某个引用类型参数,也不允许使用通配符,如下:

public static <T extends Comparable<T>> T max(DynamicArray<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; }

这里的代码如果使用通配符,就会出现意想不到的问题,所以也无法使用通配符操作,从上面我么可以总结出,无限定通配符和泛型类型参数的关系,如下:

1.无限定通配符能修饰的泛型,都可以使用泛型类型参数的方式替换

2.通配符可以减少泛型类型参数,代码更简洁,可读性更好

3.如果类型参数之间有依赖关系,或者返回值依赖于传递的类型参数,这里只能使用泛型类型参数

超类型通配符

上面我们知道可以在泛型中存在继承关系,所以我们可以指定泛型的父类上界,也可以使用有限定通配符,一定程度上可以实现我们开发的灵活简化,但是也存在这样的场景,比如我们知道某一个具体的子类实现,但是我们希望无论是哪一级的父类型都可以作为通用的操作,这个时候我们不确定类型的就是超类了,还能使用泛型吗?答案是能,泛型在Java1.6中加入了超类型通配符操作,形式为<? super E>,首先我们先看没有超类型通配符的一个简单场景,如下:

//定义了一个copy方法 public void copyTo(DynamicArray<E> dest){     for(int i=0; i<size; i++){         dest.add(get(i));     } }   

我们想要做的操作很简单,只要将当前容器的元素传递如对应的容器中,这个时候我们可能希望这么使用:

DynamicArray<Integer> ints = new DynamicArray<Integer>(); ints.add(100); ints.add(34); //构建一个Number父类型的动态数组 DynamicArray<Number> numbers = new DynamicArray<Number>(); ints.copyTo(numbers);

按照java特性来说,Integer是Number的子类型,将Integer的实例数组对象copy进入父类型的数组中是完全合理的,但是由于这里使用了泛型,指定的类型参数不一致,导致java编译器会提示编译错误,但是我们使用了超类型通配符以后,问题迎仍而解,如下:

public void copyTo(DynamicArray<? super E> dest){     for(int i=0; i<size; i++){         dest.add(get(i));     } }

通配符比较

现在我们对三种通配符都有了一定的了解了,将三种通配符进行比较和总结如下:

1.三种通配符存在的意义都是为了使得java动态代码更加灵活,可以接受更广泛的类型

2.<? super E>通配符方式更适合灵活写入的场景,使得java编译器不会捕捉子类型写入父类型的容器的错误,并且不能被泛型参数类型的方式替换

3.<?>和<? extends E>方式更适合用于灵活的读取,使得代码可以读取E和任何子类的对象,这里的通配符操作和泛型类型参数操作完全等同,可以互相替换

泛型使用的细节与注意点

学习到这里,可能了解了泛型的原理,其实就是通过java的类型擦除的特性实现的,实际编译的时候还是会转换为Object类型或者限定的父类型,但是使用泛型并不是任何场景都适用的,下面我们来罗列一下泛型的细节与使用的局限性:

使用泛型类、接口和泛型方法时

需要注意:

1.基本类型不能作为实例化类型参数,应使用其包装类型

2.运行时类型信息不适用于泛型,例如"string".class这种不被允许作为泛型实例传递

3.类型擦除也可能会出现一些冲突,例如父类型实现某个接口,父类型作为泛型的时候,子类如果想实现父类接口的某个方法,重新实现该接口就会出现错误

Ending

Tip:由于文章篇幅有限制,下面还有20个关于MySQL的问题,我都复盘整理成一份pdf文档了,后面的内容我就把剩下的问题的目录展示给大家看一下

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】

如果觉得有帮助不妨【转发+点赞+关注】支持我,后续会为大家带来更多的技术类文章以及学习类文章!(阿里对MySQL底层实现以及索引实现问的很多)

Java泛型机制详解;这些你都知道吗,如何用一段代码证明JVM加载类是懒加载模式【附源码】_程序员lqs

Java泛型机制详解;这些你都知道吗,如何用一段代码证明JVM加载类是懒加载模式【附源码】_程序员lqs

吃透后这份pdf,你同样可以跟面试官侃侃而谈MySQL。其实像阿里p7岗位的需求也没那么难(但也不简单),扎实的Java基础+无短板知识面+对某几个开源技术有深度学习+阅读过源码+算法刷题,这一套下来p7岗差不多没什么问题,还是希望大家都能拿到高薪offer吧。

本站由小牛团队全力维护,小牛十年了,大家已经步入中年 。本站源码全部经过团队成员测试并调试,价格可能比其它网站略贵几元钱,不解释!
小牛资源 » Java泛型机制详解;这些你都知道吗,如何用一段代码证明JVM加载类是懒加载模式【附源码】_程序员lqs

发表评论

全站资源亲测可用,价格略高几元,不解释

立即查看 了解详情