一文讲懂泛型

news2024/9/28 9:20:58

Java高级

  • Java高级语言特性
    • 一. 泛型
      • 1. 1 为什么我们需要泛型
      • 1. 2 泛型类和泛型接口的定义
      • 1. 3 泛型方法
      • 1. 4 限定类型变量
      • 1. 5 泛型中的约束和局限性
      • 1. 6 泛型中的继承规则
      • 1. 7 通配符类型
        • 1.7.1 问题抛出,为啥需要通配符?
        • 1.7.2 `? extends X`
        • 1.7.3 `? super X`
        • 1.7.4 无限定通配符
      • 1. 7 虚拟机是如何实现泛型的?

Java高级语言特性


一. 泛型


1. 1 为什么我们需要泛型

  • 功能1:适用于多种类型数据,执行相同的代码。
  • 功能2:在编码的时候,可以对这一段功能代码的数据类型进行指定,不需要强制类型转换。

1. 2 泛型类和泛型接口的定义

  • 参数化类型,将所需要传入的数据类型参数化了。比如:int、double、float都可以看成一种数据类型,可以使用泛型代替他们所有。

泛型类的使用

package com.itheima.reggie.genmenthod;

//这里定义的是T,说明了这里传入的参数T可以是任何的类型,需要什么类型,就可以设置成什么类型
public class NormalGeneric<T> {
    private T data;
    
    
    //构造函数
    public NormalGeneric(){
        this.data = data;
    }
    
    //get函数
    public T getData(){
        return data;
    }
    
    //set函数
    public void setData(T data){
        this.data = data;
    }

    public static void main(String[] args) {
    	//这里注意:这里的泛型T指定为String类型,这就实现了功能2,对一段功能代码进行指定参数类型。
        NormalGeneric<String> normalGeneric = new NormalGeneric<>();
        normalGeneric.setData("ok");
        System.out.println(normalGeneric.getData());
    }
}

泛型接口的使用

泛型接口1.

//不需要指定所实现的方法的具体类型
package com.itheima.reggie.genmenthod;

public class ImplGenerator<T> implements Generator<T>{
    public T next(){
        return null;
    }
}

泛型接口2.

package com.itheima.reggie.genmenthod;

public class ImplGenerator2 implements Generator<String>{

    @Override //大家可以看到,当使用的泛型接口被指定了为某一个类型的时候,继承类中的方法就得使用所指定的类型 
    public String next(){
        return null;
    }
}


1. 3 泛型方法

普通方法:

在这里插入图片描述

这里就是一个普通的方法,因为只是利用了泛型类中的泛型,而不是泛型方法的泛型

泛型方法:
在这里插入图片描述
泛型方法加强:

package cn.enjoyedu.generic.defgeneric.genmethod;

/**
 * 类说明:
 */
public class GenericMethod3 {
    static class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    static class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    static class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    static class GenerateTest<T>{
        //普通方法
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。
        //可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,
        //编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,
        //注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        //定义GenerateTest中的T是Fruit类型的
        GenerateTest<Fruit> generateTest = new GenerateTest<>();
        generateTest.show_1(apple);
        //generateTest.show_1(person);

        
        //相当于show_2和Fruit是没有关系的了
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //这里的show_3和show_2实际上是一样的
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}


泛型类中定义的泛型仅仅影响类中的普通的方法,对泛型方法是没有影响的。


1. 4 限定类型变量


  • 有时候,我们需要对类型变量加以约束,比如计算两个变量的最小,最大值。


    在这里插入图片描述

  • 请问,如果确保传入的两个变量一定有compareTo方法?那么解决这个问题的方案就是将T限制为实现了接口Comparable的类,T extends Comparable中:

在这里插入图片描述

  • T表示应该绑定类型的子类型,Comparable表示绑定类型,子类型和绑定类型可以是类也可以是接口。
    如果这个时候,我们试图传入一个没有实现接口Comparable的类的实例,将会发生编译错误。

在这里插入图片描述

  • 同时extends左右都允许有多个,如 T,V extends Comparable & Serializable。
  • 注意限定类型中,只允许有一个类,而且如果有类,这个类必须是限定列表的第一个。
  • 这种类的限定既可以用在泛型方法上也可以用在泛型类上。

1. 5 泛型中的约束和局限性


  • 现在我们有泛型类:
public class Restrict<T> {   泛型类
  • 不能用基本类型实例化类型参数:
Restrict<double>   这种是不可行的
Restrict<Double> restrict = new Restrict<>();   这种是可以的,必须要大写才行
  • 运行时类型查询只适用于原始类型
if(restrict instanceof  Restrict<Double>)   这种是不可行的  
if(restrict instanceof  Restrict<T>)   这种是不可行的 


Restrict<String> restrictString= new Restrict<>();   这是可行的

System.out.println(restrict.getClass()==restrictString.getClass());   true
System.out.println(restrict.getClass().getName());
System.out.println(restrictString.getClass().getName());

  • 不能实例化类型变量
 public Restrict() {   
    this.data = new T();   不能实例化类型变量,这种是不可以的
 }
  • 静态域或者方法里不能引用类型变量
 private static T instance;    这种是不可以的 
 
 private static <T> T getInstance(){};   这种是可以的
  • 不能创建参数化类型的数组(可定义,但是不能初始化)
Restrict<Double>[] restrictArray;   这是可以的

Restrict<Double>[] restricts = new Restrict<Double>[10];   这是不可以的
  • 泛型不能继承Exception/Throwable
private class Problem<T> extends Exception;  这是不行的

    /*不能捕获泛型类对象*/
//    public <T extends Throwable> void doWork(T x){
//        try{
//
//        }catch(T x){
//            //do sth;
//        }
//    }



这是可行的

    public <T extends Throwable> void doWorkSuccess(T x) throws T{
        try{

        }catch(Throwable e){
            throw x;
        }
    }




1. 6 泛型中的继承规则


  • Pair<Employee>Pair<Worker>没有继承关系

  • 泛型类可以继承或者扩展其他泛型类,比如ListArrayList

Employee:

package cn.enjoyedu.generic.inherit;

/**
 * @author 
 */
public class Employee {
    private String firstName;
    private String secondName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }
}

Worker继承至Employee:

package cn.enjoyedu.generic.inherit;

/**
 * @author Mark老师   
 */
public class Worker extends Employee {
}

功能类Pair:

package cn.enjoyedu.generic.inherit;

/**
 * @author 
 */
public class Pair<T> {

    private T one;
    private T two;

    public T getOne() {
        return one;
    }

    public void setOne(T one) {
        this.one = one;
    }

    public T getTwo() {
        return two;
    }

    public void setTwo(T two) {
        this.two = two;
    }

    private static <T> void set(Pair<Employee> p){
    }

    public static void main(String[] args) {
        //Pair<Employee>和Pair<Worker>没有任何继承关系
        Pair<Employee> employeePair = new Pair<>();
        Pair<Worker> workerPair = new Pair<>();

        Employee employee = new Worker();
        //Pair<Employee> employeePair2 = new Pair<Worker>();


        Pair<Employee> pair = new ExtendPair<>();


        set(employeePair);
        //set(workerPair);
    }

    /*泛型类可以继承或者扩展其他泛型类,比如List和ArrayList*/
    private static class ExtendPair<T> extends Pair<T>{

    }
}


1. 7 通配符类型


1.7.1 问题抛出,为啥需要通配符?

  • 正是因为前面所述的,Pair<Employee>Pair<Worker>没有任何关系,如果我们有一个泛型类和一个方法。

一个泛型类:

public class GenericType<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

一个方法:

    public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }
  • 现在我们有继承关系的类:Food Fruit Orange Apple HongFuShi
  • Food
    • Fruit
      • Orange
      • Apple
        • HongFuShi
  • wildchar

各个类的代码:

package cn.enjoyedu.generic.wildchar;

/**
 * @author 
 */
public class Food {
}

package cn.enjoyedu.generic.wildchar;

/**
 * @author 
 */
public class Fruit extends Food {
    private String color;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

package cn.enjoyedu.generic.wildchar;

/**
 * @author Mark老师  
 */
public class Orange extends Fruit {
}

package cn.enjoyedu.generic.wildchar;

/**
 * @author 
 */
public class Apple extends Fruit {
}

package cn.enjoyedu.generic.wildchar;

/**
 * @author 
 */
public class HongFuShi extends Apple {
}

package cn.enjoyedu.generic.wildchar;

/**
 * @author 
 */
public class WildChar{

    public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use(){
       GenericType<Fruit> a = new GenericType<>();
        print(a);
       GenericType<Orange> b = new GenericType<>();
        //print(b);
    }


    public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use2(){
        GenericType<Fruit> a = new GenericType<>();
        print2(a);
        GenericType<Orange> b = new GenericType<>();
        print2(b);
        //print2(new GenericType<Food>());
        GenericType<? extends Fruit> c =  new GenericType<>();

        Apple apple =  new Apple();
        Fruit fruit = new Fruit();
        //c.setData(apple);
        //c.setData(fruit);
        Fruit x = c.getData();
    }

    public static void printSuper(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void useSuper(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
        GenericType<Orange> orangeGenericType = new GenericType<>();
        printSuper(fruitGenericType);
        printSuper(appleGenericType);
//        printSuper(hongFuShiGenericType);
//        printSuper(orangeGenericType);


        //表示GenericType的类型参数的下界是Apple
        GenericType<? super Apple> x = new GenericType<>();
        x.setData(new Apple());
        x.setData(new HongFuShi());
        //x.setData(new Fruit());
        Object data = x.getData();

    }

}

则会产生这种情况:

   public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use(){
       GenericType<Fruit> a = new GenericType<>();
       print(a);
       GenericType<Orange> b = new GenericType<>();
        //print(b);   // 这种是不被允许的
    }

为解决这个问题,于是提出了一个通配符类型?

有两种使用方式:
? extends X 表示类型的上界,类型参数是X的子类
? super X 表示类型的下界,类型参数是X的超类
这两种 方式从名字上来看,特别是super,很有迷惑性,下面我们来仔细辨析这两种方法。


1.7.2 ? extends X


  • 表示传递给方法的参数,必须是X的子类(包括X本身)。这里的 ? extends Fruit是用在方法里面的。
  • 但是对泛型类GenericType来说,如果其中提供了getset类型参数变量的方法的话,set方法是不允许被调用的,会出现编译错误
  • get方法则没问题,会返回一个Fruit类型的值。
    public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use2(){
        GenericType<Fruit> a = new GenericType<>();
        print2(a);
        GenericType<Orange> b = new GenericType<>();
        print2(b);
        //print2(new GenericType<Food>());
        GenericType<? extends Fruit> c =  new GenericType<>();

        Apple apple =  new Apple();
        Fruit fruit = new Fruit();
        //c.setData(apple);
        //c.setData(fruit);
        Fruit x = c.getData();
    }

为何?

道理很简单,? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说,get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。

总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。


1.7.3 ? super X


