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

news2024/11/28 18:46:57

目录

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两次装箱的引用指向的地址就不相等了~
下期见~~~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2017年五一杯数学建模C题宜居城市问题值解题全过程文档及程序

2017年五一杯数学建模 C题 宜居城市问题 原题再现 城市宜居性是当前城市科学研究领域的热点议题之一&#xff0c;也是政府和城市居民密切关注的焦点。建设宜居城市已成为现阶段我国城市发展的重要目标,对提升城市居民生活质量、完善城市功能和提高城市运行效率具有重要意义。…

正则表达式回溯陷阱

一、匹配场景 判断一个句子是不是正规英文句子 text "I am a student" 一个正常的英文句子如上&#xff0c;英文单词 空格隔开 英文单词 多个英文字符 [a-zA-Z] 空格用 \s 表示 那么一个句子就是单词 空格&#xff08;一个或者多个&#xff0c;最后那个单词…

An example of a function uniformly continuous on R but not Lipschitz continuous

See https://math.stackexchange.com/questions/69457/an-example-of-a-function-uniformly-continuous-on-mathbbr-but-not-lipschitz?noredirect1

十七、事件组

1、事件组是什么 1.1、举例说明 (1)学校组织秋游&#xff0c;组长在等待&#xff1a; 张三&#xff1a;我到了李四&#xff1a;我到了王五&#xff1a;我到了组长说&#xff1a;好&#xff0c;大家都到齐了&#xff0c;出发&#xff01; (2)秋游回来第二天就要提交一篇心得…

leetcode刷题详解五

117. 填充每个节点的下一个右侧节点指针 II 关键点&#xff1a;先递归右子树 画一下就知道了&#xff0c;画一个四层的二叉树&#xff0c;然后右子树多画几个节点就知道为啥了 Node* connect(Node* root) {if(!root || (!root->left && !root->right)){return ro…

针对操作系统漏洞的反馈方法

一、针对操作系统漏洞的反馈方法 漏洞扫描指基于漏洞数据库&#xff0c;通过扫描等手段对指定的远程或者本地计算机系统的安全脆弱性进行检测&#xff0c;发现可利用漏洞的一种安全检测&#xff08;渗透攻击&#xff09;行为。在进行漏洞扫描后&#xff0c;需先确定哪些是业务…

二叉树:leetcode1457. 二叉树中的伪回文路径

给你一棵二叉树&#xff0c;每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的&#xff0c;当它满足&#xff1a;路径经过的所有节点值的排列中&#xff0c;存在一个回文序列。 请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。 给定二叉树的节点数目…

SSD FTL 映射管理

映射的种类 根据映射粒度的不同分为如下几种 1.块映射 一个逻辑块&#xff08;logical block&#xff09;映射到一个闪存物理块(physical block) 优点是&#xff1a;映射表需要空间小&#xff0c;对大Block size顺序读写&#xff0c;但是对小尺寸数据的写入性能很差。因为即使…

基于改进莱维飞行和混沌映射粒子群优化算法(LPSO)-BP神经网络的时间序列预测模型

基于改进莱维飞行和混沌映射粒子群优化算法(LPSO)原理&#xff1a; 通过引入混沌映射机制&#xff0c;对其群体进行初始化&#xff0c;增加粒子群个体的多样性&#xff1b;然后在粒子群个体的位置更新公式上引入改进的莱维飞行机制&#xff0c;提高搜索精度&#xff0c;帮助粒…

重庆数字孪生技术推进制造业升级,工业物联网可视化应用加速

重庆数字孪生、5G、人工智能、物联网、大数据等新一代信息技术的出现及终端计算设备的发展&#xff0c;带来了研发模式、生产模式、消费模式、体制机制的系统性变革&#xff0c;企业应该建设适应工业4.0时代发展要求的新型生产体系。巨蟹数科数字孪生智能工厂通过部署多样化用例…

Java EE 进程线程

JavaEE 进程&线程 文章目录 JavaEE 进程&线程1. 进程1.1 概念1.2 进程管理1.3 PCB (Process Control Block) 2. 线程2.1 概念2.1 线程与进程的区别2.3 创建线程 1. 进程 1.1 概念 什么是进程&#xff1f; 进程是操作系统对一个正在执行的程序的一种抽象 我们可以打开…

Android 相机库CameraView源码解析 (二) : 拍照

1. 前言 这段时间&#xff0c;在使用 natario1/CameraView 来实现带滤镜的预览、拍照、录像功能。 由于CameraView封装的比较到位&#xff0c;在项目前期&#xff0c;的确为我们节省了不少时间。 但随着项目持续深入&#xff0c;对于CameraView的使用进入深水区&#xff0c;逐…

2006-2023年2月地级市城投债数据

2006-2023年2月地级市城投债数据 1、时间&#xff1a;2006-2023年2月 2、指标&#xff1a;省份、城市、证券代码、证券简称、债券简称、证券全称、债券初始面值单位元、债券最新面值交易日期20221231、发行总额单位亿元、债券余额日期20221231单位亿、起息日期、计息截止日、…

2018年10月9日 Go生态洞察:Go Cloud的Wire与编译时依赖注入

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

火柴棒等式

枚举 只要在保证等式正确的基础上判断火柴棒有没有用完就可以 因为数比较小&#xff0c;而且我不知道最大的等式中的数是多少&#xff0c;索性就设置为999了 还好对效率要求不大&#xff08;doge&#xff09; 要不然就得自己慢慢改最大数来试了 代码如下&#xff1a; #in…

树套树 (线段树+splay)

树套树&#xff0c;就是线段树、平衡树、树状数组等数据结构的嵌套。 最简单的是线段树套set&#xff0c;可以解决一些比较简单的问题&#xff0c;而且代码根线段树是一样的只是一些细节不太一样。 本题中用的是线段树套splay&#xff0c;代码较长。 树套树中的splay和单一的…

基于springboot实现农机电招平台系统项目【项目源码+论文说明】

基于springboot实现农机电招平台系统演示 摘要 随着农机电招行业的不断发展&#xff0c;农机电招在现实生活中的使用和普及&#xff0c;农机电招行业成为近年内出现的一个新行业&#xff0c;并且能够成为大群众广为认可和接受的行为和选择。设计农机电招平台的目的就是借助计算…

C++ day37 贪心算法 单调递增的数字 监控二叉树

题目1&#xff1a;738 单调递增的数字 题目链接&#xff1a;单调递增的数字 对题目的理解 返回小于或等于n的最大数字&#xff0c;且数字是单调递增&#xff08;单调递增数字的定义&#xff1a;每个相邻位上的数字满足x<y&#xff09; 贪心算法 注意本题的遍历顺序是从…

写 SVG 动画必看!SVG系列文章1-简介

1、SVG是什么 SVG 是一种 XML 语言&#xff0c;类似 XHTML&#xff0c;可以用来绘制矢量图形&#xff0c;例如下面展示的图形。SVG 可以通过定义必要的线和形状来创建一个图形&#xff0c;也可以修改已有的位图&#xff0c;或者将这两种方式结合起来创建图形。图形和其组成部分…

Vatee万腾科技的未来探险:Vatee数字创新的独特发现

在科技的浩瀚海洋中&#xff0c;Vatee万腾科技如一艘探险船般&#xff0c;勇敢地驶向未知的数字化领域。这次未来的探险&#xff0c;不仅是一场科技创新的冒险&#xff0c;更是对数字化时代的独特发现和深刻探讨。 Vatee万腾科技视科技创新为一座高峰&#xff0c;而他们的未来探…