泛型你掌握多少?包装类你深入了解过吗?快进来看看吧~

目录

1、泛型是什么——引出泛型

2、泛型的使用

2.1、语法

2.2泛型类的使用

2.3、裸类型

3、泛型如何编译

3.1、擦除机制

3.2、为什么不能实例化泛型类型数组

4、泛型的上界

5、泛型方法

5.1、语法

5.2、举例

6、通配符

6.1、什么是通配符

6.2、统配符解决了什么问题

6.3、通配符上界

6.4、通配符的下界

7、包装类

7.1、基本数据类型对应的包装类

7.2、装箱和拆箱

7.3、自动装箱和自动拆箱

7.4、关于装箱和拆箱的面试题


1、泛型是什么——引出泛型

        设想现在有一个场景,要求我们实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。

思路:

        我们现在无法确定他的数据类型是什么,所以我们可以直接使用他们所有的类父类,也就是Object类,来实现:

代码如下:

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:9:40
 */
class Arr {
    //数组
    public Object[] arr = new Object[10];
    //获取数据
    public Object getPos(int pos) {
        return arr[pos];
    }
    //设置某位置上的数据
    public void setVal(int pos,Object val){
        this.arr[pos] = val;
    }
}
public class test1 {
    public static void main(String[] args) {
        Arr arr = new Arr();
        arr.setVal(0,10);
        arr.setVal(1,"hhh");
        Integer ret1 = (Integer) arr.getPos(0);
        String ret2 = (String) arr.getPos(1);
    }
}

        我们会看到上述的代码,在一个数组中,我们既可以存放int类型的数据,也可以存放字符串了,也就是说可以存放任意类型的数据~

        但是我们会发现一个点,我们在获取数据时,每次都需要我们进行一次类型强转,不强转就是编译报错:

        那在这种情况下,虽然说,可以去存放多种类型的数据在一个数组中,但更多情况下,我们还是希望他只能够持有一种数据类型,而不是同时持有这么多类型。

        以上,就引出了泛型,通过上述,我们大概也能猜到了,泛型是在干什么?

        泛型的主要目的,就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时就需要我们把类型,作为参数传递,需要什么类型,我们就传入什么类型。


2、泛型的使用

2.1、语法

//语法:
class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}
//例1:
class ClassName<T1, T2, ..., Tn> {

}
//语法:
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
//例2:
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}

        上述语法,大致意思就是,我们把参数的类型也当做参数使用尖括号传过去,并且我们这个类还能去继承一个父类,这个父类也可以是带有泛型的~

使用泛型将目录1中的代码进行改写:

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:9:40
 */
class Arr2<T> {
    //数组
    public T[] arr = (T[]) new Object[10];
    //获取数据
    public T getPos(int pos) {
        return arr[pos];
    }
    //设置某位置上的数据
    public void setVal(int pos,T val){
        this.arr[pos] = val;
    }
}
public class test2 {
    public static void main(String[] args) {
        Arr2<Integer> arr2 = new Arr2<>();
        arr2.setVal(0,10);
        Integer ret = arr2.getPos(0);
        System.out.println(ret);
    }

}

上述代码说明:

  1. 类名后<T>代表的是占位符,表示当前类是一个泛型类
  2. 上面的代码new一个数组时,不能new泛型类型的数组,会报错,所以我这里是new一个Object数组,再强转了一下~
  3. main函数中new Arr2时,需要指定当前类型
  4. 这种方式,我们在获取数据时无需强转了
  5. 此时我们指定arr2这个数组是Integer类型,我们插入其他类型的数据时,会编译报错,我们重新new 一个Arr2即可~

2.2泛型类的使用

        上面main函数中,其实就是在使用泛型类了~
        总结上面的语法:

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

说明:

  • 上面两个语法,我在上面的main函数中,只使用了一个,因为第二个尖括号那里,编译时会推导出来他的类型,可以不传这个参数~

实例,下面两种方式都可以:

