Java 泛型进阶

news2024/12/28 6:04:52

目录

一、什么是泛型

二、引出泛型

 1、语法

四、泛型类的使用

1、语法

2、示例

3、类型推导(Type Inference)

4、裸类型(Raw Type) (了解)

(1)说明

五、泛型如何编译的

1、擦除机制

2、为什么不能实例化泛型类型数组

六、泛型的上界

1、语法

2、示例

3、复杂示例

七、泛型方法

1、定义语法

2、示例

3、使用示例-可以类型推导

4、使用示例-不使用类型推导

八、通配符

1、通配符解决什么问题

2、通配符上界 

语法:

3、通配符下界

语法:


 在进一步学习泛型之前,我们先简单的回顾一下之前学习的内容

一、什么是泛型

一般的类和方法,只能使用具体的类型 : 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《 Java 编程思想》对泛型的介绍。
泛型是在 JDK1.5 引入的新的语法,通俗讲,泛型: 就是适用于许多许多类型 。从代码上讲,就是对类型实现了参数化。

二、引出泛型

现在,有这么一个要求:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
思路:
1. 我们以前学过的数组,只能存放指定类型的元素,例如: int[] array = new int[10]; String[] strs = new String[10];
2. 所有类的父类,默认为 Object 类。数组是否可以创建为 Object?

代码示例:

class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,Object val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0,10);
        myArray.setVal(1,"hello");//字符串也可以存放
        String ret = myArray.getPos(1);//编译报错
        System.out.println(ret);
    }
}

 问题:以上代码实现后 发现

1. 任何类型数据都可以存放
2. 1 号下标本身就是字符串,但是确编译报错。必须进行强制类型转换
虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型。 所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查 此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

 1、语法

class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
    // 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
    // 可以只使用部分类型参数
}

此时,我们可以使用泛型,将上面的代码改写成这个样子:

class MyArray<T> {
    public T[] array = (T[])new Object[10];//1
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();//2
        myArray.setVal(0,10);
        myArray.setVal(1,12);
        int ret = myArray.getPos(1);//3
        System.out.println(ret);
        myArray.setVal(2,"hello");//4
    }
}
代码解释:
1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类
【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型

2. 注释1处,不能new泛型类型的数组

这也就意味着

T[] ts = new T[5];//是不对的

 3. 注释2处,类型后加入 <Integer> 指定当前类型

4. 注释3处,不需要进行强制类型转换

5. 注释 4 处,代码编译报错,此时因为在注释 2 处指定类当前的类型,此时在注释 4 处,编译器会在存放元素的时候帮助我们进行类型检查。

四、泛型类的使用

1、语法

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

2、示例

MyArray<Integer> list = new MyArray<Integer>();
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!

3、类型推导(Type Inference)

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

MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer

4、裸类型(Raw Type) (了解)

(1)说明

裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型
MyArray list = new MyArray();
注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的
小结:
1. 泛型是将数据类型参数化,进行传递
2. 使用 <T> 表示当前类是一个泛型类。
3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

五、泛型如何编译的

1、擦除机制

那么,泛型到底是怎么编译的?泛型本质是一个非常难的语法,要理解好他还是需要一定的时间打磨。
通过命令:javap -c 查看字节码文件,所有的 T 都是 Object

在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息
 那么这个时候我们就会提出疑问了:
1 、那为什么, T[] ts = new T[5]; 是不对的,编译的时候,替换为 Object ,不是相当于: Object[] ts = new Object[5]吗?
2 、类型擦除,一定是把 T 变成 Object 吗?

2、为什么不能实例化泛型类型数组

 我们先来看一下这一段的代码:

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 static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>();
        Integer[] strings = myArray1.getArray();
    }

这时候我们会发现编译器进行了报错:

/*
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at TestDemo.main(TestDemo.java:31)
*/
原因:替换后的方法为:将 Object[] 分配给 Integer[] 引用,程序报错。
public Object[] getArray() {
    return array;
}
通俗讲就是:返回的 Object 数组里面,可能存放的是任何的数据类型,可能是 String ,可能是 Person ,运行的时候,直接转给Integer 类型的数组,编译器认为是不安全的。

六、泛型的上界

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

1、语法

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

2、示例

public class MyArray<E extends Number> {
    ...
}
只接受 Number 的子类型作为 E 的类型实参
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
了解: 没有指定类型边界 E,可以视为 E extends Object

3、复杂示例

public class MyArray<E extends Comparable<E>> {
    ...
}
E 必须是实现了 Comparable 接口的

七、泛型方法

1、定义语法

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

2、示例

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

3、使用示例-可以类型推导

Integer[] a = { ... };
swap(a, 0, 9);
String[] b = { ... };
swap(b, 0, 9);

4、使用示例-不使用类型推导

Integer[] a = { ... };
Util.<Integer>swap(a, 0, 9);
String[] b = { ... };
Util.<String>swap(b, 0, 9);

八、通配符

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

1、通配符解决什么问题

package www.hello.java.test;
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("hello world");
        fun(message);
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}

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

public class TestDemo {
    public static void main(String[] args) {
        Message<Integer> message = new Message() ;
        message.setMessage(99);
        fun(message); // 出现错误,只能接收String
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}
我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符 "?" 来处理
范例:使用通配符
public class TestDemo {
    public static void main(String[] args) {
        Message<Integer> message = new Message() ;
        message.setMessage(55);
        fun(message);
    }
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<?> temp){
//temp.setMessage(100); 无法修改!
        System.out.println(temp.getMessage());
    }
}
"?" 的基础上又产生了两个子通配符:
? extends 类:设置通配符上限
? super 类:设置通配符下限

2、通配符上界 

语法:

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

示例 1

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 TestDemo {
    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()); //仍然无法修改!
        System.out.println(temp.getMessage());
    }
}

 此时无法在fun函数中对temp进行添加元素,因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错!但是可以获取元素。

public static void fun(Message<? extends Fruit> temp){
    //temp.setMessage(new Banana()); //仍然无法修改!
    //temp.setMessage(new Apple()); //仍然无法修改!
    Fruit b = temp.getMessage();
    System.out.println(b);
}
通配符的上界,不能进行写入数据,只能进行读取数据。

3、通配符下界

语法:

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

 示例

class Food {
}
class Fruit extends Food {
}
class Apple 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) {
        Plate<Fruit> plate1 = new Plate<>();
        plate1.setPlate(new Fruit());
        fun(plate1);
        Plate<Food> plate2 = new Plate<>();
        plate2.setPlate(new Food());
        fun(plate2);
    }
    public static void fun(Plate<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
        temp.setPlate(new Apple());//这个是Fruit的子类
        temp.setPlate(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
        System.out.println(temp.getPlate());//只能直接输出
    }
}
通配符的下界,不能进行读取数据,只能写入数据。

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

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

相关文章

Nginx Rewrite的应用

目录 一、Nginx Rewrite 二、Rewrite的功能 1.Rewrite 跳转场景 2.Rewrite 跳转实现 3.Rewrite 实际场景 4.Rewrite 正则表达式 5.Rewrite 命令/语法格式 6.location 分类 7.location 优先级 8.Rewrite和location比较 9.根据以上了解&#xff0c;小案例来操…

【STM32】F103(64K/128K Flash)外设概述

本文介绍的是STM32F103 中等容量产品&#xff08;STM32F103x8xx和STM32F103xBxx&#xff09;的硬件数据&#xff0c;即64KB或128KB Flash&#xff0c;20KB SRAM。 ST官网资料&#xff1a;https://www.st.com/zh/microcontrollers-microprocessors/stm32f103.html ST官方的中等…

Scala中的隐式参数、隐式函数和隐式类

使用 implicit 修饰的内容是隐式内容, 隐式的特点就是遇到适应的类型会自动的应用。隐式可以使得静态类型动态化&#xff0c;为现有类库添加功能&#xff0c;隐式的代理增强一个类或者一个方法。 隐式转化的时机 当方法中的参数的类型与目标类型不一致时当对象调用所在类中不…

HOT18-矩阵置零

leetcode原题链接: 矩阵置零 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,…

【Flutter】Flutter Redux 入门:解决状态管理的问题

文章目录 一、 前言二、 Flutter Redux 简介1. 什么是 Redux2. 为什么需要 Redux3. Flutter Redux 的作用 三、 Flutter Redux 的基本使用1. 安装和配置2. 创建 Store3. 使用 StoreProvider 四、 Flutter Redux 的基础示例1. 创建一个简单的计数器应用2. 解析代码和说明 五、 版…

解决npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher

一、问题 环境 系统&#xff1a;centos 7 node &#xff1a;v18.16.1 npm&#xff1a;9.5.1 安装pm2 npm install -g pm2提示报错&#xff1a; npm WARN deprecated uuid3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certai…

记录react 视频和 预览拖动

一、react 视频 ##1、循环播放 import React, { useEffect, useState, useRef } from "react"; const videoRef useRef(null); const showVideoClass { display: "block", width: "100%", height: "100%" } const hindVideoClass …

Zookeeper的应用场景

