包装类和简单认识泛型
一.包装类
1.基本数据类型对应的包装类:
2.装箱:把基本的数据类型变成引用数据类型(包装类型)。
这个装箱方法已经过时了,就不再使用这个方法来进行装箱了。
现在都是这样装箱了:
第一个装箱的方法称为显示(手动)装箱,第二个装箱的方法称为自动装箱。(需要注意的是,自动装箱的底层调用也是用的valueof来调用的。)
3.拆箱:把包装类型数据变成基本数据类型。
拆箱的两种方式:第一种是自动拆箱,第二种是手动拆箱。(拆箱之前要先装箱)
这里还可以拆箱为其他类型:(包括其他的类型也可以)
二.包装类面试题
1.第一个为true和第二个为false的原因:
因为这个是装箱底层调用的valueof,我们需要看一下valueof底层逻辑:
这里我们看到这里的valueof的底层逻辑,这里是有一个if语句,这个语句里面有一个范围,如果不在这个范围,则会new一个新的对象,所以接下来看一下这个范围。
这里得low是-128
high是127
在valueof的底层逻辑实现中有一个cache的缓存数组,这里存储的就是low到high的闭区间范围的数据:-128~127一共是256个数据。
并且这里仔细的看的话我们知道给一个i值他会加-(-128)那么就是加上了128,那么第一个数据cache[0]=-128,以此类推。
所以上述我们创建了四个Integer我们就看到了第一对是true是因为数据在cache这个缓存中,直接拿就行了,但是200就超出了这个缓存中数据的大小,那么就会new两个不同的对象,所以返回的false。
三.裸类型
1.实现一个类,类中包含一个数组成员,是的数组中可以存放任何数据,也可以根据成员方法返回返回数组中某个下标的值:
2.使用这个类:
3.但是需要强转类型才能接受这个值:(比较麻烦)
并且我们无法知道接受这个值是哪个类型,因为我们需要返回去看这个值的类型才能知道要强转哪种类型,比较麻烦。
所以使用起来并不是很通畅,所以我们就引出了泛型。
四.泛型
1.泛型指可以适用于许多类型。
2.泛型语法:通过这个<T>(代表一个占位符)尖括号,里面可以写任意的大写字母,一般有一些意义,这里就代表当前类是泛型类。
3.泛型类:(需要注意的是,这里并不是把T变成我们所指定的是数据类型)
4.泛型类的使用:这里第二个小括号里面可以不写类型。但是这里的两个小括号都不能省略,并且第一个小括号必须写类类型。
这里存储字符串的时候报错是因为这里尖括号里面是Integer,也就是说明才new对象的时候这里的两个尖括号就是用来指定数据类型的。
这里并没有发生强转,当我们接收这个值的时候。
通过上面的两个图我们总结出在编译时,每次存储数据类型的时候,会检查你存入的数据类型,是不是和你指定的数据类型一样。2.此时就不需要我们强转来接受这个值。这里说的都是编译时候泛型的作用,但是在运行的时候就没有泛型这个概念了。
5.这里不能这样写数组的原因:java中的数组是一种类型,当我们在创建数组的时候数组需要明确的类型,而这里我们并不知道数组的类型是什么。因为T这个泛型的标志只是用来检查这个类型是否为我们所实例化对象的哪个类型,而不是说把类型替换成我们所实例化的类型。
6.这样的写法也不是很好,写出来也是会警告:(在泛型如何编译的中详细讲解)
7.泛型里面尖括号的细节:
8.泛型类不能实例化对象,不知道具体类型就不知道是否存在不带参数的构造方法!
9.小结:
五.泛型的如何编译的
1.在运行的时候代码在JVM中没有泛型的概念。
2.这里的反汇编就可以看出Java泛型是一个编译时机制,而不是运行时机制。
3.这里将所有的T变为Object的机制叫做擦除机制。
4.虽然在编译的时候会把T转换为Object,所以这里接收的数组类型是Object而不是String[]。
但是这里进行强转也不行!虽然Object是所有类的父类,但是Object[]不是所有数组的父类。他只是一个单独的类型。
Object可以是String的父类,但是Object[]不是String[]的父类。所以这里只能用Object[]来定义这个变量:
所以最好不用这种写法:
六.泛型的上界
1.对传入的类型进行约束。
2.语法:class 泛型类名称<类型形参 extends 类型边界> :
3.示例:(这里也接收Number自己)
4.复杂:
这里我们写一个泛型类,找到数组中的最大值:
这里报错的原因是,这两个不是基本数据类型不能用大于小于比较大小,要用compareTo方法。
而要使用compareTo方法就要实现comparable接口,那么这里就需要用到泛型的上界:
这里报错的原因是因为Integer里面实现了Comparable接口,而Person里面没有。
七.泛型方法
1.方法的写法:
2.红色框中的可以不写。
3.也可以变成静态方法:
4.使用的话直接用类名调用:
八.泛型的进阶
1.通配符:我们自己创建了一个泛型类,我们通过set方法来创建变量,最后是通过自己定义了一个方法传入的参数是Message<String> temp,然后在直接通过get方法的返回值直接打印出来,但是当我们传入了Integer类型的数据的时候就没办法通过fun方法通过get的返回值来打印了,因为这个方法的返回值是String,此时就需要使用到通配符,图二:将String改成?
2.通配符的上界:<?extends 类>代表通配符的上界(上界顾名思义就是此时所extends的类就是父类,只能传入比他小的类或者它本身):(途中画蓝色框中就是继承fruit的所有子类和自己,这就是通配符的上界,接收子类的对象需要用父类引用接收实现的对象,因为子类有许多个。)
3.通配符的下界:<?super 类> :代表通配符的下界(下界代表这个类是最小的类,只能传入比他大的类和它自己):(红色框是范围,但是可以在方法里面通过set实例化子类的对象,并且如果需要接收Fruit的父类对象就需要进行类型的强转)