Arr2<Integer> arr2 = new Arr2<>();
Arr2<Integer> arr3 = new Arr2<Integer>();

2.3、裸类型

        上面2.1中的代码,我们在main函数中使用时,不传类型这个参数,也不会报错:

        这种就是裸类型,就是我们在使用时,没有给他传类型这个参数。

        一般不建议使用这个方式,这种方式编译不会报错是因为,要兼容老版本的API。


3、泛型如何编译

3.1、擦除机制

        所谓的擦除机制,其实就是java在编译的过程当中,将所有的T替换为Object~

        所以说,java的泛型机制是在编译级别实现的,我们可以去看看我们上述代码中的:

3.2、为什么不能实例化泛型类型数组

看下面一段代码:

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:11:29
 */
class MyArray<T> {
    public T[] array = (T[])new Object[10];
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
    public T[] getArray() {
        return array;
    }
}
public class test3 {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();

        Integer[] arr = myArray.getArray();
    }
}

看着代码,好像没什么问题,我们运行看看:

报错,显示泛型类型数组实例化这一行不能强转类型转换,为什么呢?

        上面我们说了,擦除机制,在编译时,把所有的T替换成了Object,也就是说,编译会认为,你想要把一个Object的数组给了Integer类型的数组,编译器认为是不安全的~

正确方式:

import java.lang.reflect.Array;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:11:29
 */
class MyArray<T> {
    public T[] array = (T[])new Object[10];

    public MyArray(){

    }
    public MyArray(Class<T> clazz, int capacity) {
        array = (T[]) Array.newInstance(clazz, capacity);
    }
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
    public T[] getArray() {
        return array;
    }
}
public class test3 {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>(Integer.class,10);

        Integer[] arr = myArray.getArray();
    }
}

上述,是通过反射创建并指定了类型的数组~


4、泛型的上界

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

4.1、语法

class 泛型类名称<类型形参 extends 类型边界> {
...
}

举例:

public class MyArray<E extends Number> {
    //...
}

 上述代码意思:只接受Number的子类型作为E的类型实参~

当没有指定类型的上边界时,可以视为E extends Object

举例2:写一个泛型类,找出数组当中的最大值,只要这个T,实现了Comparable接口就行

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:11:45
 */
class Alg<T extends Comparable<T>> {
    public T findMaxVal(T[] array) {
        T maxVal = array[0];
        for (int i = 1; i < array.length; i++) {
            if(array[i].compareTo(maxVal) > 0) {
                maxVal = array[i];
            }
        }
        return maxVal;
    }
}
public class test4 {
    public static void main(String[] args) {
        Alg<Integer> a1 = new Alg<>();
        Integer[] arr = {2,4,5,9,10,1};
        Integer ret = a1.findMaxVal(arr);
    }
}

5、泛型方法

        上面我们一直在给一个类传一个参数过去,那方法中,泛型方法,如何使用呢?

5.1、语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

5.2、举例

例1:

public class Util {
    //静态的泛型方法 需要在static后用<>声明泛型类型参数
    public static <E> void swap(E[] array, int i, int j) {
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
}

使用上面的方法:

//使用1:
Integer[] a = { ... };
swap(a, 0, 9);
String[] b = { ... };
swap(b, 0, 9);

//使用2:
Integer[] a = { ... };
Util.<Integer>swap(a, 0, 9);
String[] b = { ... };
Util.<String>swap(b, 0, 9);

6、通配符

6.1、什么是通配符

        ?用于在泛型的使用,即为通配符。具体继续往下看~

6.2、统配符解决了什么问题

        通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List<Student> 也应该是 List<Person> 的子类。但是泛型是不支持这样的父子类关系的。
        泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围。

 

案例:

class Message<T> {
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Message<String> message = new Message() ;
        message.setMessage("早上好");
        fun(message);
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}

上述代码的问题:

        这里我直接给他指定了一个数据类型,那也就是说,如果main函数中,我设的是Integer类型,就无法调用这个方法了:

编译报错了~

解决方案:使用通配符

