【Java】泛型

news2024/11/19 14:34:01

当你觉得这条路很难走的时候,一定是上坡路


目录

1.初识泛型

1.1 什么是泛型

1.2泛型类语法

1.2.1泛型类定义

1.2.2泛型类使用语法 

 1.2.3泛型类的使用

1.2.4裸类型

2.泛型如何编译 

2.1擦除机制 

3.泛型的上界

3.1语法 

3.2示范 

4.泛型方法

4.1 语法 

4.2示例

 4.2.1使用示例-可以类型推导

4.2.2使用示例-不使用类型推导

5.通配符 

5.1通配符的作用 

5.2子通配符 

5.2.1通配符上界

5.2.2通配符的下界 

6.包装类 

6.1基本类型对应的包装类 

6.2装箱和拆箱

 6.2.1装箱

6.2.2拆箱 

6.2.3自动装箱和自动拆箱 


1.初识泛型

1.1 什么是泛型

《Java编程思想》这本书中对泛型的介绍中有这样一段话:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大

如下代码: 

class Array {
    public int[] arr = new int[10];

    public void setArr(int val,int pos) {
        this.arr[pos] = val;
    }

    public int getArr(int pos) {
        return this.arr[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        Array array = new Array();
        array.setArr(1,0);
        int ret = array.getArr(0);
        System.out.println(ret);
    }
}

一般在方法的形参和返回值都会用到具体的类型,这样就限制了代码。如:我们实例化了一个Array 类型的对象,当我们用这个对象调用 setArr 方法设置数组值时,因为形参是整型那么只能传入整型的实参。当我们用这个对象调用 getArr 方法去获取数组某个位置值时,因为返回值是整型所以只能返回整型的值,那么这种刻板的限制对代码的束缚就会很大

为了避免如上的限制在 JDK1.5 版本中引入了泛型。

泛型就是参数化类型 ,说起参数大家第一时间就会想起方法的形参,当我们要调用方法时就需要传入实参,那什么叫做参数化类型呢?顾名思义也就是根据实参的类型来决定形参的类型

那我们现在需要将上述代码数组中可以存放任何类型的数据,那需要如何进行修改代码呢?

思路:所有类的父类,默认为Object类。所以类型都有包装类,那么它们的包装类都继承了Object类,那么我们就可以将数组设置为 Object 类。所以的子类都都可以用父类接收,那么我们将数组设置为 Object 类型之后就可以接收任何类型的值

class Array {
    public Object[] arr = new Object[10];

    public void setArr(Object val,int pos) {
        this.arr[pos] = val;
    }

    public Object getArr(int pos) {
        return this.arr[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        Array array = new Array();
        array.setArr(1,0);
        array.setArr("obj",1);
        //int ret = (int)array.getArr(0);
    }
}

将数组设置为 Object 类型之后,就可以将数组中的不同位置放不同类型的值,但是当我们获取一个数组中指定位置的值时,需要强制类型转换。在大多数的情况下我们想要的是它只能够持有一种数据类型,而不是同时持有这么多类型。泛型的主要目的就是指定当前的容器,要持有什么类型的对象,让编译器去做检查。此时就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

1.2泛型类语法

1.2.1泛型类定义

class 泛型类名称<类型形参列表> {

}
class Array<T> {
  
}

注:一个泛型的类型形参列表可以指定多个类型 

类型形参一般使用一个大写字母表示,类型形参命名: 

  • E 表示 Element
  • K 表示 Key
  • V 表示 Value
  • N 表示 Number
  • T 表示 Type
  • S, U, V 等等 - 第二、第三、第四个类型

1.2.2泛型类使用语法 

泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造方法实参); 

泛型类<类型实参> 变量名:定义一个泛型类引用

 new 泛型类<类型实参>(构造方法实参):实例化一个泛型类对象 

Array<Integer> array = new Array<Integer>();

类型推导:

Array<Integer> array = new Array<>();

 当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

 1.2.3泛型类的使用

class Array<T> {
    public T[] arr = (T[])(new Object[10]);

    public void setArr(T val,int pos) {
        this.arr[pos] = val;
    }

    public T getArr(int pos) {
        return this.arr[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        Array<Integer> array = new Array<Integer>();
        array.setArr(1,0);
        array.setArr(1,1);
        Integer ret = array.getArr(0);
        System.out.println(ret);
    }
}
  • <T>:占位符,相当于当前类是一个泛型类
  • 不能 new 泛型类型的数组
  • 类型后加入<Integer>指定当前类型  
  • 不需要进行强制类型转换 
  • 编译器会在存放元素的时候帮助我们进行类型检查  
  • 泛型只能接受类,所有的基本数据类型必须使用包装类

泛型存在的意义: 

  • 存数据是会进行类型检查 
  • 取数据的时候会自动帮你类型转换,也就不需要我们手动强制类型转换了 

1.2.4裸类型

裸类型:在定义和实例化泛型类的时候不给类型实参

泛型类 变量名 = new 泛型类(构造方法实参); 
Array array = new Array();

裸类型存在的意义:是为了兼容老版本的 API 保留的机制

2.泛型如何编译 

2.1擦除机制 

class Array<T> {
    public T[] arr = (T[])(new Object[10]);

    public void setArr(T val,int pos) {
        this.arr[pos] = val;
    }

    public T getArr(int pos) {
        return this.arr[pos];
    }
}

接下来我们就通过DOS窗口查看一下上述这个泛型类的字节码文件:

我们在Dos窗口中用命令 javap -c 查看字节码文件,此时所有的 T 都是 Object 。那么在编译的过程当中,将所有的 T 替换为 Object 这种机制,我们称为:擦除机制

Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息 

问题:为什么不能 new 泛型类型的数组呢?

答:T[] arr = new T[],在编译过程当中不会将 new 的这个 T 擦除为Object,Java 规定擦除机制只针对定义变量时的类型和返回值的返回类型,不会擦除实例化时的泛型,所以 new 一个 T 数组,不会将 T 擦除为 Object 时,那么编译器就不认识 T,那么编译器在编译期间就会报错。

3.泛型的上界

泛型的上界:在定义泛型的时候,有时候需要给传过来的类型做一个约束,此时就可以用泛型的上界来进行约束。比如:我们希望传过来的类型是继承 School 类,那我们就可以用泛型的上界来约束,如果传过来的类型没有继承 School 这个类,则会报错

3.1语法 

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

3.2示范 

class Student<T extends School> {

}

此时只接受 School 的子类型作为 T 的类型实参,在编译时期擦除时会被擦除为其父类也就是擦除为School

注:如果没有指定泛型类型的上界,默认视为 T extends Object 

public class MyArray<E extends Comparable<E>> {

}

 此时传过来的实参类型必须是实现了 Comparable 接口

4.泛型方法

4.1 语法 

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

4.2示例

public class Test {
    public static <T> void swap(T[] array,int i,int j) {
        T t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
}

 4.2.1使用示例-可以类型推导

public static void main(String[] args) {
        Integer[] arr = {1,2,3,4,5,6};
        swap(arr,0,1);
    }

 当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

4.2.2使用示例-不使用类型推导

public static void main(String[] args) {
        Integer[] arr = {1,2,3,4,5,6};
        Test.<Integer>swap(arr,0,1);
    }

手动将类型实参传给类型形参

注: 给泛型传类型的时候必须传类,不能传基本数据类型但是可以传它们的包装类

5.通配符 

通配符:?用于在泛型的使用,这个?就是通配符

5.1通配符的作用 

class Array<T> {
    public T[] arr = (T[])(new Object[10]);

    public void setArr(T val,int pos) {
        this.arr[pos] = val;
    }

    public T getArr(int pos) {
        return this.arr[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        Array<Integer> array = new Array<Integer>();
        fun(array);
    }

    public static void fun(Array<Integer> arr) {
        arr.setArr(1,0);
        System.out.println(arr.getArr(0));
    }
}

在主函数中实例化了一个 Array 泛型类,传的类型是 Integer。在 fun 方法形参中只能接收 Array 类的类型为 Integer 的对象,调用 fun 方法可以使用类型推导,如果传入的不是 Array 类的类型为Integer 的对象则会报错。Array 是一个泛型类,实例化的时候可以传入不同类型的实参,那么 fun这样就有了一定的局限性

那么此时我们就可以将fun方法形参中的Array<>里面类型设置为通配符,这样就可以接收 Array 类的任何类型的对象了

class Array<T> {
    public T[] arr = (T[])(new Object[10]);

