目录
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直接比较值,超出这个范围会创建新对象
再看看上面的题: