包装类 or 泛型

前言



前言:上文我们了解了接下来学习的路线, 明白了如何计算时间复杂度和空间复杂度, 最后还留下来 两个题目, 这里就先来解答上文留下来的两个题目, 在来学习本文介绍的泛型。

题目一:消失的数字

解法:

在这里插入图片描述


题目二:189. 轮转数组 - 力扣(LeetCode)

方法一:直接按照题意

在这里插入图片描述

如过我们 数据量不是像力扣这样 给你一坨我们 是可以这样写的。


方法二: 三步反转发法:

在这里插入图片描述

好 了 上文留的题目完成了 我们来 学习一下本文内容 泛型


包装类


再学习泛型前我们先来学习一下包装类。


啥是包装类呢?

包装类(Wrapper Class): Java是一个面向对象的编程语言,但是Java中的八种基本数据类型却是不面向对象的,为了使用方便和解决这个不足,在设计类

时为每个基本数据类型设计了一个对应的类进行代表,这样八种基本数据类型对应的类统称为包装类(Wrapper Class),包装类均位于java.lang包。

同时没有包装类 我们接下来学习的 泛型 , 对于基础类型 也是不能够使用的 。

基本数据类型对应的包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

观察上面的表格 : 我们能很清楚的知道他们的对应关系, 细心的同学肯定发现了, 除了 `int` 和 `char` 其他都是首字母大小即可,所以我们可以着重记忆下这两个即可

包装类存在的意义:

  • 当我们需要将某种类型的数据转换成其它的数据类型时,需要通过某种来达到目的。
  • 而包装类就是这些功能的集大成者,包含多种类型转换方法和其他功能。

演示: 将字符串 转化为 整形数据

在这里插入图片描述

除了上面这两个方法, 我们的包装类还有很多方法, 如果感兴趣可以 通过Alt + 7 去查看, 或者百度查找也是ok的。

在这里插入图片描述

拆箱和 装箱


先来看一段代码

在这里插入图片描述

有没有疑问 不是说我们的 Integer 不是一个类吗 为啥 不通过 new 来创建 而是 直接 给了 一个 整形数据, 另外 还有一个疑问, a 明明 是一个对象,为啥能直接赋值给 b 这个整形数据类型 ?

小小的疑惑大大的脑袋, 其实 这里跟我们下面要将的 装箱和拆箱有关 (这里也可以 称为 装包 和 拆包)


先看概念:

装箱 / 装包 :把简单类型数据 变为 包装类类型数据

拆箱 / 拆包 : 把包装类类型数据 变为 简单类型数据

知道了 概念 , 我们就可以解释上面的 代码 了

在这里插入图片描述

另外 我们 也可通过 反汇编来 看底层是如何实现的

第一步: 找到我们的 字节码文件 ( 再此之前 Build 一下, 或者 运行一下,产生字节码文件)

在这里插入图片描述

第二步: 找到我们的 字节码文件(.class 后缀的 )
在这里插入图片描述

第三步 : 输入 javap -c 查看的类名
在这里插入图片描述

第四步: 观察字节码文件 装箱 过程 (装箱:把简单类型数据 变为 包装类类型数据 )
在这里插入图片描述

下面就来看一下拆箱(把包装类类型数据 变为 简单类型数据):这里 前面都差不多就直接来看 字节码文件

在这里插入图片描述

学完了装箱 和 拆箱 , 这里 来 问一个问题 : 请问下面输出什么(注: 阿里的面试题)

在这里插入图片描述


A: true, true B: false , false , C true , false


答案: C

解释: 这里我们想要回答正确 ,就需要 去观察 源码, 这里我们 装包是会调用 valueOf ,这里我们就点这个方法看源码 。
在这里插入图片描述


泛型


注意:泛型的算 java 里面 比较难的语法,这里我们的学习目标只需要能看懂java集合源码即可

什么是泛型?

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。

泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。

看完上面引用是不是非常蒙,这里是讲了啥,不要紧下面就通过 代码来引出我们的 泛型

引出泛型


先来看一个问题 : 实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?

补充: object类是所有类的父类。

根据上面的补充,那么我们能不能 new 一个 object数组来存放我们的数据呢?

这里就来演示:
在这里插入图片描述

可以 看到我们的 object 数组存放了我们的 字符串类型数据,整形数据, 浮点类型数据, 还有我们的自定义数据,是不是非常方便呀, 啥都能存。

但这里 是有问题的, 啥问题呢?


这里我们来取数据就能够知道了,开始演示:


因为我们是 object 数组, 那么返回的元素 也应该是 一个 object 类型的 元素, 这里就需要使用 我们的 object来接收。

在这里插入图片描述

在这里插入图片描述


可以发现 这里就拿出来我们的数据, 但是有一个问题, 我们使用 object 这个类型来接收,那么能不能使用我们存放的类型呢?


演示:

在这里插入图片描述


发现这里爆红线了, 发现不能, 其实是可以的这里就需要强转 (父类对象给子类引用【Obejct 是 所有类的 父类】,需要进行强转, 向下转型)。

在这里插入图片描述


此时我们就 拿到了我们的数据, 可以发现这样是非常麻烦的,如果采用这样存储数据,不仅需要对这个数组存放的数据非常了解, 还要对取出来的数据进行强转,是真的麻烦, 那么有什么方法,能解决呢?

这里就需要我们的 泛型。

根据上面我们可以先来 总结出使用 Object 的缺点


使用Object 的 缺点:

  • 在存放元素任何类型的数据都能存放 (太过于随便,就好比公交车谁都可以上,肯定没有私家车好坐)
  • 取出元素的时候你得自己判断(也就是说你需要强转)


这里我们的Obejct 不能将类型参数化导致什么类型都能放入Object数组中.


这里我们的泛型就能让我们传一个类型。

在这里插入图片描述


这里我们的 T相当于一个占位符,(这里 E F G H I K 等等都可以, 名字而已) 。

常用的名称

  • E 表示 Element

  • K 表示 Key

  • V 表示 Value

  • N 表示 Number

  • T 表示 Type

  • S, U, V 等等 - 第二、第三、第四个类型

<T> 就表示当前的类是泛型类

下面就来改我们的 代码

在这里插入图片描述


这里发现我们的 T 报错了, 这里的原因比较复杂,这里先不解释后面在解释。


这里我们先写成这样, 但是这样写也不太好。

在这里插入图片描述


使用:类我们创建好了,这里我们就来使用

在这里插入图片描述


这里我们指定 当前 MyArray ,接收的类型为 Integer(我们在尖括号里面传的是Integer类型),这里就我们 就只能存放整形数据(这里就会类型检查,如果不是Integer 就会报错), 如果看不懂Integer 没有关系,后面会讲到包装类, 这里就 将 Integer 当作 int 的 升级版即可。

在这里插入图片描述


此时我们 拿数据 就不需要想 上面一样需要强转了。


下面我们就在存放我们的字符串数据。

在这里插入图片描述


通过上面这里就能得出 泛型存在的意义

1.存放元素的时候,会进行类型的检查。
2.取出元素的时候,会自动帮你进行类型转换 (不需要在进行类型的强转了)。


注意: 上面这两步都是在编译的时候完成的,运行时就没有泛型的概念。


正因为 泛型主要是编译时期的一种机制,这种机制会有一种概念叫做 擦除机制。


看到这里, 我们就基本能够看的懂 后面java 集合源码。


上面展示完泛型, 这里我们 来看一下语法(其实就是上面的)。

泛型语法

class 泛型类名称<类型形参列表> {
  // 这里可以使用类型参数
}

一个泛型的参数列表可以指定多个类型
class ClassName<T1, T2, ..., Tn> { 
}


class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
  // 这里可以使用类型参数
}


class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
  // 可以只使用部分类型参数
}


一个泛型的参数列表可以指定多个类型:

在这里插入图片描述


下面继续 、

裸类型(Raw Type)

作为一个刚刚知道如何定义泛型的小白玩家,肯定会有下面的操作


高高兴兴的写好泛型类:

在这里插入图片描述


使用泛型类,没有带<>
在这里插入图片描述


如果此时取数据,发现是需要强转的。

在这里插入图片描述


如果我们 这里再加上一些其他的类型。

在这里插入图片描述


此时 就会发现我靠这里回到了使用Object , 发现啥类型都能存放,取数据也是需要强转。


此时这种情况就好比我们幸幸苦苦的做好了一个工具,然而我们需要使用结果脑袋一抽忘记了使用这个工具导致我们需要完成很多步多余的工作。

这里没有加<> 就称为裸类型 (Raw Type)


大家想一想为啥我们没有加上<> ,编译器没有报错呢?

