强化基础-Java-泛型

什么是泛型?

泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。

为什么会有泛型?

在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查

Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];

一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?

List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

泛型擦除

泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {public static void main(String[] args) {List<Integer> integerList = new ArrayList<>();List<String> stringList = new ArrayList<>();System.out.println(Objects.equals(integerList, stringList));System.out.println(integerList.getClass());System.out.println(stringList.getClass());}
}

再看下面这个例子:

public class Pair<T> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在这里插入图片描述

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限

public class Pair<T> {private T first;private T second;
}

擦除后,没有限定泛型的界限所以是Object类型:

public class Pair {private Object first;private Object second;
}

2 限定了泛型的界限

public class Pair<T extends Comparable> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

在这里插入图片描述

这就证明在擦除后:

public class Pair<T extends Comparable & Serializable> {private Comparable first;private Comparable second;
}

如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。

public class Pair<T extends Serializable & Comparable> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

在这里插入图片描述

所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:

  • 对原始方法的调用得到Object
  • 将返回的Object类型强制转换为具体的类型。

3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:

public <E> E convert(T t) { 
}

非泛型方法:

public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {return null;
}

泛型方法擦除后:

public static Comparable min(Comparable [] data) {return null;
}

需要注意的是泛型方法的多态是通过桥方法实现的

public class Pair<T> {private T time;public void setTime(T time) {this.time = time;}
}
// 被擦除以后
public void setTime(Object time) {
}

如果这个时候子类继承Pair,并指定了类型:

class DateInterVal extends Pair<LocalDate> {@Overridepublic void setTime(LocalDate time) {}
}

这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:

setTime(setTime((LocalDate) time));

总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换

泛型方法

1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。

public class Pair<T extends Comparable & Serializable> {private T data;// 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的public static <E> E convert2E(T t) {return null;}// 在非静态方法的情况下 可以使用上面的类中定义的泛型Tpublic <E> E convert2E(T t) {return null;}// 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型// 这个T可以和类的T是同一个类型,也可以不是同一个类型public static  <T> T accept(T t) {return null;}
}

2 泛型方法中的可变参数

public class Pair<T> {static class Dot {private int x;private int y;public Dot(int x, int y) {this.x = x;this.y = y;}@Overridepublic String toString() {return "Dot{" +"x=" + x +", y=" + y +'}';}}// 泛型方法可变参数private static <T> void print(T ...es) {for (T t : es) {System.out.println(t  + "");}}public static void main(String[] args) {Dot dot = new Dot(1, 1);print(15000, "15000", dot);}
}

2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。

// 也就是说必须申明
public static  <T> T accept(T t) {return null;
}

泛型缺陷

1 不能使用基本类型实例化类型参数
也就是说没有

Pair<int> Pair<double> 类型

因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的

String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();

但是我们可以通过反射来创建一个实例:

 Pair<String> pair = Pair.class.newInstance();

3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。

pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair

并且下面的语句也会返回true:

Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());

4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。

// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double> 
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];

5 Varargs 警告

public static <T> void addAll(Collection<T> coll, T... ts){// 这里其实创建了数组就违背了不能创建数组for(T t : ts) {coll.add(t);}
}
public static void main(String[] args) {Collection<Pair<String>> table = new ArrayList<>();Pair<String> pair1 = new Pair<>();Pair<String> pair2 = new Pair<>();addAll(table, pair1, pair2);
}
static class Pair<T> {}

6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。

// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]

这里也可以通过反射来进行:

public T[] demo() {T data[] = (T[]) Array.newInstance(Pair.class, 2);return data;
}

7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。

// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){for(T t : ts) {coll.add(t);}}

8 不能抛出或捕获泛型类的实例

