数据结构预备知识(Java):包装类&泛型

目录

1、包装类

1.1 包装类

1.2 装箱和拆箱

1.2.1 装箱

1.2.1.1 自动装箱&显示装箱

1.2.2 拆箱

1.2.2.1 自动拆箱&显示拆箱

1.2.3 自动拆箱&自动装箱 底层原理

1.3 包装类面试题 --->缓存数组

2、泛型

2.1 什么是泛型

2.2 泛型的语法

2.3 泛型的使用

2.4 裸类型(Raw Type) (了解)

2.5 泛型是如何进行编译的?

2.5.1 擦除机制

2.5 泛型的上界

2.5.1 语法

2.5.2 示例一(以类为边界)

2.5.3 示例二(以接口为边界)

2.6 泛型方法

2.6.1 语法

2.6.2 示例

2.7 通配符

2.8 泛型的特点


1、包装类

1.1 包装类

在Java中,每一个基本数据类型都有一个对应的包装类:

在SE的学习中我们已有过简单了解。

我们可以注意到,除了int类型的包装类为Integer,char类型的包装类为Character外,其余基本类型的包装类均将首字母大写即可。

1.2 装箱和拆箱

装箱(装包):把基本数据类型变为包装类类型的过程,叫做装箱。

拆箱(拆包):把包装类类型变为基本数据类型的过程,叫做拆箱。

装箱又分为 自动装箱和显示装箱。

拆箱又分为 自动拆箱和显示拆箱。

1.2.1 装箱

装箱(装包):把基本数据类型变为包装类类型的过程,叫做装箱。

装箱分为 自动装箱和显示装箱。

1.2.1.1 自动装箱&显示装箱
public static void main(String[] args) {
        Integer a = 10;//自动装箱

        int b = 10;
        Integer c = Integer.valueOf(b);//显示装箱
    }

我们可以将数据直接赋值给包装类类型来自动装箱,也可以通过包装类中的方法来显示装箱。

1.2.2 拆箱

拆箱(拆包):把包装类类型变为基本数据类型的过程,叫做拆箱。

拆箱分为 自动拆箱和显示拆箱。

1.2.2.1 自动拆箱&显示拆箱
public static void main(String[] args) {
        Integer a = 10;//自动装箱(先装好箱,再来拆箱)
        int a1 = a;//自动拆箱
        
        int a2 = a.intValue();//手动拆箱
        double a3 = a.doubleValue();//手动拆箱
    }

1.2.3 自动拆箱&自动装箱 底层原理

其实不管是自动装箱,还是自动拆箱,底层都是帮我们调用了valueOf或者intValue/doubleValue/..... 方法:

1.3 包装类面试题 --->缓存数组

我们先来看以下代码:

读到这里,大家可以先猜测一下结果。

结果是出人意料的:

为什么会出现以上的结果的?明明两组数据都是包装类啊,为什么一组结果是true,而另一组结果是false呢?

要解决问题,我们就需要找到问题的主要矛盾。

我们可以发现,这几行代码,仅仅只发生了装箱。那我们就去看装箱是怎么操作的,也就是valueOf的源码是怎么工作的:

我们发现,当我们传入的值满足一个范围的时候,返回了一个数组中的值,而不满足这个范围的时候,则新返回了一个对象,既然返回了一个新对象,新对象用 == 来进行比较,那结果必然是false!

那这个范围是多少呢?

我们可以看到,范围为[-128,127] 。

也就是说,当我们要装箱的数据在这个范围当中时,是直接从一个数组中拿的数据,而这个数组就是缓存数组

缓存数组中共有256个数字,数组下标的范围为[0,255] ,存储着如下的数据:

所以当传入的数据在[-128,127]这个范围时,是直接从这个缓存数组中拿到的数据。

2、泛型

2.1 什么是泛型

顾名思义,泛型就是适用于许多许多类型。

在我们之前的学习中,我们可以将一个数据当做参数传到一个方法中,而泛型,是将一个数据类型当做参数传入,我们需要什么类型,就传入什么类型。

2.2 泛型的语法

类名后的 <E> 代表占位符,表示当前类是一个泛型类。
new关键字后<>中的类型实参可以不写,Java会根据第一个传入的参数自动的来推导出这个类型。
<>中参数传入规范:

2.3 泛型的使用

我们将Integer作为参数传入,那我们用E来接收的参数的类型必须为整型,不能再传入其他类型,如:字符串、字符型......:

这里就会帮我们进行自动类型检查,如果不是对应的类型,就会报错。

我们接收数据时也不需要强制类型转换,会进行自动类型转换

注意!注意!注意!!!

实例化对象时,泛型中传入的类型只能为类类型,不能为基本数据类型!!!

泛型代码:

class myArray<E> {
    public Object[] array = new Object[10];