这里是因为 我们的泛型是在 1.5之后引入的一种的新语法,如果我们这里报错,那么之前版本就不能使用吗?

这里我们就需要考虑兼容性问题, 所以这里就没有严格的限制(编译器没有报错)。

注意
  我们 不要自己去使用裸类型,裸类型是兼容老版本的API保留机制


这里我们可以小结一下泛型:

小结:

1.泛型是将数据类型参数化,进行传递2.使用 表示当前类是一个泛型类。3.泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
# 泛型如何编译的   到这里有没有 疑问 泛型这个语法 在编译的时候底层是咋做的?   刚刚我们说过 泛型主要是编译时期的一种机制,这种机制会有一种概念叫做 擦除机制。   那么啥是擦除机制呢?   这里我们就来学习一下擦除机制

擦除机制


这里主要看我 演示 即可 :

在 idea 中有一个 插件

在这里插入图片描述

这里我们 通过 这个插件可以观察我们的 .class文件 ,这里就通过这个插件进行演示, 如果感兴趣可以 自行了解。

在这里插入图片描述

这里我们 看到 我们的方法,点击 setObjects ,这里 I 代表是 int ,后面 这个Object 就是我们的 T类型。

在这里插入图片描述


在来看一下 getobjects 这个方法

在这里插入图片描述

(I) 代表 括号里的参数类型是 int , <()java/lang/Object> 代表返回值的类型是Object


这我们就能发现 我们的 T 在编译的时候都变成了Object ,这里就是擦除机制在编译的时候 将T擦成 Object类型。


那么这里 有一个问题:我们在编译的时候将 T 全部擦成Object,那么为啥我们 还只能存放 我们指定的类型呢?

答案: 上面我们不是说过 使用泛型语法会在编译时自动进行类型检查和转换,那么就会通过我们指定的类型,对我们传入的参数进行检查如果不符合就报错。


这里如果想要更深层次的 学习擦除机制这里有一篇文章可以学习一下:Java泛型擦除机制


这里我们讲完了擦除机制, 来解答一下上面留下来的问题 。

这里按照我们的 擦除机制, T 会擦成 Object 那么我们 new 一个Object 为啥不行呢?

在这里插入图片描述


这里如果我们 这样写 就会 回到我们 最初的问题,这样写数组什么元素都能存放没有限制, 而且我们需要对存入的数据十分了解,才能拿到数据,而且每次拿数据都需要强转非常麻烦。


这里我们继续来看

假设这样创建能够创建数组,那么我们是不是可以创建返回一个T[]类型的方法。

这里 public T[] objects = new T[10] ; 在编译器上报错, 我们拿下面这种创建方式来演示,返回T[] 数组

(注意这里我们是假设 T[] obkects = new T[] 能够创建数组)

在这里插入图片描述


使用:返回 一个Integer 数组, 使用 Integer 数组接收

在这里插入图片描述


这里发现没有报错, 这里运行一下。(这里没有报错的原因是 我们的指定的类型是Integer, 泛型会进行类型检查,认为 getT 返回的类型会是 Integer[] 类型, 所以这里就不会报错)

在这里插入图片描述


发现报错了,这里的错误原因就是我们的 擦除机制会将T类型 擦除成 Object 类型, 返回的类型就是 Object 那么我们使用 Integer 数组接收是不是就 有问题。


最后我们还说过 : public T[] objects = (T[])new Object[10];也不太好

主要问题 也是一样的 数组的元素 也有可能存储不同类型的元素。

这里我们要 创建数组 最正确的写法是使用 反射,反射是什么 ,不要急这里先埋下一个坑,等后面 会讲到的。

这里我们只需要了解一下如何使用反射来创建这个数组即可。
在这里插入图片描述


下面继续 : 这里通过一个题目引出我们接下来学习的知识点。


题目: 写一个泛型类,类中有一个方法,求一个数组当中的最大值。


这里看到题目是不是 , 都笑出了声,这不贼简单,直接比较不就好了,那么我们就来写代码。
在这里插入图片描述

发现这里 报错了, 眉头一紧 ,我靠为啥这里会报错。

想一想:

第一: 这里我们传入的类型是不是未知的我们如果我们就这样使用 < > = 这些符号比较是不是就 非常草率了,


这里补充一个细节: 我们的 泛型 尖括号内部 必须是引用类型。 如:Integer, String , Double 这些都是后面要讲 的 包装类型(包装类型是一个引用类型)。

