目录
一、包装类
1.1 基本数据类型对应的包装类
除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。
1.2 装箱 和 拆箱
【注意】装箱 和 拆箱 仅指基本数据类型 和 包装类类型之间的转化,与引用数据类型等无关
1. 把 基本数据类型 转换成 对应的类类型叫作装箱:
【手动装箱】:对应类类型.valueOf()。
【自动装箱】:直接将 基本数据类型 的值 赋值给 对应的类类型。
【补充】通过反汇编可以发现:手动装箱 和 自动装箱底层都是使用的手动装箱的原理。
【注意】采用 new 包装类() 的方式进行装箱已经过时。
2. 把 包装类类型 转换成 基本数据类型(并不局限于一 一对应) 叫作拆箱:
【手动拆箱】:包装类类型的变量.基本数据类型Value()。
【自动拆箱】:直接将 包装类类型的变量 赋值给 基本数据类型的变量。
【补充】通过反汇编可以发现:手动拆箱 和 自动拆箱底层都是使用的手动拆箱的原理。
3. 【面试题】:下图代码的输出,为什么是 true 和 false?
【分析】:很显然造成比较结果不相等的原因只可能是装箱时使用的Integer.valueOf()内部的原因。我们观察Integer.valueOf()内部的代码可以发现它的逻辑是,如果i在-128~127之间则返回cache(缓存的意思)数组中下标为[i + (-IntegerCache.low)]的值 (-128在下标为0的位置),反之则会返回new的一个新的Integer对象。因此由于100在范围之间所以变量a、b指向的同一个对象,200不再范围之间所以变量c、d指向的不是同一个对象。
二、泛型
2.1 怎么用的
1. 实现了把类类型作为参数传递的效果,编译时会自动进行类型检查和转换。
2. 如果你把一个类定义成泛型类,那么在这个泛型类中,它的成员方法的参数,返回类型都可以用泛型的记号指代。在实例化泛型类的对象时给泛型标记明确的类型A,此时该泛型类有使用泛型标记的方法中泛型标记处都将表示为类型A(不过并不能理解成类型A替换了泛型标记,而是编译器在编译时会检查泛型标记处是否传递的是类型A类型的参数,运行时并没有泛型的概念)
3. 【场景举例】:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据(定义成Object类型),也可以根据成员方法返回数组中某个下标的值?
【代码改良】:改良后的代码可以实现给泛型类的泛型标记指定类型A,在泛型类的方法中有使用到泛型标记的地方都得满足参数/返回类型满足类型A。
下图代码中我们把MyArray类定义成了泛型类,并将里面的某些方法的某些参数/返回类型部分修改成了泛型标记,在main方法中初始化泛型类的对象后,我们调用泛型类的相关方法时如果方法的参数指定了是泛型的则就不能随便传参数了,下图代码中相当于将myArray数组定义成了Integer类型的。
【注意一下】:如果一个类是泛型类,例如:Dog 和 Dog<xxx>会认为是不同的类型。
2.2 定义语法
1. 类名后的<T>代表占位符,表示当前类是一个泛型类。
【规范】类型形参一般使用一个大写字母表示,常用的名称有:
2. 实例化一个泛型类对象的格式:
上图中的省略写法编译器会自动推导。
【注意】:在实例化一个泛型类对象时,<>只能写类类型,基本数据类型不能写(写成对象的包装类)。
2.3 裸类型(了解即可)
1. 指的一个泛型类在实例化对象时没有写<>,此时被称为裸类型。
这样写编译器并不会报错,因为裸类型是为了兼容老版本的 API 保留的机制,我们平常写时不要写成裸类型。
2.4 编译器如何编译的泛型
1. 在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
2. Java的擦除机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
【提出问题,关于与泛型类有关的数组如何定义的问题】:
1、那为什么,在泛型类中定义成T[] ts = new T[5];这样的数组是不对的,编译的时候通过擦除机制将T替换为Object,不是相当于:Object[] ts = new Object[5]吗?
答:在定义数组时一定要明确指定数组的类型;泛型标记只起检查作用,并不会去替换,例如,在实例化化一个对象后,虽然知道泛型标记指定的是Integer,但泛型类中有T的地方并没有被替换成Integer,泛型类中的所有T仍然是T;且定义数组在擦除机制之前,所以在定义的时候我们仍然是没有明确数组的类型的。
【注意】:Object[]是一种单独的类型,它不是所有数组的父类。
2、如果我们定义成 public T[] myArray3 = (T[])new Object[10];虽然编译器不会明确报错,但使用时也会存在一些问题:
我们在原本的方法中添加一个新方法,返回myArray3的地址
在main方法中运行下列代码,虽然编译时没报错,但运行时会报类型转换异常
即使类型转换后也会运行报错,反而我们就写成 private Object[] myArray = new Object[10];并不会出现此现象。
因此这种在泛型类中定义数组的方式也不可取。
2、类型擦除,一定是把T变成Object吗?
2.5 泛型的上界
1. 在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
2. 语法格式:
下图实例中,表示在实例化泛型类时<>只能接收Number类或Number类的子类。
如果没有指定类型边界,可以视为 E extends Object。
3. 复杂定义写法示例:E的边界也是一个泛型类类型,下图代码表示<>中只接收有实现Comparable接口的类类型。
4. 【使用举例】:
2.6 泛型方法
当一个类不是泛型类,而这个类中的方法想使用泛型,那你就将方法写成泛型的,定义的方法和泛型类一样,格式是,在方法的返回类型前面,修饰符后 写<T1,T2,T3,,,> 或 <T1 extends XXX>,在调用该方法时,想平时一样调用即可,此时编译器会根据形参来推导泛型标签是什么类型的,如果你想明确写也行,在方法名的前面写上<xx>即可,具体使用见下图示例。
本篇已完结 ......