1. 为什么要有泛型
我们先用数组实现一个简易版MyArrar,里面存放int类型的元素
public class MyArray { int[] array; int size; //数组中有效元素的个数 public MyArray(int initCapacity){ if(initCapacity <= 0){ initCapacity = 10; } array = new int[initCapacity]; } //尾插 public void add(int e){ if(size >= array.length){ System.out.println("MyArray已经存满了"); return; } array[size] = e; size++; } //获取index位置上的元素 public int get(int index){ if(index<0 || index>=size){ throw new ArrayIndexOutOfBoundsException("数组下标越界"); } return array[index]; } public static void main(String[] args) { MyArray myArray = new MyArray(5); myArray.add(1); myArray.add(2); } }
但是我们不想在往MyArray中添加int类型的元素,会怎么办?
再创建一个类?没有体现代码的复用性,太麻烦
使用Object类型来接收数据?可以 ,我们先创建MyArray,在创建一个Student类和一个Person类,并且添加一些数据,看看使用的时候会暴漏哪些问题?
class Person{ int age; String name; } class Student{ String name; int age; } public class MyArray { Object[] array; int size; public MyArray(int initCapacity){ if(initCapacity <= 0){ initCapacity = 10; } array = new Object[initCapacity]; } public void add(Object e){ if(size >= array.length){ System.out.println("MyArray已经存满了"); return; } array[size] = e; size++; } public Object get(int index){ if(index<0 || index>=size){ throw new ArrayIndexOutOfBoundsException("数组下标越界"); } return array[index]; } public static void main(String[] args) { MyArray myArray = new MyArray(10); myArray.add(new Person()); myArray.add(new Person()); myArray.add(new Student()); myArray.add(new Student()); } }
我想得到一个Person对象
发现会报错,思考一下为什么?
因为MyArray存放的是Object,Object是所有类的父类,所以这里存在向下转型,向下转型是不安全的,所以我们必须强转一下
这个时候这个问题就解决了
但是这种时候会出现一另个问题:
观察一下,发现明明是Person类型,为什么用Student类型接收,编译器不报错?
我们可以运行一下看看结果如何:
ClassCastException为类型转换异常,是一种运行时错误,所以在编译期不会报错,会完成编译,但是运行时就会报错
为什么会出现这种错误?
因为MyArray里面存的东西我们一般也不清楚是什么类型,所以在接收的时候,无法选择正确的类型来接收,所以会导致出现这种错误
所以对于上面问题,使用Object类型来接收可以实现,但不可取,因为会出现向下转型,和类型转换错误,导致我们写起来很麻烦
这个时候,泛型就出现了,使用泛型可以很好的解决以上问题
2. 认识泛型
泛型指的是类型参数化,即在写代码时将类型设置一个特定格式的参数,这个参数可以指定不同的类型来控制形参具体限制的类型
使用泛型对上述代码改造体会泛型的用法
public class MyArray<T> { T[] array; int size; public MyArray(int initCapacity){ if(initCapacity <= 0){ initCapacity = 10; } array = (T[])new Object[initCapacity]; } public void add(T e){ if(size >= array.length){ System.out.println("MyArray已经存满了"); return; } array[size] = e; size++; } public T get(int index){ if(index<0 || index>=size){ throw new ArrayIndexOutOfBoundsException("数组下标越界"); } return array[index]; } }
这里注意一个问题:
在new对象的时候,型泛型必须替换为具体类,这里使用Object并且在强制转换一下
往里边插入Person和Student对象
MyArray<Student> myArray1 = new MyArray<>(10); myArray1.add(new Student()); MyArray<Person> myArray2 = new MyArray<>(10); myArray2.add(new Person());
当泛型参数指定成Student类型时,如果在往里边插入Person对象时会发生什么?
发现编译器直接报错,说明类型一旦确定,就不能往里边在插入别的类型的对象了
注意:
泛型是作用在编译期间的一种机制
泛型在代码运行期间,会进行类型擦除,底层实际用的是Object实现的,但是用户不用在代码中做强制类型准换的事了,让编译器在编译阶段检查
查看字节码:
3. 为什么要有包装类
我们如果想要往MyArray中插入int类型的数据呢?
发现将泛型参数替换为int会报错,原因只有引用类型能替换泛型参数,而int为基本类型,那这时候怎么办呢?
基本类型对应的包装类就很好的解决了这个问题
4. 包装类
4.1 基本类型对应的包装类
基本数据类型 包装类
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
我们只记Integer和Character,因为其它都是基本类型首字母大写
4.2 装箱和拆箱
装箱:就是将基本类型转化为对应的包装类型
int i = 10; Integer in1 = Integer.valueOf(i); Integer in2 = new Integer(i);
拆箱:就是将包装类型转化为对应的基本类型
Integer in3 = new Integer(10); int a = in3.intValue();
4.3 自动装箱和自动拆箱
从上面装箱和拆箱操作中可以看出,装箱和拆箱给我们增加了繁琐的代码量,所以为了减少开发的负担,Java提供了自动装箱和自动拆箱机制
int n = 10; Integer in6 = n; //自动装箱 int m = in6; //自动拆箱
4.4 经典面试题
看看下面代码会输出什么?
public class test2 { public static void main(String[] args) { Integer a = 127; Integer b = 127; Integer c = 128; Integer d = 128; System.out.println(a==b); System.out.println(c==d); } }
这是为什么呢?
看一下Integer底层源码:
发现Integer底层维护了一个数组,这个数组值的范围为[-128,127],如果Integet对象的值在这个范围内,直接从cache数组中拿,类似于字符串常量池,就是Integer类型的引用直接指向数组对应值的地址,如果Integer对象的值超过这个范围,会创建新的对象
简单总结:[-128,127],Integer直接比较值,超出这个范围会创建新对象
再看看上面的题: