Java 泛型的介绍

news2024/12/27 11:41:34

文章目录

  • 1.学习目标
  • 2.什么是泛型
  • 3.引入泛型
    • 语法
  • 4.泛型类的使用
    • 语法
    • 示例
  • 6.泛型的上界
    • 语法
    • 示例
  • 7.泛型的方法
    • 定义语法
    • 示例
  • 8.通配符
    • 通配符解决什么问题
    • 通配符上界
    • 通配符下界
  • 9.包装类
    • 基本数据类型和对应的包装类
    • 装箱和拆箱
    • 自动装箱和自动拆箱

1.学习目标

1.以能阅读 java 集合源码为目标学习泛型
2.了解泛型
3.了解通配符

2.什么是泛型


先来看下面的代码,如果要给方法传参,传递的参数必须是符合参数类型的。

public class Test {

    public static void func (int a) {

    }

    public static void main(String[] args) {
        func(10);
    }
}


因为上述代码中的 func 方法的参数是 int 类型,此时调用方法时传递的参数就必须是 int 类型的。

如果是其他类型的这里就会报错。



通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化;简单来说就是在给方法传参时,可以将一些不同类型的参数一起传递过去。

3.引入泛型


解决下面的问题:

实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值

此时如果将这个数据定义为具体的某一个类型,那么它就只能传递具体的某一个参数类型。
比方说,如果是一个 int 的数组,它就只能传递 int 类型的参数;如果是一个 char 类型的参数,那么它就只能传递 char 类型的参数…

解决办法就是,将这个数组定义为 Object 数组 即可,这是因为 Object 是所有类的父类,也就是所有都继承这个类,如下代码片段所示。

class MyArray {
    public Object[] objects = new Object[10];

    // 给数组添加元素
    public void setVal (int pos, Object val) {
        objects[pos] = val;
    }

    // 返回数组中某个下标的值
    public Object getVal (int pos) {
        return objects[pos];
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        // 调用方法给数组的 0 下标添加一个int类型的 10 元素
        myArray.setVal(0, 10);
        // 给数组 1 下标添加一个 String 类型的 “hello” 元素
        myArray.setVal(1, "hello");
    }
}


按照上述代码中调用 setVal 方法就可以给数组添加元素。


在调用 getVal 方法返回某个下标的的值时代码报错了,鉴于上述情况可以得出结论:

1、任何类型的数据都可以存放。
2、1号下标本身就是字符串,但是取数据的时候编译却报错了。此时必须进行强制类型转换



可以看到强转后就不报错了,按照上述的情况,必须要看看下标元素是什么类型后再取出,
这就比较不合适了。

虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型。
而不是同时持有这么多类型。因此,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。
此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。


接下来修改代码使用 泛型 来解决这个问题。

class MyArrays<T> {
    public T[] objects = (T[])new Object[10];

    // 给数组添加元素
    public void setVal (int pos, T val) {
        objects[pos] = val;
    }

    // 返回数组中某个下标的值
    public T getVal (int pos) {
        return objects[pos];
    }
}

public class Work {

    public static void main(String[] args) {
        // 此时指定的是 Integer 类型,只能传递整数
        MyArrays<Integer> myArrays = new MyArrays<Integer>();
        myArrays.setVal(0, 10);
        myArrays.setVal(1, 20);
    }
}


< T > : 相当于是一个占位符,表示当前类是一个泛型类

括号里的字母不一定是 T,也可以别的,但是我们习惯括号里的字母可以将我们想要表达的意思表示出来,T 和 E 一般用来表示类型。

main 函数里的 < Intrger>表示当前只能传递 Integer 类型的参数,也就是只能传递整数。

如果此时要传递 Integer 类型以外的参数。


可以看到代码此时会报错。

如果要取出指定下标元素的值定义一个变量接收方法返回的值即可。

 int ret = myArrays.getVal(1);



根据输出的结果可以判断出的确得到了下标为 1 的元素的值。


如果想要传递其他类型的参数,更改 <> 中的类型即可。

public static void main(String[] args) {
    MyArrays<String> myArrays1 = new MyArrays<String>();
    myArrays1.setVal(2, "hello");
    myArrays1.setVal(3, "world");
    String retString = myArrays1.getVal(2);
    System.out.println(retString);
}


可以看到得到的结果也是正确的。

这就是泛型!!!


泛型存在的两个最大的意义:

1、存放元素的时候,会进行类型的检查。

2、取出元素的时候,会自动进行类型转换,不在需要类型的强转了。

泛型只要是编译时期的一种机制,这种机制叫做 擦除机制,而运行的时候是没有泛型概念的。