一、Zookeeper的应用场景包括&#xff1a; 配置中心&#xff1a;Zookeeper可以用来存储和管理配置信息&#xff0c;例如集群中的机器配置、服务地址配置等。通过Zookeeper&#xff0c;可以将配置信息统一管理&#xff0c;同时实现动态加载和更新。统一命名服务&#xff1a;Zoo…

Ubuntu 20.04.02 LTS安装virtualbox7.0

ubuntu22.04的软件仓库也有virtualbox&#xff0c;不过版本较老。 使用安装命令&#xff1a;sudo apt install virtualbox 如果想要安装最新版&#xff0c;那么需要去官网下载deb包或者使用官方的仓库。 这里采用安装Oracle官方仓库的方法。 执行如下命令&#xff1a; wge…

fastadmin 点击获取当前行数据

fastadmin 点击获取当前行数据 // 先获取当前id // 使用方法的语法&#xff1a;$(#table).bootstrapTable(method, parameter);$(document).on("click",".detailtips",function(){var ids$(this).attr("id");var rows$("#table").boo…

pytorch快速入门中文——04(训练图片分类器)

训练分类器 原文&#xff1a;https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#sphx-glr-beginner-blitz-cifar10-tutorial-py 就是这个。 您已经了解了如何定义神经网络&#xff0c;计算损失并更新网络的权重。 现在您可能在想&#xff0c; 数据呢&…

java小技能:分布式任务调度平台

文章目录 引言I 报表数据生成II 注意事项2.1 任务创建2.2 pom.xml 添加到maven项目 see also 引言 任务调度的应用场景&#xff1a; 生成日报、月报、定时处理任务&#xff08;定期清理文件、处理数据&#xff09; I 报表数据生成 https://kunnan.blog.csdn.net/article/deta…

你知道GPT-3带的即时学习能力是什么吗

你知道GPT-3带的即时学习能力是什么吗 在人工智能领域&#xff0c;GPT-3&#xff08;Generative Pre-trained Transformer 3&#xff09;是当前比较先进的自然语言处理模型之一。它采用了自监督学习的方式进行训练&#xff0c;并且拥有强大的“in-context learning”&#xff…

nginx-rewrite

目录 1.rewrite 2.应用场景 3.跳转实现及特点 4.格式 5.location分类 6.具体应用场景 1.基于域名跳转 2.基于客户端ip访问跳转 3.基于旧域名跳转新域名后加的目录 4.基于匹配的跳转 5.基于目录下所有php结尾文件跳转 6.基于最普通一条url请求的跳转 7.总结 1.rewrite 重…

星辰秘典:揭开Python项目的神秘密码——2048游戏

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;html css js&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;你好&#x…

Docker 部署 jar 项目

文章目录 1、上传jar包2、新建 Dockerfile 文件3、新建 deploy.sh 脚本&#xff08;创建并运行&#xff09;4、新建 upgrade.sh 脚本&#xff08;更新&#xff09; 1、上传jar包 2、新建 Dockerfile 文件 添加jar包及修改端口 # 基础镜像 FROM java:8 # 添加jar包 ADD servic…

百度智能车竞赛丝绸之路智能车设计与编程实现控制

一、项目简介 本项目现已基于鲸鱼机器人开发套件对其整体外形进行设计&#xff0c;并且对应于实习内容——以“丝绸之路”为题&#xff0c;对机器人各个功能与机器人结构部分进行相关设计与调整。主要可以实现“车道线巡检”“音乐交际”、“城堡检测”、“翻山越岭”。 本项…

Java使用策略模式和工厂模式来消除冗余的if-else语句(UML类图+案例+提供Gitee源码)

前言&#xff1a;在最近的后端开发中&#xff0c;多多少少会发现有很多if-else语句&#xff0c;如果条件过多则会造成整体代码看起来非常臃肿&#xff0c;这边我就举一个我在实际开发中的例子&#xff0c;来进行阐述这两种模式在实际开发中我是如何运用的。 目录 一、工厂模式…

把Jar打包为Maven 把jar打包为maven 将java项目打包为maven 将Java项目打包为Maven

把Jar打包为Maven 把jar打包为maven 将java项目打包为maven 将Java项目打包为Maven 自己写了一个通用SDK Jar包&#xff0c;但是现在的项目都是Maven项目&#xff0c;需要把Jar打包为Maven格式&#xff0c;输出到本地Maven仓库&#xff0c;在项目中可以引用查看Maven是否安装打…

nginx进行反向代理

Nginx是一个开源的高性能Web服务器和反向代理服务器。它最初是由Igor Sysoev在2004年开发的&#xff0c;现在由一个全球性的社区维护和支持。 Nginx的主要特点包括&#xff1a; 高性能&#xff1a;Nginx使用事件驱动模型&#xff0c;可以处理高并发请求&#xff0c;具有出色的…