第二: 根据上面的补充我们能知道 我传入的类型是 引用类型,那么我们的引用类型能 通过< = > 直接比较吗?

(之前讲过的对象比较需要实现Comparator 或者 Comparable 接口,通过 compareTo 方法 来比较。,注意: equals 在这种情况下是不可以的,因为 equals 是 比较是否相等的, 返回的 是 true 和 false)

如果不懂可以看看下面的文章

三种常用接口的传送入口

当我们想要通过 compareTo方法进行比较的时候,发现这并没有与 ComparableComparator 相关的功能。

在这里插入图片描述


解释: 为啥没有compareTo这个方法 , 是因为我们的 T 在编译的时候都擦除成 Object类,这里我们进入源码一看会发现我们的Object类是没有实现我们的Comparable 接口的,所以他是不会具备我们的 compareTo方法的

在这里插入图片描述

那么此时需要如何解决呢?

来看
在这里插入图片描述

这里我们就不报错了,为啥要这样呢?

不要急慢慢来,保持一个学者的心。

泛型的上界


这里 就是泛型的上界 --》 T一定是实现了这个接口的。
在这里插入图片描述

先来使用一下这个 Alg类
在这里插入图片描述


下面举个反例

自定义一个类 Person , 传入 Alg 这个类中,此时你会发现报错了。

在这里插入图片描述



这里我们想要不报错 ,这里需要让 Person 实现 Comparable这个接口然后重写compareTo这个方法
在这里插入图片描述


可以看到这里不报错了。

下面通过 Alg 来 找到我们的 People 数组中的最大值。

在这里插入图片描述


这里有一个小细节: 我们的 包装类 都实现了 Comparable 重写了compareTo方法。
在这里插入图片描述

这里就不在多讲了, 回到我们的泛型上界

在这里插入图片描述


这里是特殊的一个上界,这里我们 T的上界是一个接口, 这个T一定是实现这个接口的。


下面这里就来看一下更简单的上界

在这里插入图片描述


这里的 E 一定是 Number的子类或 Number 本身。

通过 java的帮助手册查询 Num的子类
在这里插入图片描述


演示:

在这里插入图片描述


那么 这里 与 没有加上 extends Number有啥区别呢?

在这里插入图片描述

再来看:

在这里插入图片描述

上界看完是不是有预言家 预言我们接下来要学习泛型的下界,抱歉这里我们的泛型只有上界没有下届的哦。

接下我们来学习一下泛型的方法

泛型的方法

定义语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }


非静态: 就那刚刚的题目来举例。

在这里插入图片描述


这里就是一个非静态的泛型方法。

静态方法

  想一想我们每次 想要通过 `findMax` 来 调用 这个找最大值的方法,每次都需要`new 一个 Alg`对象是不是感觉有点麻烦,那么有什么办法呢不new这个对象呢?

动动我们聪明的小脑袋瓜想一想, 是否能想到 static 呢?

在类和对象那里我们 讲过 static 他表示静态的, 这里被static修饰的方法 是存放在方法去的, 不需要 new 对象就能够调用 ,那么我们是不是就可以将我们的 findMax方法写成我们的静态方法从而直接通过类名进行调用。

开始尝试: 发现加上 static 报错了

在这里插入图片描述

 

有问题多思考才能进步是不是, 那么这里我们在来想一想 。

那么这里没有加上 <T> , 那么 T 不就更确定不了了吗?
在这里插入图片描述


这里我们就需要对上面的代码进行修改、


这里就没有报错了,这里我们的方法是静态的必须要在 static 加上一个<T> (这里可以指定泛型上界)


这里来使用一下:注意这里我们不是没有传入参数类型,这里会省略掉<Integer> , 它会根据我们传入数组的类型来推到我们传入的类型。

在这里插入图片描述


看完泛型的方法, 我们来学习一下通配符

通配符 : ?

先来看一段代码

class Message<T>{
    private T message;
    public T getMessage(){
        return message;
    }

    public void setMessage(T message) {
        this.message = message;
    }
}
public class Main {