    public static void main(String[] args) {
        Message<Integer> message = new Message() ;
        message.setMessage(111);
        fun(message);
    }
    public static void fun(Message<?> temp){
        System.out.println(temp.getMessage());
    }

        这种方式也有缺点的,因为我们无法确定这个方法过来的是什么数据类型,所以无法进行修改~
        所以为了解决这个问题,就引出了通配符的上、下界:

6.3、通配符上界

语法:

<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类

举例:

代码:

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:12:10
 */
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> { // 设置泛型上限
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class test5 {
    public static void main(String[] args) {
        Message<Apple> message = new Message<>() ;
        message.setMessage(new Apple());
        fun(message);
        Message<Banana> message2 = new Message<>() ;
        message2.setMessage(new Banana());
        fun(message2);
    }
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Fruit> temp){
        //temp.setMessage(new Banana()); //仍然无法修改!
        //temp.setMessage(new Apple()); //仍然无法修改!
        Fruit b = temp.getMessage();
        System.out.println(b);
    }
}

通配符的上界的主要作用:

        更方便我们去接收数据~(不能进行写入数据,只能进行读取数据)~

6.4、通配符的下界

语法:

<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

举例:

在上述代码下,增加:
 

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-11-26
 * Time:12:10
 */
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> { // 设置泛型上限
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class test5 {
    public static void main(String[] args) {
        Message<Apple> message = new Message<>() ;
        message.setMessage(new Apple());
        fun(message);
        Message<Banana> message2 = new Message<>() ;
        message2.setMessage(new Banana());
        fun(message2);
        
        Message<Fruit> message3 = new Message<>();
        message3.setMessage(new Fruit());
        fun2(message3);
        
    }
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Fruit> temp){
        //temp.setMessage(new Banana()); //仍然无法修改!
        //temp.setMessage(new Apple()); //仍然无法修改!
        Fruit b = temp.getMessage();
        System.out.println(b);
    }
    public static void fun2(Message<? super Fruit> temp){
        // 此时可以修改!!添加的是Fruit 或者Fruit的子类
        temp.setMessage(new Apple());//这个是Fruit的子类
        temp.setMessage(new Fruit());//这个是Fruit的本身
        //Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
        System.out.println(temp.getMessage());//只能直接输出
    }
}

通配符的下界的主要作用:

        不能进行读取数据,只能写入数据~
 


7、包装类

7.1、基本数据类型对应的包装类

7.2、装箱和拆箱

int i = 10;
// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer ii = Integer.valueOf(i);//方法1
Integer ij = new Integer(i);//方法2
// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j = ii.intValue();

7.3、自动装箱和自动拆箱

int i = 10;
Integer ii = i; // 自动装箱
Integer ij = (Integer)i; // 自动装箱
int j = ii; // 自动拆箱
int k = (int)ii; // 自动拆箱

本质:

  1. 自动装箱其实就是调用Integer.valueOf()这个方法
  2. 自动拆箱就是调用intValue()这个方法

7.4、关于装箱和拆箱的面试题

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这个类中,然后:

        上面我们会看到这个valueOf()方法,我们来看看这个方法内部是怎么实现的:

        他是判断我们这个数字的大小,是不是在这个[IntegerCache.low , IntegerCache.high]区间中,如果在,他就会返回IntegerCache.cache这个数组中的一个值,这个值位置:用我们给的这个数字加上刚才判断中的那个区间的左边界值的相反数。

        我们再来看看这个IntegerCache长什么样:

那就是说,他其实是有一个Cache数组,如下:

        数组的长度是256,存放的数值是-128到127之间的,当我们来了一个要装箱的数字,如果这个数字在这个数组中,那就返回这个数组的所在位置,就有了我们上述的127和127两次装箱,引用所指向的地址相等了,如果数字不在这个数组上,就会去new一个对象了,每次new出来的地址,是不相等的,所以128和128两次装箱的引用指向的地址就不相等了~
下期见~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/191180.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据中台】开源项目(2)-Dbus系统架构

大体来说&#xff0c;Dbus支持两类数据源&#xff1a; RDBMS数据源 日志类数据源 1 RMDBMS类数据源的实现 以mysql为例子. 分为三个部分&#xff1a; 日志抽取模块(最新版DBus已经废弃该模块&#xff0c;使用canal直接输出到kafka) 增量转换模块 全量拉取模块 1.1 日志抽…

单片机学习4——中断的概念

中断的概念&#xff1a; CPU在处理A事件的时候&#xff0c;发生了B事件&#xff0c;请求CPU迅速去处理。&#xff08;中断产生&#xff09; CPU暂时中断当前的工作&#xff0c;转去处理B事件。&#xff08;中断响应和中断服务&#xff09; 待CPU将B事件处理完毕后&#xff0…

深入理解JVM虚拟机第二十六篇:详解JVM当中的虚方法和非虚方法,并从字节码指令的角度去分析虚方法和非虚方法

😉😉 学习交流群: ✅✅1:这是孙哥suns和树哥给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783824 📚​​​​​​​📚 微信:DashuDeveloper拉你进微信群,免费领取! 一:非虚方法和虚方法 方法…

【JAVA杂货铺】一文带你走进面向对象编程|继承|重载|重写|期末复习系列 | (中4)

&#x1f308;个人主页: Aileen_0v0&#x1f525;系列专栏:Java学习系列专栏&#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 继承 私有成员变量在继承中的使用​编辑 当子类和父类变量不重名时: 当子类和父类重名时: &#x1f4dd;总结: 继承的含义: …

网络视频播放卡顿原因分析

一、问题描述 某项目通过拉摄像机rtsp流转rtmp/http-flv/ws-flv的方案&#xff0c;使用户可以在网页中观看摄像机的视频画面。在 观看视频时偶发出现卡顿现象。 二、卡顿现象分析和解决 此问题涉及的原因较多&#xff0c;所以得考虑各环节的问题可能性&#xff0c;并根据现场实…

Vue常见的实现tab切换的两种方法

目录 方法一&#xff1a;事件绑定属性绑定 效果图 完整代码 方法二&#xff1a;属性绑定 动态组件 component标签 效果图 完整代码 方法一&#xff1a;事件绑定属性绑定 效果图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta c…

5.前端--CSS-基本概念【2023.11.26】

1. CSS 语法规范 CSS 规则由两个主要的部分构成&#xff1a;选择器以及一条或多条声明。 属性和属性值之间用英文“:”分开 多个“键值对”之间用英文“;”进行区分 选择器 : 简单来说&#xff0c;就是选择标签用的。 声明 &#xff1a;就是改变样式 2.CSS引入方式 按照 CSS 样…

有了倾斜摄影,如何搭建一座智慧城市?

随着无人机航测、倾斜摄影等全新一代测绘信息技术方法的发展&#xff0c;可以迅速搜集制作精细化的城市三维模型&#xff0c;搭建城市地理信息基础服务架构。 近期都在重点关注的“智慧城市”究竟是什么&#xff0c;有什么重大作用&#xff0c;同时又面临着什么难关&#xff0c…

物联网AI 无线连接学习之蓝牙基础篇 协议的发展

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 蓝牙由来 “蓝牙”&#xff08;Bluetooth&#xff09;原是一位在10世纪统一丹麦的国王哈拉尔 (HaralBluetooth)&#xff0c;他将当时的瑞典、芬兰与丹麦统一起来。而将“蓝牙”与后来的无线通讯技术标准关联…

正则化与正则剪枝

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 引言正则化为什么会过拟合拉格朗日与正则化梯度衰减与正则化 应用解决过拟合网络剪枝 …

【Amazon】在Amazon EKS集群中安装部署最小化KubeSphere容器平台

文章目录 一、准备工作二、部署 KubeSphere三、访问 KubeSphere 控制台四、安装Amazon EBS CSI 驱动程序4.1 集群IAM角色建立并赋予权限4.2 安装 Helm Kubernetes 包管理器4.3 安装Amazon EBS CSI 驱动程序 五、常见问题六、参考链接 一、准备工作 Kubernetes 版本必须为&…

Jmeter性能综合实战——签到及批量签到

提取性能测试的三个方面&#xff1a;核心、高频、基础功能 签 到 请 求 步 骤 1、准备工作&#xff1a; 签到线程组 n HTTP请求默认值 n HTTP cookie 管理器 n 首页访问请求 n 登录请求 n 查看结果树 n 调试取样器 l HTTP代理服务器 &#xff08;1&#xff09;创建线…

VMware安装部署kail镜像服务器【详细包含百度云盘镜像】

VMware安装部署kail镜像服务器【详细包含百度云盘镜像】 kail是一个很好玩的操作系统&#xff0c;不多说了哈 下载kail镜像 kail官网:https://www.kali.org/get-kali/#kali-platforms 百度云盘下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1PRjoP_1v8DEZ7-dA_…

JVM基础篇:垃圾回收

1.前言 1.1C/C的内存管理 在C/C这类没有自动垃圾回收机制的语言中&#xff0c;一个对象如果不再使用&#xff0c;需要手动释放&#xff0c;否则就会出现内存泄漏。我们称这种释放对象的过程为垃圾回收&#xff0c;而需要程序员编写代码进行回收的方式为手动回收。内存泄漏指的…

【计网 可靠数据传输RDT】 中科大笔记 (十 一)

目录 0 引言1 RDT的原理RDT的原理&#xff1a; 2 RDT的机制与作用2.1 重要协议停等协议&#xff08;Stop-and-Wait&#xff09;:连续ARQ协议: 2.2 机制与作用实现机制&#xff1a;RDT的作用&#xff1a; &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#x…

Git远程仓库常用开发命令和理解

远程仓库 创建与合并分支 每次提交&#xff0c;Git都把它们串成一条时间线&#xff0c;这条时间线就是一个分支。截止到目前&#xff0c;只有一条时间线&#xff0c;在Git里&#xff0c;这个分支叫主分支&#xff0c;即master分支。 HEAD严格来说不是指向提交&#xff0c;而…

基于springboot学籍管理系统

一、设计目的 1. 复习、巩固Java语言的基础知识&#xff0c;进一步加深对Java语言的理解和掌握&#xff1b; 2. 课程设计为学生提供了一个既动手又动脑&#xff0c;独立实践的机会&#xff0c;将课本上的理论知识和实际有机的结合起来&#xff0c;锻炼学生的分析解决实际问题…

系列十八、Spring bean线程安全问题

一、概述 我们知道Spring中的bean&#xff0c;默认情况下是单例的&#xff0c;那么Spring中的bean是线程安全的吗&#xff1f;这个需要分情况考虑&#xff0c;bean中是否存在成员变量&#xff1f;bean中的成员变量是怎么处理的&#xff1f;...&#xff0c;针对bean的状态会有不…

OpenGL的学习之路 -5

1.视景体 正交投影 人眼看世界&#xff0c;有一个可见范围。范围内可见&#xff0c;范围外不可见。视景体就是这么一个概念。 &#xff08;上图仅学习记录用&#xff09; 在OGL中&#xff0c;有两种投影方式&#xff0c;对应两种视景体。第一种&#xff0c;正交投影&#xf…

Flask学习二:项目拆分、请求与响应、cookie

教程 教程地址&#xff1a; 千锋教育Flask2框架从入门到精通&#xff0c;Python全栈开发必备教程 老师讲的很好&#xff0c;可以看一下。 项目拆分 项目结构 在项目根目录下&#xff0c;创建一个App目录&#xff0c;这是项目下的一个应用&#xff0c;应该类似于后端的微服…