【泛型】JAVA基础篇(三)

泛型(Generics)是Java编程语言中的一个强大的特性,它提供了编译时类型安全检测机制,这意味着可以在编译期间检测到非法的类型。泛型的使用减少了程序中的强制类型转换和运行时错误的可能性。

一、泛型使用规范

  • 类型参数命名约定:通常使用单个大写字母来表示类型参数,例如 E 表示集合的元素类型,K 和 V 分别代表键和值的类型,T 通常代表"类型"(Type)。
  • 泛型类和接口:在定义泛型类或接口时,你应该在类名后面加上尖括号,并在其中声明类型参数。例如:class Box<T>
  • 泛型方法:在方法返回类型之前声明类型参数,以使该方法成为泛型方法。如 public <T> void method(T param)。
  • 类型通配符:使用 ? 表示未知类型。? extends T 表示接受 T 或其子类(上界通配符),? super T 表示接受 T
    或其父类(下界通配符)。
  • 类型擦除:泛型信息仅在编译阶段存在,在进入 JVM 前,与泛型有关的类型信息就会被擦除,这个过程称为类型擦除。类型擦除是为了兼容旧版本的Java。
  • 避免创建泛型数组:Java 不允许实例化泛型数组,如 new List[] 是非法的。可以通过创建类型为 List[]
    的数组然后将其转换为 List<T>[] 来绕过这个限制,但这种做法不安全并会引发警告。
  • 限制:由于类型擦除,某些操作在泛型中是不允许的,比如:判断 (instanceof) 泛型类型、创建泛型实例 (new
    T())、创建泛型的数组。

二、泛型的基本用法

1、泛型类

public class Box<T> {private T t;public void set(T t) {this.t = t;}public T get() {return t;}
}// 实例化一个泛型类
Box<Integer> integerBox = new Box<>();
integerBox.set(10);Box<String> stringBox = new Box<>();
stringBox.set("Hello World");

2、泛型接口

// 定义一个泛型接口
public interface Pair<K, V> {public K getKey();public V getValue();
}// 实现泛型接口
public class OrderedPair<K, V> implements Pair<K, V> {private K key;private V value;public OrderedPair(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}
}// 使用泛型接口
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<>("hello", "world");

3、泛型方法

// 定义一个泛型方法
public class Util {public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {return p1.getKey().equals(p2.getKey()) &&p1.getValue().equals(p2.getValue());}
}// 使用泛型方法
Pair<Integer, String> p1 = new OrderedPair<>(1, "apple");
Pair<Integer, String> p2 = new OrderedPair<>(2, "pear");
boolean same = Util.compare(p1, p2);

4、有界类型参数(边界类型)

public <U extends Number> void inspect(U u){System.out.println("T: " + u.getClass().getName());
}// 使用有界类型参数
inspect(123);
inspect(12.34);

5、通配符类型

public void printBoxContent(Box<?> box){System.out.println("The box contains: " + box.get());
}// 使用通配符类型
Box<Integer> integerBox = new Box<>();
integerBox.set(10);Box<String> stringBox = new Box<>();
stringBox.set("Hello World");printBoxContent(integerBox);
printBoxContent(stringBox);

三、无泛型 和 有泛型对比

在引入泛型之前,Java 集合框架没有类型检查,所以所有对象都被当做 Object 类型存储
以下是一个没有使用泛型的例子:

import java.util.ArrayList;
import java.util.List;public class NonGenericExample {public static void main(String[] args) {List integerList = new ArrayList();// 添加整数到集合integerList.add(new Integer(10)); // 自动装箱为 IntegerintegerList.add(new Integer(20));// 尝试添加一个字符串到整数列表// 编译器不会报错,运行时也不会出错integerList.add("thirty"); // 这是不安全的操作// 获取元素并进行类型转换Integer firstNumber = (Integer) integerList.get(0);Integer secondNumber = (Integer) integerList.get(1);// 这里尝试将第三个元素转换为 Integer,但它实际上是一个 String// 这会引发 ClassCastExceptiontry {Integer thirdNumber = (Integer) integerList.get(2);} catch (ClassCastException e) {System.out.println("Error: " + e.getMessage());}// 输出正确的整数值System.out.println("First number: " + firstNumber);System.out.println("Second number: " + secondNumber);}
}

在上面的代码中,我们创建了一个非泛型的 ArrayList,并向其中添加了两个 Integer 对象和一个 String 对象。由于没有类型检查,ArrayList 允许我们添加任何类型的对象,而不会在编译时报错。但是,当我们试图将 String 对象转换为 Integer 时,程序会在运行时抛出 ClassCastException,因为这是一个无效的转换。

引入泛型后,我们可以在编译时期进行类型检查,从而避免这种类型安全问题。下面是使用泛型的代码示例:

import java.util.ArrayList;
import java.util.List;public class GenericExample {public static void main(String[] args) {List<Integer> integerList = new ArrayList<Integer>();// 添加整数到集合integerList.add(10); // 自动装箱为 IntegerintegerList.add(20);// 下面的代码会在编译时报错,防止了运行时错误// integerList.add("thirty"); // 编译错误// 获取元素时不需要进行类型转换Integer firstNumber = integerList.get(0);Integer secondNumber = integerList.get(1);// 输出正确的整数值System.out.println("First number: " + firstNumber);System.out.println("Second number: " + secondNumber);}
}

在这个泛型版本的例子中,如果尝试将一个不是 Integer 类型的对象添加到 integerList 中,编译器将会报错,从而保证了类型安全。这使得代码更安全,更清晰,也更容易维护。

四、泛型擦除

类型擦除是 Java 编译器应用的过程,它允许泛型代码与不支持泛型的旧有 Java 代码兼容。在这个过程中,编译器将泛型类型参数替换为它们的限定边界(如果存在),或者其他情况下替换为 Object。这意味着在编译后的 Java 字节码中,所有的泛型类型信息都会丢失。

1、背景

Java在5.0版本中引入了泛型,这使得开发者能够在编写集合类(如List、Map等)时指定集合中元素的类型,例如 List<String> 或 Map<Integer, String>。在引入泛型之前,所有的集合中的对象都是 Object 类型,这需要显式的类型转换,并且可能导致运行时错误。

为了保证向后兼容,Java的泛型是通过类型擦除来实现的。这意味着泛型信息只在编译阶段有效,一旦代码被编译,所有泛型类型参数都会被擦除,替换为它们的限定边界类型(如果有的话)或者Object。这也意味着在运行时,我们无法获取到泛型的类型参数信息。

2、泛型擦除实例

public class GenericErasure<T> {public void doSomething(T t) {System.out.println(t);}
}// 在编译后的class文件中,geString 和 geInteger 类型实际上是相同的
// 它们都被擦除成了 GenericErasure 类型,使用的是 Object 类型,如下:public class GenericErasure {public void doSomething(Object t) {System.out.println(t);}}

如上,编译后 泛型 T 变成了 Object,泛型类型信息丢失,转变成了 Object,使得和不支持泛型的java代码一致。

3、限定边界

在Java泛型中,“限定边界”(Bounded Type)是指对可以使用的泛型的类型参数进行限制。限定边界可以是一个特定的类,或者是一个满足特定接口的任何类型。使用限定边界可以确保传递给泛型类型的类型参数满足某些基本要求,这样就可以在类或方法内安全地调用定义在边界类型上的方法。

限定边界有两种类型:

  • 上界限定(Upper Bounded Wildcards):? extends Type 表示参数化类型可能是指定的类型,或者是此类型的子类。
  • 下界限定(Lower Bounded Wildcards):? super Type 表示参数化类型可能是指定的类型,或者是此类型的父类。

这里是针对上界限定的一个例子:

假设你有一个类 Animal 和两个子类 Dog 和 Cat。你想要写一个方法,这个方法可以接受 Animal 的任何子类的列表。你可以通过使用限定边界来实现:

public class Animal {public void feed() {// ...}
}public class Dog extends Animal {// ...
}public class Cat extends Animal {// ...
}public class Main {public static void feedAnimals(List<? extends Animal> animals) {for (Animal a : animals) {a.feed();}}public static void main(String[] args) {List<Dog> dogs = Arrays.asList(new Dog(), new Dog());List<Cat> cats = Arrays.asList(new Cat(), new Cat());feedAnimals(dogs); // 正确:dogs 是 Animal 的子类的 ListfeedAnimals(cats); // 正确:cats 是 Animal 的子类的 List}
}

在这个例子中,feedAnimals 方法的参数 animals 使用了限定边界 ? extends Animal,这意味着这个参数可以是 List、List、List 或任何 Animal 子类的列表。

当你没有具体的限定边界时,你可以简单地使用 Object 类型,因为在Java中所有的类都是 Object 的子类。如果你的泛型没有指定上界或下界,它其实隐式的被限定为 Object。例如:

public static void printList(List<?> list) {for (Object obj : list) {System.out.println(obj);}
}

在这个例子中,printList 方法接受一个未知类型的列表,并且由于每个对象最终都继承自 Object 类,我们可以安全地将列表中的每个元素作为 Object 输出。

总结:
限定边界用来指定泛型类型参数必须继承自特定的父类,或者实现特定的接口,从而在编译时期就保证了类型安全。
如果没有特定的限定边界,泛型类型默认是 Object。

4、结论

泛型:提供了编译时类型安全检测机制,预防了运行时报错异常问题。

泛型擦除:保证了泛型代码与不支持泛型的旧有 Java 代码兼容

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

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

相关文章

【LeetCode】70. 爬楼梯

70. 爬楼梯 难度&#xff1a;简单 题目 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼…

面试刷题自备中

面试刷题自备中 JavaSE基础线程redisMQkafkaspringspringmvcspringbootspringcloud设计模式数据库FastDFS垃圾回收机制项目部署dockerlinux JavaSE基础 面向对象都有哪些特性以及你对这些特性的理解访问权限修饰符 public、private、protected, 以及不写&#xff08;默认&…

asp.net core webpi 结合jwt实现登录鉴权

1.安装jwt nuget包 <PackageReference Include"Microsoft.AspNetCore.Authentication.JwtBearer" Version"6.0.25" /><PackageReference Include"System.IdentityModel.Tokens.Jwt" Version"7.0.3" />2.配置jwt信息 build…

<JavaDS> 二叉树遍历各种遍历方式的代码实现 -- 前序、中序、后序、层序遍历

目录 有以下二叉树&#xff1a; 一、递归 1.1 前序遍历-递归 1.2 中序遍历-递归 1.3 后序遍历-递归 二、递归--使用链表 2.1 前序遍历-递归-返回链表 2.2 中序遍历-递归-返回链表 2.3 后序遍历-递归-返回链表 三、迭代--使用栈 3.1 前序遍历-迭代-使用栈 3.2 中序遍…

服务器bash进程占用cpu过多疑似中挖矿病毒记录

发现过程 因为我有使用conky的习惯&#xff0c;也就是在桌面上会显示cpu和内存的占用情况&#xff0c;由于服务器不止我一个人使用&#xff0c;最近发现好几次我同学的账户下的bash进程占用特别多&#xff0c;问了他之后&#xff0c;他也说他几次都是没有使用过bash相关服务&a…

【LeetCode】每日一题 2023_11_28 设计前中后队列(数组/链表/双端队列)

文章目录 刷题前唠嗑题目&#xff1a;设计前中后队列题目描述代码与解题思路偷看大佬题解 结语 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 这道题的难度&#xff0c;才是我想象中的中等题的难度好吧&#xff0c;昨天那玩意对我来说还是太难了…

9.Spring 整合 Redis

引入依赖&#xff1a;spring-boot-starter-data-redis配置 Redis&#xff1a;配置数据库参数、编写配置类&#xff0c;构造 RedisTemplate访问 Redis&#xff1a; redisTemplate.opsForValue() redisTemplate.opsForHash() redisTemplate.opsForList() redisTemplate.opsForSe…

Vue3-toRaw 和 markRaw 函数

Vue3-toRaw 和 markRaw 函数 toRaw(转换为原始)&#xff1a;将响应式对象转换为普通对象&#xff0c;只适用于 reactive 生成的响应式对象。markRaw(标记为原始)&#xff1a;标记某个对象&#xff0c;让这个对象永远都不具备响应式。一些集成的第三方库&#xff0c;会有大量的…

华为认证大数据工程师(HCIA-Big Data)--练习题

华为认证大数据工程师&#xff08;HCIA-Big Data&#xff09;–练习题 单线 1、Hive定义一个自定义函数类时&#xff0c;需要继承以下哪个类&#xff1f; B A.FunctionRegistry B.UDF C.MapReduce2、 多选 1、以下场景中&#xff0c;哪些选项属于华为数据治理中心DataArts …

UE4 UE5 使用SVN控制

关键概念&#xff1a;虚幻引擎中使用SVN&#xff0c;帮助团队成员共享资源。 1. UE4/UE5项目文件 如果不需要编译的中间缓存&#xff0c;则删除&#xff1a; DerivedDataCache、Intermediate、Saved 三个文件夹 2.更新、上传

Hive进阶函数:SPACE() 一行炸裂指定行

数据一行如何转多行 假如有一张表&#xff0c;字段有两个&#xff0c;分别是name 和 number&#xff0c;代表含义为名字 和 名字出现的次数&#xff0c;现在需要把一行数据转为number行 举例&#xff1a; 输入&#xff1a; tom&#xff5c;3jery&#xff5c;4输出&#xff1a…

C++单调向量(栈):好子数组的最大分数

作者推荐 利用广度优先或模拟解决米诺骨牌 题目 给你一个整数数组 nums &#xff08;下标从 0 开始&#xff09;和一个整数 k 。 一个子数组 (i, j) 的 分数 定义为 min(nums[i], nums[i1], …, nums[j]) * (j - i 1) 。一个 好 子数组的两个端点下标需要满足 i < k <…

【LangChain实战】LangChain快速入门

1、什么是大语言模型 大语言模型是一种人工智能模型&#xff0c;通常使用深度学习技术&#xff0c;比如神经网络&#xff0c;来理解和生成人类语言。这些模型的“大”在于它们的参数数量非常多&#xff0c;可以达到数十亿甚至更多&#xff0c;这使得它们能够理解和生成高度复杂…

【Vue3】

组合式API setup选项 执行时机&#xff1a;比beforeCreate早 不能使用this 数据和函数需要return才能应用 标准写法 <script>export default {setup() {const message "vue32"const logMessage () > {console.log(message)}return {message,logMess…

MATLAB算法实战应用案例精讲-【图像处理】机器人视觉

目录 前言 机器人视觉发展历程 知识储备 光的偏振 01偏振光 02偏振应用

PostgreSQL数据库中的后台进程

在 PostgreSQL 中&#xff0c;有多个后台进程&#xff08;也称为工作者进程&#xff09;&#xff0c;负责处理不同的任务和功能。这些进程包括但不限于以下几种&#xff1a; 1. PostgreSQL数据库中的后台进程 1.1. Postmaster 进程 这是 PostgreSQL 服务器的主进程&#xff…

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号livox 介绍更换环境更换livox激光雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 介绍 览沃科技有限公司&#xff08;Livox&#xff…

【动态规划】求最长递增子序列问题

目录 问题描述递推关系建立递推关系的思路约束条件:以 s [ k ] s[k] s[k] 结尾约束条件:以 s [ k ] s[k] s[k] 开头约束条件:增加子问题参数&#xff08;前缀&#xff09;约束条件:增加子问题参数&#xff08;后缀&#xff09;约束条件:LIS长度为k且末尾元素最小 运行实例 问…

将图像的rgb数据转成DICOM医学图像格式

dcmtk官方文档&#xff1a;https://support.dcmtk.org/docs/ dcmtk最新源码下载&#xff1a;https://www.dcmtk.org/en/dcmtk/dcmtk-software-development/ dcmtk旧版本源码下载&#xff1a;https://dicom.offis.de/download/dcmtk/ 用DCMTK库实现将图像转成dcm格式 dcmtk库的…

linux logrotate日志轮询设置案例一

1.编辑/etc/logrotate.conf文件&#xff0c;添加如下配置&#xff0c;并保存 /var/log/ztj.log {missingokhourlycreate 644 root rootsharedscriptspostrotateif [ -f /var/run/syslogd.pid ];then/bin/kill -HUP $(/bin/cat /var/run/syslogd.pid) >/dev/null 2>&…