【数据结构】什么是泛型?为什么要使用泛型?泛型怎么用?那包装类呢?

简介: 发现Integer底层维护了一个数组,这个数组值的范围为[-128,127],如果Integet对象的值在这个范围内,直接从cache数组中拿,类似于字符串常量池,就是Integer类型的引用直接指向数组对应值的地址,如果Integer对象的值超过这个范围,会创建新的对象

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对象

image.png


发现会报错,思考一下为什么?

image.png

因为MyArray存放的是Object,Object是所有类的父类,所以这里存在向下转型,向下转型是不安全的,所以我们必须强转一下

image.png


这个时候这个问题就解决了


但是这种时候会出现一另个问题:

image.png


观察一下,发现明明是Person类型,为什么用Student类型接收,编译器不报错?


我们可以运行一下看看结果如何:

image.png

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];
    }
}

这里注意一个问题:

image.png

在new对象的时候,型泛型必须替换为具体类,这里使用Object并且在强制转换一下

image.png

往里边插入Person和Student对象

MyArray<Student> myArray1 = new MyArray<>(10);
        myArray1.add(new Student());
        MyArray<Person> myArray2 = new MyArray<>(10);
        myArray2.add(new Person());

     当泛型参数指定成Student类型时,如果在往里边插入Person对象时会发生什么?


image.png

发现编译器直接报错,说明类型一旦确定,就不能往里边在插入别的类型的对象了


注意:


泛型是作用在编译期间的一种机制

泛型在代码运行期间,会进行类型擦除,底层实际用的是Object实现的,但是用户不用在代码中做强制类型准换的事了,让编译器在编译阶段检查

查看字节码:

image.png

3. 为什么要有包装类

我们如果想要往MyArray中插入int类型的数据呢?

image.png

发现将泛型参数替换为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; //自动拆箱

 

image.png


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);
    }
}

image.png

这是为什么呢?


看一下Integer底层源码:

微信图片_20221029155315.png

微信图片_20221029155325.png

发现Integer底层维护了一个数组,这个数组值的范围为[-128,127],如果Integet对象的值在这个范围内,直接从cache数组中拿,类似于字符串常量池,就是Integer类型的引用直接指向数组对应值的地址,如果Integer对象的值超过这个范围,会创建新的对象


简单总结:[-128,127],Integer直接比较值,超出这个范围会创建新对象


再看看上面的题:

image.png

相关文章
|
8月前
|
安全 Java 开发者
【Java】<泛型>,在编译阶段约束操作的数据结构,并进行检查。
【Java】<泛型>,在编译阶段约束操作的数据结构,并进行检查。
51 0
|
Java 编译器 API
【数据结构】 简单认识包装类与泛型
【数据结构】 简单认识包装类与泛型
|
3月前
|
存储 Java 编译器
数据结构第四篇【再谈泛型】
数据结构第四篇【再谈泛型】
29 7
|
3月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
37 5
|
3月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
28 2
|
存储 Java
Java数据结构之第十四章、泛型进阶
Java数据结构之第十四章、泛型进阶
89 0
|
Java 编译器 API
Java数据结构之第二章包装类&初识泛型
可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制int num=10;//自动装箱//手动装箱//拆箱操作:将 Integer 对象中的值取出,放到一个基本数据类型中//自动拆箱1.3.2【面试题】//内部自动调用valueOf方法为什么分别输出true和false呢?接下来我们看看内部的源码:由于a和b在-128~127的范围内,所以返回cashe[127+(-128)]=cashe[-1];
61 0
|
编译器
【数据结构】初识泛型
【数据结构】初识泛型
|
Java 编译器 容器
【数据结构】包装类&简单认识泛型
【数据结构】包装类&简单认识泛型
77 0
|
存储 编译器