    public void setArr(T val,int pos) {
        this.arr[pos] = val;
    }

    public T getArr(int pos) {
        return this.arr[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        Array<Integer> array = new Array<Integer>();
        array.setArr(1,0);
        fun(array);
    }

    public static void fun(Array<?> arr) {
        System.out.println(arr.getArr(0));
    }
}

 使用通配符"?"说明它可以接收任意类型的Array对象,但是由于不确定类型,所以无法修改

5.2子通配符 

在通配符的基础上又产生了两个子通配符:

  • ?extends 类:设置泛型上界
  • ?super 类:设置泛型下界 

5.2.1通配符上界

①语法

<? extends 上界>
<? extends Number>

传入的实参类型必须是 Number 本身类或者是 Number 的子类 

②示例 

class Fruits {

}
class Apple extends Fruits {

}

class Message<T> {
    private T message;

    public void setMessage(T message) {
        this.message = message;
    }

    public T getMessage() {
        return message;
    }
}
public class Main {
    public static void main(String[] args) {
        Message<Apple> message = new Message<>();
        message.setMessage(new Apple());
        fun(message);
    }

    public static void fun(Message<? extends Fruits> tmp) {
        //tmp.setMessage(new Apple());
        Fruits fruits = tmp.getMessage();
    }
}

fun 形参类型传过来的实参类型必须是 Fruits 类型 或者是 Fruits 子类类型。在fun里面用通配符接收类型,那么我们也就不清楚是具体的哪种类型,只知道是Fruits 类型 或者是 Fruits 子类类型,那么我们就无法调用 setMessage 去设置 message 的值,因为子类类型无法存储父类对象。我们知道主函数传给fun方法的对象类型要么是 Fruits 类型 要么是 Fruits 子类类型,那么我们就可以调用 getMessage 去获取 message 的值,用Fruits 接收即可。

通配符的上界,不能进行写入数据,只能进行读取数据 

5.2.2通配符的下界 

①语法 

<? super 下界>
<? super Integer>

传过来的类型必须是 Integer 本身类 或者是 Integer 的父类 

②示例 

class Fruits {

}
class Apple extends Fruits {

}

class Message<T> {
    private T message;

    public void setMessage(T message) {
        this.message = message;
    }

    public T getMessage() {
        return message;
    }
}
public class Main {
    public static void main(String[] args) {
        Message<Fruits> message = new Message<>();
        message.setMessage(new Fruits());
        fun(message);
    }

    public static void fun(Message<? super Fruits> tmp) {
        tmp.setMessage(new Apple());
        //Fruits fruits = tmp.getMessage();
    }
}

fun 形参类型传过来的实参类型必须是 Fruits 类型 或者是 Fruits类型的父类,那我们在fun方法中通过 setMessage 去设置 message 的值时,我们知道主函数传给 fun 方法的对象类型最低也得是Fruits 方法,通过 setMessage 去设置 message 值是我们可以设置为 Fruits 子类对象,因为 Fruits子类肯定也是Fruits父类的子类,所以可以设置 message 的值。但是不能通过 getMessge 去获取messge的值,原因就在于我们只知道主函数传给fun实参类型是 Fruits 类型 或者是 Fruits类型的父类,如果传过来的实参类型是 Fruits 父类那我们去获取message的值时我们就不知道用什么接收了。

通配符的下界,不能进行读取数据,只能写入数据 

6.包装类 

在 Java 中,基本类型是不会继承 Object 类的,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型 

6.1基本类型对应的包装类 

基本数据类型包装类
byteByte
shoutShout
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

6.2装箱和拆箱

