当前位置: 首页 > news >正文

Java中常见错误-泛型擦除及桥接方法问题及解决方案

Java中泛型擦除及桥接方法

    • 泛型擦除
      • 无界擦除
      • 上界擦除
      • 下界擦除
    • 桥接方法
      • 演示案例
        • wrong1
        • wrong2
        • wrong3
        • right
      • 原理总结

泛型擦除

泛型擦除是Java泛型机制的一个特性,它意味着**在编译期间,所有的泛型信息都会被移除,而在运行时,所有泛型类型都转换为其边界类型(通常是Object,或者对于参数化的类型参数,是其指定的边界)。这是因为Java的泛型是类型安全的,但不是类型保留的**。换句话说,泛型只在编译时提供类型检查,而运行时并不保留这些类型信息。泛型擦除可以分为以下三类

无界擦除

​ 无界擦除是指没有指定泛型类型的边界通常使用 ? 作为通配符。这意味着方法或变量可以接受任何类型的对象。例如,想象你有一个动物观察站,它记录任何动物的信息,但不关心它们的类型。

public class ObservationStation {public void recordAnyAnimal(Animal<?> animal) {// 不关心动物的类型,只是记录System.out.println(animal);}
}@Data
@NoArgsConstructor
public class Animal<T> {private T attribute; // 使用泛型T来表示动物的一个属性,比如名字、年龄等public Animal(T attribute) {this.attribute = attribute;}
}public class unBoundMain {public static void main(String[] args) {ObservationStation station = new ObservationStation();Animal<String> animal1 = new Animal<>("Dog");Animal<Integer> animal2 = new Animal<>(1);station.recordAnyAnimal(animal1); // 无界擦除,因为可以接受任何Animal类型station.recordAnyAnimal(animal2); // 同理}
}

​ 在运行时,recordAnyAnimal 方法的参数类型被擦除为 Animal,这意味着它接受任何 Animal 类型的对象,而不管具体的 T 是什么类型编译器在调用方法时会确保传入的参数是 Animal 的实例

运行结果:
在这里插入图片描述

上界擦除

上界擦除指的是在使用泛型时泛型参数限制某种类型的子类型该类型本身

@Data
@NoArgsConstructor
public class Animal<T extends LivingBeing> { // 设置上界,T必须是LivingBeing或其子类private T attribute; // 使用泛型T来表示动物的一个属性,比如名字、年龄等public Animal(T attribute) {this.attribute = attribute;}
}public class LivingBeing {private Age age; // 假设Age是一个枚举,表示生物的年龄阶段public enum Age {PUPPY,ADULT,SENIOR}// 其他属性和方法
}@Data
public class Dog extends LivingBeing{private Age age;
}public class ObservationStation {public void recordAnyAnimal(Animal<? extends LivingBeing> animal) { // 上界擦除,接受任何LivingBeing或其子类的Animal// 不关心动物的类型,只是记录System.out.println(animal);}
}public class upBoundMain {public static void main(String[] args) {ObservationStation station = new ObservationStation();Dog dog = new Dog(); // 创建Dog对象dog.setAge(LivingBeing.Age.ADULT); // 设置Dog的年龄Animal<Dog> animal1 = new Animal<>(dog); // 将Dog放入Animal<Dog>station.recordAnyAnimal(animal1); // 正确,Dog是LivingBeing的子类}
}

​ 在这个案例中,我引入了一个假设的LivingBeing基类,并修改了Animal类的泛型声明Animal<T extends LivingBeing>以及ObservationStation类的recordAnyAnimal方法签名private T attribute;Animal(T attribute) 以确保只有LivingBeing或其子类的Animal实例可以被记录。这样就实现了上界擦除效果

运行结果:

在这里插入图片描述

下界擦除

下界通配符允许你指定一个类型参数的下界,意味着可以接受该类型其父类型类型擦除的确会让泛型参数具体类型信息消失,但不影响使用下界或上界通配符来表达类型约束

​ 尽管类型擦除导致泛型类型参数的具体类型信息在运行时不可用,但Java依然支持通过下界通配符(如? super T)来限定泛型参数确保能够接受类型T及其父类型。这并不意味着下界概念在擦除过程中失效,而是指定了类型使用的灵活性在编译时得到保障,即使实际类型信息在运行时不可获取。

public class LivingBeing {
}public class Dog extends LivingBeing {// Dog类的其他属性和方法
}@Data
@NoArgsConstructor
public class Animal<T> {private T attribute;public Animal(T attribute) {this.attribute = attribute;}
}public class ObservationStation {public <T extends LivingBeing> void recordSpecificAnimal(Animal<? super T> animal, T specificType) {// 这里可以使用specificType来访问animal的attribute,因为animal能接收specificType或其父类型System.out.println("Recording " + specificType + " with attribute: " + animal.getAttribute());}public class lowBoundMain {public static void main(String[] args) {ObservationStation station = new ObservationStation();Dog dog = new Dog();Animal<Dog> animal1 = new Animal<>(dog);station.recordSpecificAnimal(animal1, dog); // 正确,Dog是Anima}
}

Dog类扩展了LivingBeing,这样它就可以用于Animal实例。在ObservationStation类的recordSpecificAnimal方法中,Dog实例可以作为T传入,因为Dog是LivingBeing的子类。Animal可以作为Animal<? super T>类型的参数传入,其中T是LivingBeing或其子类,因为Dog是LivingBeing的子类,所以Animal满足Animal<? super T>的约束。

运行结果:
在这里插入图片描述

public <T extends LivingBeing> void recordSpecificAnimal(Animal<? super T> animal, T specificType)

对 此处 <T extends LivingBeing> 的理解

​ 是Java泛型中的一个类型参数声明,它定义了一个名为 T 的类型变量,这个变量必须是 LivingBeing 类或其子类的类型。这里的 extends 关键字用来指定类型参数的边界,表明 T 只能是 LivingBeing 或者 LivingBeing 的任何子类

对 此处 ? super T 的理解

​ ? super T 是下界通配符,super 关键字在这里表示下界,T 是一个类型参数,代表一个未知的具体类型。
当你看到 ? super T,你可以理解为“任何类型,只要它是 T 或 T 的超类型”。这意味着 animal 变量可以接收任何 Animal 实例,只要这个实例的类型是 T 或者是 T 的父类。例如,如果 T 被实例化为 Dog,那么 animal 可以是 Animal,Animal,或者其他任何 Dog 的父类的 Animal 实例

桥接方法

​ 桥接方法(Bridge Method)是Java编译器在泛型类型擦除过程中自动生成的一种特殊方法旨在解决泛型类型擦除后可能导致的多态性问题。具体来说,当一个类继承了泛型类或者实现了泛型接口,并且重写了其中的方法时,如果子类或实现类的方法签名中的类型参数不同于父类或接口中的类型参数,编译器就会自动插入一个桥接方法来确保类型兼容性和多态性的正确实现
桥接方法的特点如下:

  • 透明性:对于开发者来说,桥接方法是不可见的,它在源代码中不会显示,仅存在于编译后的字节码中
  • 自动生成:桥接方法由Java编译器自动生成,无需程序员手动编写。
  • 方法签名桥接方法的签名与原始方法相同,但其返回类型或参数类型是原泛型类型擦除后的类型(通常是Object或其父类),而实际执行逻辑则委托给擦除类型参数后的具体实现方法

访问标志:桥接方法在字节码层面会被标记为 ACC_BRIDGEACC_SYNTHETIC,表示这是一个桥接方法和编译器生成的方法

用途:主要目的是为了确保在泛型类型擦除后,能够正确地调用到子类中重写的泛型方法,维持多态行为

演示案例

公共部分

public class Pig1 extends Animal {public void setValue(String value) {System.out.println("Pig1.setValue called");super.setValue(value);}
}public class Pig2 extends Animal<String> {@Overridepublic void setValue(String value) {System.out.println("Pig2.setValue called");super.setValue(value);}
}public class Animal<T> {AtomicInteger updateCount = new AtomicInteger();private T value;@Overridepublic String toString() {return String.format("value: %s updateCount: %d", value, updateCount.get());}public void setValue(T value) {System.out.println("Animal.setValue called");this.value = value;updateCount.incrementAndGet();}
}
wrong1
    public static void main(String[] args) {wrong1();}public static void wrong1() {Pig1 pig1 = new Pig1();Arrays.stream(pig1.getClass().getMethods()).filter(method -> method.getName().equals("setValue")).forEach(method -> {try {System.out.println(method);method.invoke(pig1, "test");} catch (Exception e) {e.printStackTrace();}});System.out.println(pig1.toString());}

运行结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

执行过程:

​ wrong1方法中,对于Pig1对象,通过反射获取所有公共方法(包括继承的方法)中名为setValue的方法。由于Pig1重写了父类Animal的setValue方法,但没有改变参数类型,所以这里会看到两个方法一个是直接从Pig1类中声明的方法,另一个是从父类继承而来的方法(因为泛型擦除后,父类的setValue(T value)变为setValue(Object value),与子类重写的方法签名相同)。这里Pig1重写并不是我们规范化的重写

输出解释:

​ 首先调用了子类Pig1的setValue(String value),打印出"Pig1.setValue called",然后由于继承关系和方法重写规则,继续调用了父类的Animal.setValue(Object value),这一步实际上也是对Pig1中重写方法的调用,因此再次打印"Animal.setValue called"。最后,updateCount被增加了2次,输出为"value: test updateCount: 2"。

wrong2
    public static void main(String[] args) {wrong2();}public static void wrong2() {Pig1 pig1 = new Pig1();Arrays.stream(pig1.getClass().getDeclaredMethods()).filter(method -> method.getName().equals("setValue")).forEach(method -> {try {System.out.println(method);method.invoke(pig1, "test");} catch (Exception e) {e.printStackTrace();}});System.out.println(pig1.toString());}

运行结果:

在这里插入图片描述

wrong2的结果及解释
执行过程:

​ 与wrong1不同的是,这里仅获取Pig1声明的自身方法(不包括继承的),因此只找到了一个setValue(String value)方法
输出解释

仅调用了一次Pig1.setValue,内部虽然调用了super.setValue(value),但因为是在同一个方法体内,updateCount只增加了一次,所以输出为"value: test updateCount: 1"。

wrong3
    public static void main(String[] args) {wrong3();}public static void wrong3() {Pig2 pig2 = new Pig2();Arrays.stream(pig2.getClass().getDeclaredMethods()).filter(method -> method.getName().equals("setValue")).forEach(method -> {try {System.out.println(method);method.invoke(pig2, "test");} catch (Exception e) {e.printStackTrace();}});System.out.println(pig2.toString());}

运行结果:

在这里插入图片描述

执行过程:

​ 对于Pig2,它继承自具有泛型参数的Animal,且重写了setValue方法。在反射调用时,因为泛型擦除,父类会产生一个桥接方法setValue(Object)。wrong3试图调用所有声明的方法(包括桥接方法),所以setValue(String)和桥接方法setValue(Object)都被调用了。

输出解释:

​ 首先调用子类Pig2的setValue(String value),打印"Pig2.setValue called",然后因为泛型擦除和桥接方法,又调用了父类的Animal.setValue(Object)(这里也是调用的子类重写的版本),再次打印"Animal.setValue called"。最终updateCount被增加了2次,输出为"value: test updateCount: 2"。

right
    public static void main(String[] args) {right();}public static void right() {Pig2 pig2 = new Pig2();Arrays.stream(pig2.getClass().getDeclaredMethods()).filter(method -> method.getName().equals("setValue") && !method.isBridge()).findFirst().ifPresent(method -> {try {System.out.println(method);method.invoke(pig2, "test");} catch (Exception e) {e.printStackTrace();}});System.out.println(pig2.toString());}

运行结果:

在这里插入图片描述

执行过程:

​ 此方法正确地处理了泛型导致的桥接方法问题。它通过检查方法是否为桥接方法!method.isBridge()),只调用了实际需要的setValue(String)方法,忽略了桥接方法
输出解释:

只调用了Pig2.setValue(String value)一次,内部调用super.setValue(value)也是针对同一个具体类型的方法,因此updateCount只增加了一次,输出为"value: test updateCount: 1"。

原理总结

类型擦除:Java泛型在编译后会被擦除,这意味着像Animal< String >在字节码层面等同于Animal< Object >,导致方法签名可能变得不明确。
桥接方法为了保证泛型擦除后的多态性,编译器会为某些泛型方法生成桥接方法,这些方法通常指向具体的实现
反射调用直接通过反射调用可能遇到桥接方法,导致意外的行为,如多次调用或不期望的类型转换。正确处理泛型和桥接方法对于避免这些问题至关重要。

本次 Java中常见错误-泛型擦除及桥接方法问题及解决方案 文章到此结束,创作不易,望我佬们三连支持一下

相关文章:

Java中常见错误-泛型擦除及桥接方法问题及解决方案

Java中泛型擦除及桥接方法 泛型擦除无界擦除上界擦除下界擦除 桥接方法演示案例wrong1wrong2wrong3right 原理总结 泛型擦除 ​ 泛型擦除是Java泛型机制的一个特性&#xff0c;它意味着**在编译期间&#xff0c;所有的泛型信息都会被移除&#xff0c;而在运行时&#xff0c;所…...

Linux 程序守护脚本

引言 程序是由代码形成的&#xff0c;代码是由人写的。只要是人&#xff0c;都会有疏忽的时候&#xff0c;导致写出的程序有bug&#xff0c;当然最严重的bug就是程序闪退。 本文旨在提供一个程序守护脚本&#xff0c;当监测到程序闪退后&#xff0c;立马将程序再起启动&#…...

跨境电商|Facebook Marketplace怎么做?

2016 年&#xff0c;Facebook打造了同名平台 Facebook Marketplace。通过利用 Facebook 现有的庞大客户群&#xff0c;该平台取得了立竿见影的成功&#xff0c;每月访问量将超过 10 亿。对于个人卖家和小企业来说&#xff0c;Facebook Marketplace是一个不错的销货渠道&#xf…...

.gitignore 文件

一.什么是 .gitignore 文件 在任何当前工作的 Git 仓库中&#xff0c;每个文件都是这样的&#xff1a; 追踪的&#xff08;tracked&#xff09;- 这些是 Git 所知道的所有文件或目录。这些是新添加&#xff08;用 git add 添加&#xff09;和提交&#xff08;用 git commit 提…...

qt中实现多语言功能

qt中实现多语言功能 原理&#xff1a; 其本质就是生成ts文件&#xff0c;然后使用Linguist软件手工翻译&#xff0c;再生成qm文件&#xff0c;最后在主程序的开始加载不同的qm文件&#xff0c;实现多语言。 步骤&#xff1a; 修改程序文件 在pro文件中加入说明 TRANSLATI…...

数据结构与算法之 leetcode 513. 找树左下角的值 (BFS) 广度优先

513. 找树左下角的值 /*** Definition for a binary tree node.* function TreeNode(val, left, right) {* this.val (valundefined ? 0 : val)* this.left (leftundefined ? null : left)* this.right (rightundefined ? null : right)* }*/ /*** param {T…...

mysql中的函数

MySQL提供了丰富的内置函数&#xff0c;涵盖了字符串操作、数字计算、日期和时间处理、条件判断、聚合计算等多个方面。这些函数可以帮助开发者在查询和数据处理时更高效地完成任务。下面是对MySQL中常见的函数分类及其主要函数的介绍&#xff1a; 字符串函数 CONCAT()&#x…...

Shell正则表达式与文本处理器

一、grep 1. 正则表达式 是一种匹配字符串的方法&#xff0c;通过一些特殊符号&#xff0c;快速实现查找&#xff0c;删除&#xff0c;替换某特定字符串。 选项&#xff1a; -a 不要忽略二进制数据。 -A 显示该行之后的内容。 -b 显示该行之前的内容。 -c 计算符合范本样…...

双指针法 ( 三数之和 )

题目 &#xff1a;给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复…...

感染恶意代码之后怎么办?

隔离设备 立即将感染设备与网络隔离&#xff0c;断开与互联网和其他设备的连接。这可以防止恶意代码进一步传播到其他设备&#xff0c;并减少对网络安全的威胁。 确认感染 确认设备是否真的感染了恶意代码。这可能需要使用安全软件进行全面扫描&#xff0c;以检测和识别任何已…...

【计算机网络】P3 计算机网络协议、接口、服务的概念、区别以及计算机网络提供的三种服务方式

目录 协议什么是协议协议是水平存活的协议的组成 接口服务服务是什么服务原语 协议与服务的区别计算机网络提供的服务的三种方式面向连接服务与无连接服务可靠服务与不可靠服务有应答服务与无应答服务 协议 什么是协议 协议&#xff0c;就是规则的集合。 在计算机网络中&…...

多角度剖析事务和事件的区别

事务和事件这两个概念在不同的领域有着不同的含义&#xff0c;尤其是在计算机科学、数据库管理和软件工程中。下面从多个角度来剖析事务和事件的区别&#xff1a; 计算机科学与数据库管理中的事务 事务(Transaction)&#xff1a; 定义&#xff1a;在数据库管理中&#xff0c…...

模糊小波神经网络(MATLAB 2018)

模糊系统是一种基于知识或规则的控制系统&#xff0c;从属于智能控制&#xff0c;通过简化系统的复杂性&#xff0c;利用控制法来描述系统变量之间的关系&#xff0c;采用语言式的模糊变量来描述系统&#xff0c;不必对被控对象建立完整的数学模型。相比较传统控制策略&#xf…...

HTML布局

标准流&#xff1a; 标准流就是元素在页面中的默认排列方式&#xff0c;也就是元素在页面中的默认位置。 1.1 块元素----独占一行----从上到下排列 1.2 行内元素----不独占一行----从左到右排列&#xff0c;遇到边界换行 1.3 行内块元素----不独占一行…...

数据结构:双链表

数据结构&#xff1a;双链表 题目描述参考代码 题目描述 输入样例 10 R 7 D 1 L 3 IL 2 10 D 3 IL 2 7 L 8 R 9 IL 4 7 IR 2 2输出样例 8 7 7 3 2 9参考代码 #include <iostream>using namespace std;const int N 100010;int m; int idx, e[N], l[N], r[N];void init…...

Python3 元组、列表、字典、集合小结

前言 本文主要对Python中的元组、列表、字典、集合进行小结&#xff0c;主要内容包括知识点回顾、异同点、使用场景。 文章目录 前言一、知识点回顾1、列表&#xff08;List&#xff09;2、 元组&#xff08;Tuple&#xff09;3、 字典&#xff08;Dictionary&#xff09;4.、…...

2024会声会影破解免费序列号,激活全新体验!

会声会影2024序列号注册码是一款专业的视频编辑软件&#xff0c;它以其强大的功能和易用性受到了广大用户的喜爱。在这篇文章中&#xff0c;我将详细介绍会声会影2024序列号注册码的功能和特色&#xff0c;帮助大家更好地了解这款产品。 会声会影全版本绿色安装包获取链接&…...

机器学习18个核心算法模型

1. 线性回归&#xff08;Linear Regression&#xff09; 用于建立自变量&#xff08;特征&#xff09;和因变量&#xff08;目标&#xff09;之间的线性关系。 核心公式&#xff1a; 简单线性回归的公式为&#xff1a; , 其中 是预测值&#xff0c; 是截距&#xff0c; 是斜…...

平滑值(pinghua)

平滑值 题目描述 一个数组的“平滑值”定义为&#xff1a;相邻两数差的绝对值的最大值。 具体的&#xff0c;数组a的平滑值定义为 f ( a ) m a x i 1 n − 1 ∣ a i 1 − a i ∣ f(a)max_{i1}^{n-1}|a_{i1}-a_i| f(a)maxi1n−1​∣ai1​−ai​∣ 现在小红拿到了一个数组…...

使用matplotlib绘制折线条形复合图

使用matplotlib绘制折线条形复合图 介绍效果代码 介绍 在数据可视化中&#xff0c;复合图形是一种非常有用的工具&#xff0c;可以同时显示多种数据类型的关系。在本篇博客中&#xff0c;我们将探讨如何使用 matplotlib 库来绘制包含折线图和条形图的复合图。 效果 代码 imp…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...