有关泛型擦除机制的文章介绍:擦除机制的文章介绍

语法

class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}

class ClassName<T1, T2, ..., Tn> {
}

class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
    // 这里可以使用类型参数
}

class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
    // 可以只使用部分类型参数
}


一个泛型类的参数列表可以指定多个类型,也就是 <> 里可以有多个字母,

如下图所示:

4.泛型类的使用

语法

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

示例

MyArray<Integer> list = new MyArray<Integer>();


注意:泛型只能接受类,所有的基本数据类型必须使用包装类!,也就是<> 里只能是 类类型,不能是基本类型。

6.泛型的上界


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

语法

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

示例

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


只接受 Number 的子类型作为 E 的类型实参。

MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型


实现一个泛型类,求一个数组中的最大值

class MaxVal<T extends Comparable<T>> {
    public T findMax (T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max.compareTo(array[i]) < 0 ) {
                max = array[i];
            }
        }
        return max;
    }
}

public class MaxValue {

    public static void main(String[] args) {
        MaxVal<Integer> maxVal = new MaxVal<>();
        Integer[] array = {1,2,3,4,5};
        Integer ret = maxVal.findMax(array);
        System.out.println(ret);
    }
}



可以输出结果正确。

7.泛型的方法

定义语法

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

示例

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

8.通配符


? 用于在泛型的使用,即为通配符。

通配符解决什么问题


通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List 也应该是 List 的子类,但是泛型是不支持这样的父子类关系的。

泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围。

class Message<T> {
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}

public class Test3 {
    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());
    }
}



以上是程序的输出结果。


以上程序会带来新的问题,如果现在泛型的类型设置的不是String,而是Integer。


可以看到程序会报错,因为 fun 方法被要求是 String 类型的。

解决办法就是引入 通配符 的概念,直接在代码中写入 ? 即可。