    public static void main(String[] args) {
        Message<String> message = new Message<>();
        message.setMessage("小葵花幼儿园欢迎你");
        fun(message);
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}


这里我们指定 传入Message这个类的参数类型是 String , 然后通过 fun 打印 我们传入的参数 小葵花幼儿园欢迎你。

(这个代码是不是非常简单, 下面我们改变一下)。

Message 传入的参数类型改为 Integer

在这里插入图片描述


此时发现报错了,我们不能使用 这个fun 方法,理由很简单,我们fun 的 Message的类型是 String , 这里我们指定的类型是Integer ,这里的类型就不匹配。
在这里插入图片描述

如果我们 再写一个 fun 函数, 就会 感觉差点意思,如果我们传入的 是Double 这个类型呢? 你再写一个吗 ,这就非常的 憨憨了。


细节扩展:

在这里插入图片描述


这里我们 将 Message<String> 改为 我们的代码就可以运行了
在这里插入图片描述


运行:

在这里插入图片描述


这里我们通配符主要解决的问题:

通配符是用来解决泛型无法协变的问题。

协变 指的就是如果 Student 是 Person 的子类,那么List 也应该是 List的子类。

但是泛型是不支持这样样子父子类关系的。

1、泛型 T 是指定的类型,一旦你传给了我就定下了。而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围。

2、或者我们可以这样理解:泛型 T 就是一个变量,等着你将来传给它一个具体的类型;而通配符则是一种规定:规定你只传某一个范围的参数类型。【比如说整形 short、int 都是整形范围里的类型】


下面继续:

统配符的上界


语法:

<? extends 上界>
    
<? extends Number>//可以传入的实参类型是Number或者Number的子类


根据下面这个图 来举例子:

在这里插入图片描述

class Food {
}

class Fruit extends Food {
}

class Apple extends Fruit {
}

class Banana extends Fruit {
}

class plate<T> { // 设置泛型上限
    private T plate;

    public T getPlate() {
        return plate;
    }

    public void setPlate(T plate) {
        this.plate = plate;
    }
}

public class Test {
    public static void main(String[] args) {
        plate<Apple> plate1 = new plate<>();
        plate<Banana> plate2 = new plate<>();
        fun(plate1);

        plate1.setPlate(new Apple());
        plate2.setPlate(new Banana());
        fun(plate2);
    }
    public static  void fun(plate<? extends Fruit> temp){

    }

}


请问: 能不能 再 temp(盘子) 放 东西

在这里插入图片描述

尝试放苹果 和 香蕉
在这里插入图片描述

这里都报错了, 此时都不可以 ,站在 temp 的角度 来看, 它不知道是接收 Apple 还是 Banana , 一般对于 统配符的上界来说存放数据一般是不会成功的, 它 不能具体的知道你要存放的类 , 这里 一般用于取数据, 这里我们取出的数据都是 Fruit的子类或 Fruit (这里是上面这个代码的 Fruit)

在这里插入图片描述


补充: 往 tmp里面 添加 一个 Fruit类

在这里插入图片描述


这里同样是不行的, 我们 new 一个 Fruit , 假设我们 传入 fun的 参数 类型是 Apple (temp 的参数类型 是 Apple) 那么 new Fruit 相当于 向下转型 ,之前文章说过, 向下转型本身 就不是一个安全的操作 。

统配符的下界

语法:

<? super 下界>

<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型


根据下面这个图 来举例子:

在这里插入图片描述

class Food {
}

class Fruit extends Food {
}

class Apple extends Fruit {
}

class Banana extends Fruit {
}

class plate<T> { // 设置泛型上限
    private T plate;

    public T getPlate() {
        return plate;
    }

    public void setPlate(T plate) {
        this.plate = plate;
    }
}

public class Test {

    public static void main(String[] args) {
        plate<Fruit> plate1 = new plate<>();
        plate1.setPlate(new Fruit());
        fun(plate1);

        plate<Food> plate2 = new plate<>();
        plate2.setPlate(new Food());
        fun(plate2);
    }
    public static  void fun(plate<? super Fruit> temp){

    }


尝试 : 传入 Fruit 的子类

在这里插入图片描述

上面我们 想要 再 temp 里面放东西,发现因为 统配符上界 ,temp 不知道我们传入的 参数类型是什么 , 不让我们放元素,这里我们 通配符的下界 会不会一样影响呢?


演示:

在这里插入图片描述

这里我们想要读一个数据是不能的 , 还是那个原因, 我们不能够知道是使用啥来接收。

泛型的学习就到这里了, 为主要了解即可, 重点能看懂后面的源码即可。

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值