泛型的介绍以及原理

news2025/1/23 2:13:38

目录

一、前言

二、什么泛型

三、为什么要使用泛型

3.1、保证了类型的安全性。

3.2、消除强制转换

3.3、提高程序的性能

3.4、 提高了代码的重用性

四、如何使用泛型

4.1、 泛型类

4.2、泛型接口

4.3、泛型方法

五、泛型通配符

5.1、无边界的通配符

5.2、固定上边界的通配符

5.3、固定下边界的通配符

六、泛型的实现原理

6.1、配置JAD

6.2、反编译分析


一、前言

泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它。

毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课

二、什么泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

三、为什么要使用泛型

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

3.1、保证了类型的安全性。

比如:没有泛型的情况下使用集合:

public static void test1(){
        ArrayList arr = new ArrayList<>();
        arr.add(1);
        arr.add(new Object()); //编译正常
    }

在不指定泛型类型时候,arr可以add任何元素,但是显然这不是我们想要的结果,因为这样就导致在遍历arr的时候,我们不知道下一个元素会是什么类型。

当我们使用泛型指定类型时,编译不通过

 相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,使得程序更加安全,增强了程序的健壮性。

3.2、消除强制转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。

不使用泛型的时候:

public static void test1() {
        ArrayList arr = new ArrayList<>();
        arr.add(1);
        Object o = arr.get(0);//获取出来不知道是什么类型,就算你知道是int,在使用的时候需要进行转换 
        int o1 = (int) arr.get(0);

    }

使用泛型的时候:

3.3、提高程序的性能

在非泛型编程中,将简单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。

泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作:

object a = 1;//由于是object类型,会自动进行装箱操作。
 
int b = (int) a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。

3.4、 提高了代码的重用性

如何提高的显而易见,假如ArrayList不使用泛型,你自己想吧~。~

四、如何使用泛型

泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。

4.1、 泛型类

格式:public class 类名 <泛型类型1,...> { }

例如:public class GenericClass<ab,a,c> {}

当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表示:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  •  - 表示不确定的 java 类型
package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass <T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

4.2、泛型接口

格式:public <泛型类型> 返回类型 方法名(泛型类型 变量名) { }

方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用run()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:39
 * @Des:
 */
public interface GenericInterface<T> {
    void run(T value);
}

class StringImpl implements GenericInterface<String> {

    @Override
    public void run(String value) {
        System.out.println(value);
    }
}

class IntegerImpl implements GenericInterface<Integer> {

    @Override
    public void run(Integer value) {
        System.out.println(value);
    }
}

4.3、泛型方法

定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

package com.cjian.generic;

import java.util.ArrayList;

/**
 * @Author: cjian
 * @Date: 2023/5/30 9:51
 * @Des: 使用泛型打印不同类型的数组
 */
public class Demo {
    public static void main(String[] args) {
        String[] strings = {"a", "b", "c"};
        Integer[] integers = {1, 2, 3};
        Double[] doubles = {1.1, 2.2, 3.3};

        printArr(strings);
        printArr(integers);
        printArr(doubles);
        test1();
    }

    public static <E> void printArr(E[] arr) {
        for (E e : arr) {
            System.out.printf("%s ", e);
        }
        System.out.println();
    }

    public static void test1() {
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(1);
        int o1 = arr.get(0);
    }
}

五、泛型通配符

//表示类型参数可以是任何类型
public class GenericClass<?>{}
 
//表示类型参数必须是A或者是A的子类
public class GenericClass<T extends A>{}
 
//表示类型参数必须是A或者是A的超类型
public class GenericClass<T supers A>{}

5.1、无边界的通配符

        就是<?>, 比如List<?>

       无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.

package com.cjian.generic;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:47
 * @Des:
 */
public class GenericTest {
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        getData(name);
        getData(age);
        getData(number);

    }

    public static void getData(List<?> data) {
        System.out.println("data :" + data.get(0));
    }
}

5.2、固定上边界的通配符

        采用<? extends E>的形式

        使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。

要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界。

注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass<T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    // 只能接收Number及其Number的子类
    public static void printNum(GenericClass<? extends Number> temp) {
        System.out.print(temp + "、");
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

5.3、固定下边界的通配符

        采用<? super E>的形式

        使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。

要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界.。

注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。

 

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass<T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    // 只能接收String或Object类型的泛型,String类的父类只有Object类
    public void printString(GenericClass<? super String> value) {
        System.out.print(value);
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

六、泛型的实现原理

泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。

Java 编译器通过如下方式实现擦除:

  • 用 Object 或者界定类型替代泛型,产生的字节码中只包含了原始的类,接口和方法;
  • 在恰当的位置插入强制转换代码来确保类型安全;
  • 在继承了泛型类或接口的类中插入桥接方法来保留多态性。

6.1、配置JAD

使用jad反编译Generic.classs

下载地址:JAD Java Decompiler Download Mirror

下载完毕后,新建一个系统变量

 然后添加到Path:

 cmd输入jad出现下面的代表安装成功:


 

6.2、反编译分析

 来到class文件所在目录,进行反编译:

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass<T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    // 只能接收Number及其Number的子类
    public static void printNum(GenericClass<? extends Number> generic) {
        System.out.print(generic.getType());
    }

    // 只能接收String或Object类型的泛型,String类的父类只有Object类
    public void printString(GenericClass<? super String> generic) {
        System.out.print(generic.getType());
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

 提示不用管,当前路径下已经生成了Generic.jad文件,打开:

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   GenericClass.java

package com.cjian.generic;

import java.io.PrintStream;

public class GenericClass
{

    public Object getType()
    {
        return type;
    }

    public void setType(Object type)
    {
        this.type = type;
    }

    public GenericClass(Object type)
    {
        this.type = type;
    }

    public static void printNum(GenericClass generic)
    {
        System.out.print(generic.getType());
    }

    public void printString(GenericClass generic)
    {
        System.out.print(generic.getType());
    }

    public static void main(String args[])
    {   
        GenericClass g1 = new GenericClass("string");
        // 合适的地方静心类型强转
        System.out.println((String)g1.getType());
        GenericClass g2 = new GenericClass(Integer.valueOf(1));
        System.out.println(g2.getType());
    }

    // 使用Object替代泛型
    private Object type;
}

发现编译器擦除 GenericClass类后面的两个尖括号,并且将 type的类型定义为 Object 类型,且在使用的地方进行了类型强转

那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extends和super语法的有界类型,如:
 

public class Generic<T extends String> {
    private T num;
}

这种情况的泛型类型,num 会被替换为 String 而不再是 Object。

再看下插入桥接方法的场景:

 Comparable.java:

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 13:54
 * @Des:
 */
public interface Comparable<T> {
    int compareTo(T obj);
}

 User.java

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 13:58
 * @Des:
 */
public class User implements Comparable<User> {
    private String name;

    @Override
    public int compareTo(User other) {
        return this.name.compareTo(other.name);
    }
}

Comparable.jad

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Comparable.java

package com.cjian.generic;


public interface Comparable
{

    public abstract int compareTo(Object obj);
}

 User.jad

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   User.java

package com.cjian.generic;


// Referenced classes of package com.cjian.generic:
//            Comparable

public class User
    implements Comparable
{

    public User()
    {
    }

    public int compareTo(User other)
    {
        return name.compareTo(other.name);
    }

    // 插入桥接方法来保留多态性
    public volatile int compareTo(Object obj)
    {
        return compareTo((User)obj);
    }

    private String name;
}

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

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

相关文章

telnet 120.XX8888会超时或者无反应,防火墙加入8888,安全组也加入8888,但是访问120.XX:8888也没有反应

⚠️命令都是远程登录的哦 选这个远程连接&#xff1a; 初次密码自己设置别忘了 1、看是否8888端口是否有正常监听 netstat -ntlp我这边清楚看到没有8888 2、如果没有监听是无法连接&#xff0c;需要安装对应的程序同时监听8888端口 比如我想用宝塔面板 我就需要下载宝塔面…

一步步入门编写PHP扩展

1、写在最前 随着互联网飞速发展&#xff0c;lamp架构的流行&#xff0c;php支持的扩展也越来越多&#xff0c;这样直接促进了php的发展。 但是php也有脚本语言不可避免的问题&#xff0c;性能比例如C等编译型语言相差甚多&#xff0c;所以在考虑性能问题的时候最好还是通过php…

00后实在太强了,98年的我被卷废了,太离谱了...

前言 最近在公司我真的感受到了什么叫“卷”&#xff0c;以往的我划划水日子过的轻轻松松&#xff0c;直到公司最近招了一个00后进来&#xff0c;真的让我感受到了危机&#xff0c;刚进来工资就和我差不多&#xff0c;我刚开始其实有点不太舒服&#xff0c;凭什么我辛辛苦苦干…

在pycharm中调用qt界面功能

目录 一、新建designer文件 1、打开pycharm中的designer 2、创建个widget 3、拖动几个简单按钮 4、保存一下 5、右击test1.ui 这边首先环境已经配置完毕&#xff0c;可以参考之前写的博客&#xff1a; 关于PyQt5的环境搭建_Littlehero_121的博客-CSDN博客 一、新建desi…

如何使用PHM技术提高汽车工业的效率和性能?

在汽车工业中&#xff0c;预测性健康管理&#xff08;PHM&#xff09;技术正日益受到关注。作为一种基于数据驱动的解决方案&#xff0c;PHM技术通过实时监测和分析设备和系统的状态&#xff0c;实现对设备健康状况的预测和管理。 图.汽车制造&#xff08;iStock&#xff09; 汽…

代码示范【FabEdge v0.8.0】配置 connector 公开端口

FabEdge项目简介&#xff1a; FabEdge是博云在2021年8月发起&#xff0c;基于Kubernetes 构建的专注于边缘计算场景的容器网络方案&#xff0c;支持 KubeEdge 、SuperEdge、OpenYurt 等主流边缘计算框架。旨在解决边缘计算场景下容器网络配置管理复杂、网络割裂互不通信、缺少…

hadoop单机版部署

1.下载hadoop wget --no-check-certificate https://mirrors.bfsu.edu.cn/apache/hadoop/common/hadoop-3.3.1/hadoop-3.3.1.tar.gz 2.解压重命名 tar -zxvf hadoop-3.3.1.tar.gz mv hadoop-3.3.1.tar.gz hadoop 3.编辑hosts vim /etc/hosts 172.17.1.1 hadoop925 4.进入配置…

4.Apache网页优化

文章目录 Apache网页优化网页压缩网页缓存隐藏版本信息Apache防盗链 Apache网页优化 Apache网页优化 网页压缩网页缓存 Apache安全优化 隐藏版本信息配置防盗链 网页压缩 配置Apache的网页压缩功能&#xff0c;是使用gzip压缩算法来对网页内容进行压缩后再传输到客户端浏览器…

LNMT架构之反向代理负载均衡

目录 一、实验前提环境配置 &#xff08;一&#xff09;关闭防火墙&#xff0c;安装本地yum &#xff08;二&#xff09;部署tomcat &#xff08;三&#xff09;部署Mariadb &#xff08;四&#xff09;部署nginx 二、反向代理负载均衡 方法一&#xff1a;&#xff08;轮…

【算法学习系列】07 - 无序数组中的局部最小值问题

文章目录 说明约束条件简单说下思路解决方案随机无序数组样本生成器算法实现验证代码进行大样本随机测试验证算法正确性 说明 在算法中&#xff0c;局部最小值是指一个函数在一个局部范围内的最小值。 具体而言&#xff0c;如果一个函数在一个小区间内的取值都比该区间内的其他…

C++:STL--priority_queue

文章目录 一.STL设计思想:容器适配器STL--stack的代码设计STL--queue的代码设计stack和queue的默认容器适配器deque的数据结构解析deque的存储结构示意图 二.C仿函数仿函数示例 三.STL--priority_queue(优先级队列)1.C优先级队列的数据结构2.priority_queue的实现框架比较函数(…

chatgpt赋能python:Python中创建画布的函数——matplotlib

Python中创建画布的函数——matplotlib Python作为一种强大的编程语言&#xff0c;拥有许多重要且广泛应用的模块和库。其中&#xff0c;matplotlib是一种用于制作高质量的图形和图表的库&#xff0c;而创建画布的函数便是其基础功能之一。 什么是matplotlib&#xff1f; Ma…

C语言---初始C语言

1、初始C语言 1、编译器主要有&#xff1a;Clang、GCC、WIN-TC、MSVC、Turbo C等 什么是编译&#xff1f; test.c----------------------------->test.exe 这个过程需要经过编译、链接等过程&#xff0c;而众多编译器实现的功能就是把我们写的test.c进行编译。 2、VS20…

如何把“困在”内网的数据释放,进行安全的流转传输呢?

互联网大时代&#xff0c;数据的生产使用与互联网紧密相关&#xff0c;但数据安全和网络安全却既有联系又互不相同。数据安全和网络安全的突出区别是核心主体不同&#xff0c;数据安全关注的数据全生命周期的安全&#xff0c;而网络安全则是侧重保障网络体系和网络环境的安全性…

硬卷完了!低代码打怪升级进阶成神之路(2023年最新版)

一、背景 应用开发周期长一直是IT部门和业务部门面临的问题。 IT部门总是被新的应用需求弄得不堪重负。他们不可能完成业务部门想要完成的每一个项目。同时&#xff0c;业务部门的用户厌倦了等待&#xff0c;并开始完全绕过IT部门。 今天&#xff0c;我们来探索一下“低代码开发…

制药企业高效过滤器检漏参考法规、方法及操作步骤

对制药企业来讲&#xff0c;高效过滤器检漏主要是现场检漏&#xff0c;通过DOP法来发现滤器本身及运输、安装过程中可能存在的问题。常使用气溶胶光度计及多分散气溶胶进行检漏。依据的标准是2010药品GMP指南(测试方法采用ISO14644-3)。 对于制药企业来说&#xff0c;高效过滤器…

自动驾驶TPM技术杂谈 ———— 边缘检测

文章目录 介绍边缘检测与微分运算离散信号的差分滤波Robert算子Prewitt算子Sobel算子拉普拉斯算子 介绍 计算机视觉&#xff08;Computer Vision&#xff0c;CV&#xff09;是一门使用计算机模拟生物视觉的学科&#xff0c;目的是使用计算机代替人眼实现对目标的识别、分类、跟…

3.2. 数学类(Math、BigInteger、BigDecimal)

1. Math类 Math类提供了一些基本的数学函数&#xff0c;如求平方根、绝对值、三角函数等。它是一个final类&#xff0c;并且所有的方法都是static的&#xff0c;因此无需创建对象&#xff0c;直接使用类名调用方法即可。 以下是Math类的一些常用方法&#xff1a; abs(double…

抖音seo源码-抖音搜索源码-抖音下拉词-抖音关键词排名系统搭建

为了优化抖音平台上的内容&#xff0c;开发抖音关键词排名系统成为了必要的措施。该系统可以针对搜索结果和下拉词进行分析&#xff0c;为用户提供更准确的搜索结果。为实现这一目标&#xff0c;开发团队进行了大量的市场调查和用户研究。 在开发过程中&#xff0c;团队利用了…

mysql8+忘记密码的详细解决方法

mysql8忘记密码的详细解决方法 不同的版本&#xff0c;可能处理的方式不一样&#xff0c;这里说一下8以上的版本处理密码忘记的问题&#xff0c;windows系统。 一.问题&#xff1a; 太久没用mysql &#xff0c;忘记了原先的root密码 二&#xff1a;解决 1.关闭mysql服务,我的…