public static <T extends Throwable> void doWork() {try {// 会产生编译错误} catch (T e) {}
}

泛型中的继承

继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型

public class Bar<T> extends Foo<T> {}

2 使用具体的类型

public class Bar extends Foo<String> {}

特别注意:

// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); 
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意 

原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
在这里插入图片描述

泛型中的限定

在通配符类型中,允许类型参数发生变化。
1 子类限定

// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}

2 超类限定

// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}

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

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

相关文章

学透Spring Boot — 创建一个简单Web应用

从今天开始&#xff0c;我们将开始学习一个新的系列&#xff0c;那就是在项目中用得非常广泛的一个框架 —— Spring Boot&#xff0c;我们会循序渐进地介绍 Spring Boot 的方方面面&#xff0c;包括理论和实战&#xff0c;也会介绍和Spring Boot一些热点面试题。 概论 本文是…

C#学生信息成绩管理系统

一、系统功能描述 本系统包括两类用户&#xff1a;学生、管理员。管理员可以通过系统来添加管理员信息、修改管理员信息、添加学生信息、修改学生信息&#xff1b;开设课程、查询课程、录入成绩、统计成绩、修改成绩、修改个人密码等&#xff0c;而学生则可以通过系统来选择课…

观察者模式和发布-订阅模式有什么异同

观察者模式&#xff08;Observer Pattern&#xff09;和发布-订阅模式&#xff08;Publish-Subscribe Pattern&#xff09;都是用于处理对象之间的一对多依赖关系&#xff0c;但它们在实现方式和应用场景上有一些异同。 观察者模式&#xff1a; 观察者模式定义了一种一对多的依…

基于Givens旋转完成QR分解进而求解实矩阵的逆矩阵

基于Givens旋转完成QR分解进而求解实矩阵的逆矩阵 目录 前言 一、Givens旋转简介 二、Givens旋转解释 三、Givens旋转进行QR分解 四、Givens旋转进行QR分解数值计算例子 五、求逆矩阵 六、MATLAB仿真 七、参考资料 总结 前言 在进行QR分解时&#xff0c;HouseHolder变换…

YOLOv8项目实践——目标检测、实例分割、姿态估计、目标追踪算法原理及模型部署(Python实现带界面)

简介 Ultralytics YOLOv8是一种前沿的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它在前代YOLO版本的成功基础上进行了进一步的创新&#xff0c;引入了全新的特性和改进&#xff0c;以进一步提升性能和灵活性。作为一个高速、精准且易于操作的设计&#xff0c;YO…

量化交易入门(二十三)什么是MTM指标,原理是什么

MTM指标全称是Momentum指标,翻译为动量指标。它用来衡量市场价格在一定时间内上涨或下跌的幅度,属于趋势型指标。其计算公式是: MTM(N) 当前收盘价 - N日前的收盘价 其中N表示统计的周期数,常用参数有6日、12日和24日。 MTM指标的应用要点如下: 判断趋势强弱:MTM数值越大,表…

uniapp 微信小程序 canvas 手写板文字重复倾斜水印

核心逻辑 先将坐标系中心点通过ctx.translate(canvasw / 2, canvash / 2) 平移到canvas 中心&#xff0c;再旋转设置水印 假如不 translate 直接旋转&#xff0c;则此时的旋转中心为左上角原点&#xff0c;此时旋转示意如图所示 当translate到中心点之后再旋转&#xff0c;此…

树状打印二叉树的类Java、Go、PHP

说明和效果 树的结构示例&#xff1a;1/ \2 3/ \ / \4 5 6 7树状打印二叉树Java代码 static class TreeNode {int val 0;TreeNode left null;TreeNode right null;public TreeNode(int val) {this.val val;}}//打印二叉树的类// TreeOperation.javastati…

P8649 [蓝桥杯 2017 省 B] k 倍区间:做题笔记

目录 思路 代码思路 代码 推荐 P8649 [蓝桥杯 2017 省 B] k 倍区间 思路 额嗯&#xff0c;这道题我刚上来是想到了前缀和&#xff0c;但是还要判断每个子序列&#xff0c;我就两层for嵌套&#xff0c;暴力解了题。就是我知道暴力肯定过不了但是写不出来其他的[留下了苦…

【ESP32S3 Sense接入语音识别+MiniMax模型对话】

1. 前言 围绕ESP32S3 Sense接入语音识别MiniMax模型对话展开&#xff0c;首先串口输入“1”字符&#xff0c;随后麦克风采集2s声音数据&#xff0c;对接百度在线语音识别&#xff0c;将返回文本结果丢入MiniMax模型&#xff0c;进而返回第二次结果文本&#xff0c;实现语言对话…

Serilog日志框架

文章目录 一. Serilog介绍1.1 安装Serilog1.2 Serilog日志级别 二. Serilog.App项目应用2.1 Serilog一般应用2.2 兼容系统日志 三. Serilog.Web应用3.1 Minimal示例3.2 WebApi示例 参考链接 一. Serilog介绍 Serilog 是 .NET应用程序的诊断日志记录库。它易于设置&#xff0c;…

libVLC 动态视频壁纸

在 Windows 上&#xff0c;你可能需要使用 Windows API 来设置壁纸&#xff0c;而在 Linux 上&#xff0c;你可能需要使用某种桌面环境特有的方法。在 macOS 上&#xff0c;这一功能可能受到限制。 效果图如下所示&#xff1a; 以下是一个简单的示例&#xff0c;说明了如何在 …

Ubuntu16.04 切换系统python和gcc版本

4.切换系统python版本 sudo update-alternatives --config python5.切换系统gcc版本 sudo update-alternatives --config gccubuntu16.04中的gcc版本及版本切换 5.查看opencv版本 pkg-config opencv --modversion查看cuda版本 cat /usr/local/cuda/version.txt或者 nvcc …

[Qt] QString::fromLocal8Bit 的使用误区

QString::fromLocal8Bit 是一个平台相关的函数。默认情况下在 Windows 下 就是 gbk 转 utf-8 ,在 Linux就应该是无事发生。因为Linux平台默认的编码方式就是 utf-8 可以通过 void QTextCodec::setCodecForLocale(QTextCodec *c)来修改 Qt默认的编码方式。如下 第一输出乱码的…

【单调队列单调栈专题】【蓝桥杯备考训练】:矩形牛棚、单调栈、滑动窗口、子矩阵、最大子序和、烽火传递【已更新完成】

目录 1、矩形牛棚&#xff08;usaco training 6.1&#xff09; 思路&#xff1a; 预处理的过程&#xff1a; 判断左右边界的过程&#xff1a; 代码&#xff1a; 2、单调栈&#xff08;单调栈模板&#xff09; 思路&#xff1a; 基本步骤&#xff1a; 1、维护单调性 2、处理要求…

vue组件如何使用?

今天我随便试两个组件 第一个轮播图 在minn.js 引入 import { createApp } from vue; import { Swipe, SwipeItem } from vant; const app createApp(); app.use(Swipe); app.use(SwipeItem); <van-swipe class"my-swipe" :autoplay"3000" indica…

python学习15:python中的input语句

python中的input语句 我们前面学习过print语句&#xff0c;可以将内容输出到屏幕上&#xff1b;在python中&#xff0c;与之对应的还有一个input语句&#xff0c;用来获取键盘输入。 数据输出&#xff1a;print 数据输入&#xff1a;input 使用上也很简单&#xff1a; 使用inp…

jvm高级面试题-2024

说下对JVM内存模型的理解 JVM内存模型主要是指Java虚拟机在运行时所使用的内存结构。它主要包括堆、栈、方法区和程序计数器等部分。 堆是JVM中最大的一块内存区域&#xff0c;用于存储对象实例。一般通过new关键字创建的对象都存放在堆中&#xff0c;堆的大小可以通过启动参数…

http 超全状态码

HTTP&#xff08;超文本传输协议&#xff09;是一种用于传输超文本的应用层协议。在进行HTTP通信时&#xff0c;服务器会向客户端返回一个状态码&#xff0c;用于表示请求的处理结果。 状态码由3位数字组成&#xff0c;使用第1个数字表示响应的类别&#xff0c;一共5种&#x…

广告牌效果的C#实现

前言 这个效果是在以前的项目时候&#xff0c;特效那边想要一个广告牌效果但是我不懂什么是广告牌两个人沟通半天&#xff0c;才把东西做出来。 代码如下 using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine;[ExecuteInEdit…