class Message<T> {
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class Test3 {

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


可以看到改写好的代码,就没有报错了。

此时的问号表示的含义是程序也不知道此时的类型是什么类型。


此时就得到了正确的结果。

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

? extends 类:设置泛型上限
? super 类:设置泛型下限

通配符上界

语法

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


示例

class Food {

}
class Fruit extends Food {

}

class Apple extends Fruit {

}

class Banana extends Fruit {

}

class Plate<T> { // 设置泛型上限
    private T plate ;

    public T getPlate() {
        return plate;
    }

    public void setPlate(T plate) {
        this.plate = plate;
    }
}

public class TestDemo {

    public static void main(String[] args) {
        // 可以看到p1 与 p2 调用 fun 方法后都是正确的
        Plate<Apple> plate1 = new Plate<>();
        plate1.setPlate(new Apple());
        fun(plate1);

        Plate<Banana> plate2 = new Plate<>();
        plate2.setPlate(new Banana());
        fun(plate2);
    }

    // 此时继承的是 Fruit 类,表名 fun 方法可以调用 Fruit、Apple or Banana
    public static void fun(Plate<? extends Fruit> temp){

    }
}


根据上面代码可以看到,plate1 设置为只能放 Apple,而 plate2 设置为了只能放 Banana。
实际上 fun 继承了 Fruit 类,在调用方法的时候就决定了可以是 Fruit、Apple、Banana其中一个。
因为 Apple 与 Banana 是 Fruit 的子类。

此时通过 fun 方法是不能往里面放元素的。


站在 fun 方法的角度考虑,此时他有可能接收的 Apple 也有可能接收的是 Banana,因此也就不可以往里面放元素。

取元素的时候是没问题的,但是放元素的时候是有问题的。因为取出的元素一定是 Fruit 或者它的子类,但是放进去的是什么就不能确定了。就好像可以说苹果是水果,但是不能说水果是苹果一样。

 public static void fun(Plate<? extends Fruit> temp){
     Fruit fruit = temp.getPlate();
 }


需要注意的是在这里的代码只能用 Fruit 来引用,还是因为具体是什么类型是不确定的。


可以看到是错的,因此通配符的上界,不能进行写入数据,只能进行读取数据

通配符下界

语法

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

示例

class Food {

}

class Fruit extends Food {

}

class Apple extends Fruit {

}

class Banana extends Fruit {

}

class Plate<T> { 
    private T plate ;

    public T getPlate() {
        return plate;
    }

    public void setPlate(T plate) {
        this.plate = plate;
    }
}

public class TestDemo {

    public static void fun(Plate<? super Fruit> temp){
        temp.setPlate(new Apple());
        temp.setPlate(new Banana());
        temp.setPlate(new Fruit());
    }
    
    public static void main(String[] args) {
        Plate<Fruit> plate1 = new Plate<>();
        plate1.setPlate(new Fruit());
        fun(plate1);

        Plate<Food> plate2 = new Plate<>();
        plate2.setPlate(new Food());
        fun(plate2);   
    }
}


下界和上界正好是相反的,可以放元素但是不可以取元素。因为不能确定这里存放的是什么。


可以看到取出的时候报错了。

下界这里方法调用的时候只能传递 Fruit 或者 它的父类作为参数。


可以看到传递 Fruit 的子类是出错了。

9.包装类


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

基本数据类型和对应的包装类



需要注意的是除了圈出的两个,其他的包装类都是首字母大写。

装箱和拆箱

int i = 10;

// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer ii = Integer.valueOf(i);
Integer ij = new Integer(i);

// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j = ii.intValue();

自动装箱和自动拆箱

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

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

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

相关文章

EventLoop事件循环

JavaScript是单线程语言 单线程执行任务队列的问题: 如果前一个任务非常耗时,则后续任务不得不等待,从而导致程序假死的问题。 同步任务和异步任务 ​ 为了防止某个耗时任务程序导致假死的问题,javascript把待执行的任务分为两类: 同步任务(synchronous) 又叫非耗时任务,指…

vue3-实战-02-管理后台项目集成

目录 1-集成element-plus 2-src别名配置 3-环境变量配置 4-svg图标配置 4.1-svg插件安装配置 4.2-svg封装为全局组件 5-集成sass 6-mock数据 7-axios二次封装 上一篇文章记录了项目初始化和项目配置&#xff0c;本章我们来进行项目集成。 1-集成element-plus 本次管…

【PWN · 总结】system返回shell(‘/bin/sh‘、‘sh‘、$(0))

pwn题中要通过system/excute等返回shell&#xff0c;进而cat flag。今天遇到一题&#xff0c;参数$(0)也可返回&#xff0c;有必要记录一下。 目录 前言 一、/bin/sh 1.strings 2.IDA 3.pwntools 4.ROPgadget 5.libc中寻找 二、sh 三、$(0) exp IDA查看机器码 …

交换机的4种网络结构方式:级联方式、堆叠方式、端口聚合方式、分层方式

交换机是计算机网络中重要的网络设备之一&#xff0c;用于实现局域网&#xff08;LAN&#xff09;内部的数据转发和通信。交换机可以采用不同的网络结构方式来满足不同的网络需求和拓扑结构。本文将详细介绍交换机的四种网络结构方式&#xff1a;级联方式、堆叠方式、端口聚合方…

c语言编程练习题:7-112 约分最简分式

#include <stdio.h> int gcd(int a,int b) {if(a<b)return gcd(b,a);if(a%b0)return b;elsegcd(b,a%b); } int main(){int a,b;if (scanf("%d/%d",&a,&b)!EOF){// 分析不敲钟的时间int gcd_value gcd(a,b);printf("%d/%d",a/gcd_value,b…

DMBOK知识梳理for CDGA/CDGP——第二章 数据处理伦理

关 注gzh “大数据食铁兽” 回复“知识点”获取《DMBOK知识梳理for CDGA/CDGP》常考知识点&#xff08;第二章数据处理伦理&#xff09; 第二章 数据处理伦理 第二章在 CDGA考试中分值占比不高&#xff0c;CDGP考试不考核。主要侧重点是考概念性的知识&#xff0c;难度较…

Matlab进阶绘图第22期—不等宽柱状图

不等宽柱状图是一种特殊的柱状图。 与常规柱状图相比&#xff0c;不等宽柱状图可以通过柱高与柱宽分别表达两个维度的数据&#xff0c;因此在多个领域得到应用。 在《Matlab论文插图绘制模板第91期》中&#xff0c;虽有介绍过利用Matlab自带bar函数绘制不等宽柱状图的方法&am…

LDGRB-01 3BSE013177R1 将数字输入和继电器输出结合

LDGRB-01 3BSE013177R1包的一部分是全面的通信选项&#xff0c;包括Modbus主/从或CS31&#xff0c;这种产品很少提供。128kB的用户内存和0.1秒/指令的程序处理时间只是AC500-eCo令人印象深刻的性能的两个例子。除了与现有AC500系列的互操作性&#xff0c;AC500-eCo系统还使用基…

PMP-范围管理的重要性

本篇文章主要是方便从事项目管理的“初学者”们了解范围管理的重要性 一、什么是范围管理 项目范围管理包括确保项目做且只做所需的全部工作&#xff0c;以成功完成项目的各个过程。管理项目范围主要在于定义和控制哪些工作应该包括在项目内&#xff0c;哪些不应该包括在项目内…

推动创意成果:创意过程如何改变 2023 年的业务

时下全球经济放缓&#xff0c;很多企业业务都陷入了增长瓶颈&#xff0c;如何突破&#xff1f;今天我们来聊聊一般业务突破的3种方式&#xff1a; 发现一片新的蓝海&#xff0c;迅速杀入占领生态位&#xff08;快&#xff09;&#xff1b; 发现结构性红利机会&#xff0c;大量投…

低代码开发重要工具:jvs-rules 规则引擎功能介绍(二)

一、JVS规则引擎接口管理 接口管理是将逻辑引擎对接所有的接口进行统一管理&#xff0c;便于各种应用统一引用。 接口的界面展示 接口列表的展示&#xff0c;接口列表是根据系统内部支持的接口进行展现&#xff0c;包括查看接口的详情 详情中包括&#xff0c;入参与出参的展…

QT(QPainter画圆弧)

文章目录 前言一、QPainter画圆弧方法二、使用示例三、画一个彩色环总结 前言 本篇文章给大家介绍一下如何使用QPainter来画圆弧。 一、QPainter画圆弧方法 drawArc() 函数是 Qt 绘图类库中的一个函数&#xff0c;用于在画布上绘制圆弧。可以通过设置起点角度和圆弧弧度来控…

Keil_C51之Memory_mode解析

有时我们会遇到这样的报错&#xff1a; *** ERROR L107: ADDRESS SPACE OVERFLOW SPACE: DATA SEGMENT: _DATA_GROUP_ LENGTH: 002CH Program Size: data117.0 xdata0 code6242 Target not created. Build Time Elapsed: 00:00:00 报错截图如下&#xff1a; 这类报…

程序员怎么玩转Linux

Linux下可以使用Python编写爬虫程序&#xff0c;常用的爬虫框架有Scrapy和BeautifulSoup。 Scrapy是一个基于Python的开源网络爬虫框架&#xff0c;可以快速高效地从网站上获取数据。它提供了强大的数据提取和处理功能&#xff0c;支持异步网络请求和分布式爬取等特性。 Beau…

九耶丨阁瑞钛伦特-springmvc(五)

Spring是最流行的企业级开发框架之一&#xff0c;它提供了一套完整的IOC&#xff08;控制反转&#xff09;和AOP&#xff08;面向切面编程&#xff09;解决方案&#xff0c;可以帮助软件开发人员快速开发出高效、可扩展的应用程序。Spring MVC是Spring框架中的一个模块&#xf…

cesium模型在前端页面加载后亮度异常的处理

一.问题产生与分析 通常情况下&#xff0c;后端生成的模型数据&#xff0c;以默认参数加载在前端页面时亮度能显示是正常的&#xff0c;如果不正常&#xff0c;猜测可能与生成的原数据有关&#xff08;数据本身在前端页面加载亮度就低&#xff09; 二.问题解决 前端页面针对这…

OpenHarmony 3.2 Release新特性解读之驱动HCS

OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;开源社区&#xff0c;在今年4月正式发布了OpenHarmony 3.2 Release版本&#xff0c;标准系统能力进一步完善&#xff0c;提升了系统的整体性能、稳定性和安全性。此次版本对于驱动也提供了一些新的特性&am…

ELK高可用集群添加用户名密码认证

确定主节点 GET _cat/nodes?v带*的就是主节点&#xff0c;这里是zcpt-prd-ELK-01节点 生成根证书elastic-stack-ca.p12&#xff08;进入主节点&#xff09; 注意操作es7.7.0使用es用户 su es进入主节点的bin目录下执行 ./elasticsearch-certutil ca会要求输入密码直接回车…

Structural Deep Clustering Network

Structural Deep Clustering Network | Proceedings of The Web Conference 2020 (acm.org) 目录 Abstract 1 Introduction 2 Model 2.1 KNN Graph 2.2 DNN Module 2.3 GCN Module 2.4 Dual Self-Supervised Module Abstract 深度聚类方法通常是通过深度学习强大的表示…

为什么很多人做项目经理多年都没有进步?

为什么很多人做项目经理多年都没有进步&#xff1f; 项目经理是一个需要不断学习和成长的职业。然而&#xff0c;很多人在做了多年的项目经理后却没有看到自己的进步。这是为什么呢&#xff1f;以下是一些可能的原因&#xff1a; 1. 缺乏自我反思和改进意识 有些人可能会在自…