  • 表示传递给方法的参数,必须是X的超类(包括X本身)
  • 但是对泛型类GenericType来说,如果其中提供了getset类型参数变量的方法的话,set方法可以被调用的,且能传入的参数只能是X或者X的子类
  • get方法只会返回一个Object类型的值。
    public static void printSuper(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void useSuper(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
        GenericType<Orange> orangeGenericType = new GenericType<>();
        printSuper(fruitGenericType);
        printSuper(appleGenericType);
//        printSuper(hongFuShiGenericType);
//        printSuper(orangeGenericType);


        //表示GenericType的类型参数的下界是Apple
        GenericType<? super Apple> x = new GenericType<>();
        x.setData(new Apple());
        x.setData(new HongFuShi());
        //x.setData(new Fruit());
        Object data = x.getData();

    }

为何?

? super X 表示类型的下界,类型参数是X的超类(包括X本身),那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是XX的子类可以安全的转型为X

总结:主要用于安全地写入数据,可以写入X及其子类型。


1.7.4 无限定通配符


表示对类型没有什么限制,可以把?看成所有类型的父类,Pair< ?>;
比如:
ArrayList<T> al=new ArrayList<T>(); 指定集合元素只能是T类型
ArrayList<?> al=new ArrayList<?>(); 集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法。
在使用上:
? getFirst() : 返回值只能赋给 Object,;
void setFirst(?)setFirst 方法不能被调用, 甚至不能用 Object 调用;


1. 7 虚拟机是如何实现泛型的?


  • 后续更新

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

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

相关文章

RocketMq的基本概念

&#x1f3b6; 文章简介&#xff1a;RocketMq的基本概念 &#x1f4a1; 创作目的&#xff1a;关于RocketMq的基本概念的大致介绍 ☀️ 今日天气&#xff1a;阳光明媚。 &#x1f4dd; 每日一言&#xff1a;冬有冬的来意&#xff0c;雪有雪的秘密。 文章目录&#x1f436; 1、Ro…

MySQL~DQL查询数据

4、DQL查询数据&#xff08;最重点&#xff09; 4.1、DQL &#xff08;Data Query LANGUAGE&#xff1a;数据查询语言&#xff09; 所有的查询操作都用它 Select简单的查询&#xff0c;复杂的查询它都能做~数据库中最核心的语言&#xff0c;最重要的语句使用频率最高 SELEC…

Kafka 集群部署与测试

安装Kafka&#xff08;需要JDK和Zookeeper&#xff09;: 下载Kafka安装包&#xff0c;并解压至node01节点中的/opt/apps目录下。修改配置文件。在server.properties配置文件中指定broker编号、Kafka运行日志存放的路径、指定Zookeeper地址和本地IP。添加环境变量。在/etc/prof…

[ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2018-19475

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

【IDEA】# 快速生成logger、通过Maven的profile配置实现环境的快速切换、常用基础设置

1. 快速生成logger 打开 Settings&#xff0c;找到 Editor 目录下的 Live Templates 选中 Java&#xff0c;点击右侧的加号&#xff0c;创建一个新的模板 在创建模板的相关位置&#xff0c;填上对应的值 Abbreviation&#xff1a;触发的关键字&#xff08;此处我使用的是 l…

Postman进阶篇(十二)-在脚本中使用pm对象访问接口响应数据(pm.response.*)

在之前的文章中介绍过postman中的两个脚本——pre-request script或test script&#xff0c;在这两个脚本中都有使用到pm对象。&#xff08;pre-request script详细介绍、Test script详细介绍&#xff09;pm对象是在postman的脚本中非常重要&#xff0c;也是十分常用的方法。本…

SpringCloud学习笔记 - Nacos配置中心搭建 - Nacos Config

Nacos 提供用于存储配置和其他元数据的 key/value 存储&#xff0c;为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Spring Cloud Alibaba Nacos Config&#xff0c;您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。 Spring Cloud Alibaba Nac…

Volo - Rust gRPC 框架入门

一、参考资料 Volo-GitHub Volo-Overview 二、开发环境搭建 1、安装脚手架 # 安装 volo-cli cargo install volo-cli # 验证安装 volo help 2、编写 IDL # 文件 volo_demo.protosyntax "proto3"; package volo.demo;message Item {int64 id 1;string title …

React学习26(react-redux优化 工作使用)

项目结构 优化说明 1&#xff09;容器组件和UI组件混合成一个文件 2&#xff09;无需自己给容器传递store&#xff0c;在index.js入口文件给包裹一个Provider <Provider store {store}><App/> </Provider> 3&#xff09;使用了react-redux后也不用自己在…

Python入门教程:基本运算符

1.运算符 计算机可以进行的运算有很多种&#xff0c;可不只加减乘除这么简单&#xff0c;运算按种类可分为算数运算、比较运算、逻辑运算、赋值运算、成员运算、身份运算、位运算&#xff0c;今天我们暂只学习算数运算、比较运算、逻辑运算、赋值运算、成员运算 2.算数运算 …

数据聚合、数据同步

文章目录数据聚合Bucket聚合语法聚合结果排序限定聚合范围Metric聚合语法RestAPI实现聚合数据同步发送MQ消息接收MQ消息数据聚合 Bucket聚合语法 GET /hotel/_search {"size": 0, // 设置size为0&#xff0c;结果中 不包含文档&#xff0c;只包含聚合结果~"…

Spark-内核(集群管理器、通讯架构、任务调度机制、Shuffle、内存管理)

文章目录Spark内核Spark部署模式的集群管理器YARN模式运行机制Standalone模式运行机制Spark通讯架构通信架构概述通讯架构解析Spark任务调度机制任务调度概述Stage级调度Spark Task级调度调度策略本地化调度失败重试与黑名单机制Spark Shuffle解析ShuffleMapStage与ResultStage…

SpringCloud微服务之Zuul网关

SpringCloud微服务之Zuul网关 家庭生活中经常有这样的感悟&#xff0c;家中的财政大权在老婆手里&#xff0c;想要花个小钱买个冰棍&#xff0c;得跟老婆请示&#xff0c;想要出个远门看看北京猿人&#xff0c;得跟老婆请示&#xff0c;想不要脸面去个夜店看看别的妞好在哪里&…

代码随想录第九天

专题&#xff1a;字符串 题目&#xff1a;字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。 比如&#xff0c;输入字符串"abcdefg"和数字2&#xff0c;该函数将返回左旋转两位得到的结果"cdefgab&…

ADI Blackfin DSP处理器-BF533的开发详解56:CVBS输入-DSP和ADV7180的MDMA用法(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 CVBS IN视频输出 代码实现功能 电视视频是奇场合偶场交替传输的&#xff0c;所以通过 CVBSIN 模块采集到的图像如上实验所看到的&#xff0c;是…

CentOS 7.6 安装与配置 MySql 5.7.40

1 通过wget下载MySql的rpm # wget https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm2 检查是否存在MySql的repo # cd /etc/yum.repos.d/ # ls CentOS-Base.repo CentOS-Epel.repo可以看到没有MySql的repo 3 安装MySql的repo&#xff0c;并查看是否安…

React 生命周期

React 生命周期 这篇文章&#xff0c;我们来聊一聊在React中的生命周期。首先我们明确一点&#xff0c;在React中&#xff0c;函数式组件是没有生命周期的。谈到生命周期&#xff0c;都是关于类组件的&#xff01; 生命周期官方网址 React.Component – React (docschina.or…

macOS Monterey 12.6.2 (21G320) Boot ISO 原版可引导镜像

macOS Monterey 12.6&#xff0c;皆为安全更新&#xff0c;不再赘述。 macOS Monterey 12.6&#xff0c;发布于 2022 年 9 月 12 日&#xff08;北京时间今日凌晨&#xff09;&#xff0c;本次为安全更新。 今日&#xff08;2022-07-21&#xff09;凌晨&#xff0c;Apple 终于…

CentOS 8:SSH远程登录

SSH远程登录 SSH远程登录&#xff0c;也是 C / S 模式 服务端&#xff1a;sshd &#xff0c;默认是启动的 systemctl status sshd systemctl start sshd SecureCRT 是打开了一个远程终端 注意&#xff1a;在终端环境里&#xff0c;不可以启动GUI程序 例如&#xff0c;在 …

【单片机】DS1302时钟/蜂鸣器

目录 一、DS1302时钟 1、DS1302时钟的介绍 2、DS1302时钟寄存器 3、DS1302时序图 4、BCD码 5、写一个时钟 6、写一个可调时钟 二、蜂鸣器 1、蜂鸣器的介绍​编辑 2、三极管放大驱动蜂鸣器 2.1NPN三极管工作原理&#xff08;基极电流和发射电流均流向集电区&#xff…