 6.2.1装箱

int a = 10;
Integer i = Integer.valueOf(a);

Integer.valueof:主要将一个基本数据类型的值存放到 Integer 类的对象里面

上述代码装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中 

Integer 类中的 valueOf 方法中,如果你存储的值在 -128(IntegerCache.low:-128)到127(IntegerCache:127)范围中就存储到 i +(-IntegerCache.low)这个位置的数组当中。如果超出了这个范围就 new 一个 Integer 对象,通过构造方法存储在 value 属性中

6.2.2拆箱 

int a = 10;
Integer i = Integer.valueOf(a);//装箱
int b = i.intValue();//拆箱

 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中

6.2.3自动装箱和自动拆箱 

从上述手动装箱和拆箱中我们可以看出手动装箱和拆箱给开发者带来了不少代码量,为了减少开发者的负担,Java提供了自动装箱拆箱机制

public class Demo {
    public static void main(String[] args) {
        int i = 10;
        Integer ii = i; // 自动装箱
        Integer ij = (Integer)i; // 自动装箱
        int j = ii; // 自动拆箱
        int k = (int)ii; // 自动拆箱
    }
}

通过字节码文件可以看出在编译时期会自动的装箱拆箱,还原为装箱和拆箱 

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

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

相关文章

i.MX 6ULL 驱动开发 十九:RGBLCD

一、RGBLCD 硬件原理 【正点原子MP157连载】第十八章 RGB LCD彩条显示实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_正点原子的博客-CSDN博客 ATK7016 时序参数&#xff1a; 二、eLCDIF 接口 eLCDIF 是 I.MX6U 自带的液晶屏幕接口&#xff0c;用于连接 RGB …

【Linux】没有GDB,何谈Linux C

一、简单的开始 1、有C代码如下 #include <stdio.h>void main() {printf("Hello World!"); }2、通过gcc编译 生成带有调试信息的可运行程序&#xff0c;编译参数-g gcc -g hello.c -o hello3、运行GDB -q表示不打印gdb版本信息&#xff0c;界面较为干净 …

linux内核调试工具之kprobe

目录 一、内核调试的痛点 二、kprobe的优点 三、kprobe探测点的要点 四、探测点的开销与优化 五、内核配置 六、API 七、程序架构 八、实例 一、内核调试的痛点 内核调试&#xff0c;添加打印信息。在运行过程中想看某个函数的变量&#xff0c;需要重新编译内核。这样破…

【C语言】指针(进阶)

目录一、字符指针二、数组指针2.1、数组指针的定义2.2、&数组名和数组名2.3、数组指针的使用三、数组传参、指针传参3.1、一维数组传参3.2、二维数组传参3.3、一级指针传参3.4、二级指针传参四、函数指针五、函数指针数组六、指向函数指针数组的指针七、回调函数一、字符指…

【C语言小游戏】详解三子棋,深刻掌握二维数组

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#xff0c;今天带领大家实现一个C语言小游戏&#xff0c;主要运用的知识点为二维数组&#xff0c;希望这篇文章让大家对二维数组有更深刻的认识。 &#x1f49e;看似不起波澜的日复一日&#xff0c;会突然在某一天让人看到坚持…

【day14】【洛谷算法题】-P5711闰年判断-刷题反思集[入门2分支结构]

&#x1f338;大家好&#xff0c;我是花无缺&#xff0c;一枚热爱生活的新时代青年&#xff0c;感谢你的阅读&#x1f970;~ &#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专…

PC - 史上最简单的远程访问群晖 NAS 方法

文章目录1、下载安装cpolar群晖套件1.1 注册cpolar账号1.2 下载cpolar群晖套件1.3 安装cpolar群晖套件2、创建隧道映射5000端口2.1 打开cpolar群晖套件2.2 创建远程访问隧道2.3 获取公网URL地址3、公网远程群晖NAS教大家一个新手小白都可以轻松掌握的远程群晖NAS方法&#xff0…

算法的时间复杂度和空间复杂度

文章目录算法的时间复杂度和空间复杂度算法效率算法的复杂度时间复杂度时间复杂度的概念大O的渐进表示法常见的时间复杂度计算举例空间复杂度常见复杂度对比复杂度的oj练习算法的时间复杂度和空间复杂度 算法效率时间复杂度空间复杂度常见的时间复杂度以及复杂度的oj练习 算法…

【题解】方格取数

&#x1f60a;博主目前也在学习&#xff0c;有错误欢迎指正&#x1f60a; &#x1f308;保持热爱 奔赴星海&#x1f308; 文章目录一、题目1、题目描述3、原题链接二、解题报告1、思路分析2、代码详解三、本题知识一、题目 1、题目描述 输入格式&#xff1a; 输入的第一行为一…

Java并发编程实战之互斥锁

文章目录Java并发编程实战之互斥锁如何解决原子性问题&#xff1f;锁模型Java synchronized 关键字Java synchronized 关键字 只能解决原子性问题&#xff1f;如何正确使用Java synchronized 关键字&#xff1f;锁和受保护资源的合理关联关系死锁预防死锁破坏占有且等待条件破坏…

字节一面:TCP 三次握手,问的好细!

大家好&#xff0c;我是小林。 有位读者在面试字节时&#xff0c;被问到这么个问题&#xff1a; 概括起来&#xff0c;是这两个问题&#xff1a; TCP 三次握手中&#xff0c;客户端收到的第二次握手中 ack 确认号不是自己期望的&#xff0c;会发生什么&#xff1f;是直接丢弃…

1024程序员节:从关注自身健康开始

今天是1024程序员节&#xff0c;我们已经历经了尽三年的疫情&#xff0c;健康是我们最应该关注的事情&#xff0c;在这个特别的日子里&#xff0c;希望程序员们都能更加爱惜自己的身体&#xff0c;少加班&#xff0c;多锻炼。 健身不仅是保持健康体魄的关键要素之一&#xff0c…

基于ssm高校科研管理系统-计算机毕业设计源码+LW文档

【摘 要】高校科研管理是一项重要而又繁琐的工作&#xff0c;有效的信息管理平台可以大大缓解科研管理压力&#xff0c;减少工作量。本文以石河子大学信息科学与技术学院为应用背景&#xff0c;开发教师教学信息与论文信息交流平台。该系统能对科研成果和课题进行较为全面的管理…

第十三届蓝桥杯C++B组国赛I题——齿轮 (AC)

目录1.齿轮1.题目描述2.输入格式3.输出格式4.样例输入5.样例输出6.样例说明6.数据范围7.原题链接2.解题思路3.Ac_code1.齿轮 1.题目描述 这天, 小明在组装齿轮。 他一共有 nnn 个齿轮, 第 iii 个齿轮的半径为 rir_{i}ri​, 他需要把这 nnn 个齿轮按一定 顺序从左到右组装起来…

[附源码]Java计算机毕业设计SSM公司办公自动化系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

10个实用的CSS样式之悬浮卡片

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位喜欢写作&#xff0c;计科专业大三菜鸟 &#x1f3e1;个人主页&#xff1a;starry陆离 &#x1f4da;订阅专栏&#xff1a;『10个实用的CSS样式』 10个实用的CSS样式之悬浮卡片1.简介2.布局设计3.样式美化3.1body美化3.2c…

隔离技术之端口隔离

端口隔离技术 端口隔离主要应用在同一个vlan内&#xff0c;不同的用户之间不可互相访问 好处&#xff1a; 可以避免广播风暴&#xff0c;节约了vlan的资源&#xff0c;提高了用户之间的安全性 比如一个用户的电脑中病毒了&#xff0c;不会影响到其他用户 端口隔离是基于端口&…

网络原理——No.2 传输层_TCP的连接管理(画图理解三次握手与四次挥手)

JavaEE传送门JavaEE 网络原理——传输层_UDP 网络原理——No.1 传输层_TCP的确认应答机制与超时重传 目录TCP的连接管理三次握手(建立连接)四次挥手(断开连接)TCP的连接管理 描述的就是 TCP 建立链接和断开链接的过程 TCP 的链接, 只是一个 “逻辑上的” “虚拟的连接” (只要…

qt学习笔记4:QMainWindow 菜单栏、工具栏、状态栏、铆接部件、

在创建基类的时候&#xff0c;有三大选择&#xff0c;一个是QWidge 空窗口&#xff0c; 另一个就是QMainWindow QMainWindow是一个为用户提供主窗口的类&#xff0c;包含一个菜单栏&#xff0c;多个工具栏&#xff0c;多个链接部件&#xff0c; 一个状态栏以及一个中心部件&…

《数据结构》(六)八大排序(上)

生活中大家从小到大处处可见排队&#xff0c;但是排队有哪些快速的方法你了解吗&#xff1f; 八大排序排序的基本概念插入排序直接插入排序基本思想代码直接插入排序总结希尔排序基本思想代码希尔排序总结选择排序直接选择排序基本思想&#xff1a;代码直接选择排序总结堆排序堆…