    public void setValue(int pos,E val) {
        array[pos] = val;
    }
    public E getValue(int pos) {
        return (E)array[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        myArray<Integer> Array = new myArray<>();
        Array.setValue(0,10);//自动类型检查
        Array.setValue(1,100);//自动类型检查
        //Array.setValue(2,"dings");自动类型检查 发现错误
        Integer ret1 = Array.getValue(0);//自动类型转换
        System.out.println(ret1);
    }
}

2.4 裸类型(Raw Type) (了解)

裸类型是一个泛型类但没有传入类型实参,例如 Array  就是一个裸类型
我们发现,我们明明定义的是一个泛型类,但是我们实例化对象时并没有传入类型参数,却也没有发生报错,这是为什么呢?
答: 泛型是在JDK5引入的裸类型是为了兼容老版本的 API 保留的机制,所以不会报错。

2.5 泛型是如何进行编译的?

2.5.1 擦除机制

泛型是编译时期的一种机制,在运行的时候没有泛型的概念,也就是说,JVM当中没有泛型的概念。

在编译完成后,我们定义的<>中的T、E......等等,都会被擦除并且替换为Object,编译器生成的字节码在运行期间并不包含泛型的类型信息,这就是擦除机制

关于擦除机制的介绍:擦除机制

2.5 泛型的上界

在定义泛型类时,有时需要对传入的泛型做一定的约束,可以通过类型边界来约束。

2.5.1 语法

2.5.2 示例一(以类为边界)

语法:

我们对泛型类定义了上界为Number ,那传入的类型只能是Number或者是Number的子类。

代码示例:

ps:没有指定类型边界 E,可以视为 E extends Object

2.5.3 示例二(以接口为边界)

例如:

那传入的类类型只能是实现了Comparable接口的类。

为什么要这样规定呢?

因为,我们使用的是泛型类,一旦我们要在类中进行数据的比较,那我们比较方法是未被定义的,就是说我们在写下这段代码时,还不知道传入的E会是什么类型,在方法中不能直接使用 >或者<来直接进行比较。于是,我们规定传入的类必须实现了Comparable接口,那就可以直接调用CompareTo方法来进行数据的比较了。

代码示例:

class A<E extends Comparable<E>> {//要求传入的类必须实现了Comparable接口,下面用来数据之间的比较
    public E findMax(E[] array) {//利用compareTo进行比较,找到数组中的最大值
        int max = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[max].compareTo(array[i]) < 0) {
                max = i;
            }
        }
        return array[max];
        }
}
public class Student implements Comparable<Student>{//实现了Comparable接口
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override//重写compareTo方法,规范比较行为
    public int compareTo(Student o) {
        return this.age - o.age;
    }

    public static void main(String[] args) {
        Student[] students = new Student[]{
                new Student("dinsg",10),
                new Student("fdd",100),
                new Student("kasg",21),
                new Student("hau",1)
        };
        A<Student> aaa = new A<>();
        Student maxStudent = aaa.findMax(students);
        System.out.println(maxStudent);
    }
}

2.6 泛型方法

  我也可以不定义泛型类,仅仅通过对方法传入类型来定义一个泛型方法。

2.6.1 语法

2.6.2 示例

class A {
    public<E extends Comparable<E>> E findMax(E[] array) {//非静态泛型方法
        E max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }
}
public class Test {
    public static void main(String[] args) {
        Integer[] arr = new Integer[]{1,2,3,4,5,100,234,23,12,4,56};
        A a = new A();
        int ret = a.findMax(arr);//通过传入的参数会自动推导出泛型方法中E类型
        //等价于int ret = a.<Integer>findMax(arr);
        System.out.println(ret);
    }
}

注意,调用泛型方法时有两种写法,这两种写法都是可以的:

就是像调用普通方法一样调用泛型方法,Java会通过传入的数据(参数)自动推导出所要传入的类型

②在方法名前手动加上所要传入的类型参数

上面举例为普通的泛型方法,若要写静态的泛型方法,在修饰访问修饰限定符后加上static即可:

2.7 通配符

泛型的通配符: ?(表示不确定的类型)

我们在定义类、方法、接口时,如果我们不确定类型,我们就可以定义泛型类、泛型方法、泛型接口。泛型默认为Object,可以接收所有的类型。

但是我们一旦知道,只能是某个继承体系中的类型时,我们就可以使用通配符来限定类型的范围

? extends E :表示只能是E或者E的子类类型
? super E:表示只能是E或者E的父类类型

2.8 泛型的特点

泛型不具备继承性,但是数据具备继承性。

泛型不具备继承性的意思是:当我们将一个具有泛型的类作为一个方法形参,那么调用这个方法传入的实参的泛型也必须为这个类型,即使具有父子类关系也不可以:

数据具备继承性的意思是:我们可以在泛型类中 使用泛型接收泛型的子类类型的数据:


OK~本次博客到这里就结束了,

感谢大家的阅读~欢迎大家在评论区交流问题~

如果博客出现错误可以提在评论区~

创作不易,请大家多多支持~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值