Java核心之细说泛型

泛型是什么?

等你使用java逐渐深入以后会了解或逐步使用到Java泛型。Java 中的泛型是 JDK 5 中引入的功能之一。"Java 泛型 "是一个技术术语,表示一组与定义和使用泛型类型和方法有关的语言特性。在 Java 中,泛型类型或方法与普通类型和方法的区别在于它们具有类型参数。

入门

如果仔细观察集合框架类,就会发现大多数类都使用对象类型的参数,并以对象形式从方法中返回值。现在,在这种形式下,它们可以将任何 Java 类型作为参数并返回相同的值。它们本质上是异构的,即不属于特定的相似类型。

像我们这样的程序员经常希望指定一个集合只包含某种类型的元素,例如Integer or String 或 Employee。在最初的集合框架中,如果不在代码中添加额外的检查,就不可能实现同质集合。引入泛型就是为了消除这一限制。它们会在编译时自动在代码中添加这种类型的参数检查。这样,我们就不必编写大量不必要的代码,如果编写得当,这些代码在运行时实际上不会增加任何价值。

泛型通过提供实际的类型参数来替代形式化的类型参数,从而实例化形成参数化的类型。例如下面这样:

public class LinkedList<E> ...
LinkedList<String> list = new LinkedList();
  • 解释
  1. 像 LinkedList 这样的类是一种具有类型参数 E 的泛型。
  2. 像 LinkedList 或 LinkedList 这样的实例被称为参数化类型。
  3. 字符串和整数是各自的实际类型参数。

通俗地说,泛型强制保证了 Java 语言的类型安全。

现在,我们已经对 Java 中为什么会出现泛型有了一定的了解。下一步是了解 Java 中的泛型是如何工作的。在源代码中使用泛型时究竟会发生什么?

泛型是如何工作的?

泛型的核心是 “类型安全”。究竟什么是类型安全?它只是编译器的一种保证,即如果在正确的地方使用了正确的类型,那么在运行时就不会出现任何 ClassCastException

一个用例可以是一个整数列表,即 List。如果您声明了 List 这样的列表,那么 Java 保证会检测并报告在上述列表中插入任何非整数类型的尝试。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add("one");  //compiler error

类型安全

泛型的核心是 “类型安全”。究竟什么是类型安全?它只是编译器的一种保证,即如果在正确的地方使用了正确的类型,那么在运行时就不会出现任何 ClassCastException。

一个用例可以是一个整数列表,即 List。如果您声明了 List 这样的列表,那么 Java 保证会检测并报告在上述列表中插入任何非整数类型的尝试。

List<Integer> list = new ArrayList<>();
list.add(1);
list.add("one");  //compiler error

类型擦除

泛型的另一个重要术语是 “类型擦除”。它的基本意思是,使用泛型添加到源代码中的所有额外信息都将从生成的字节码中删除。在字节码中,如果完全不使用泛型,得到的将是旧的 Java 语法。这必然有助于生成和执行 Java 5 之前编写的代码,因为 Java 5 尚未在语言中添加泛型。
来看一个例子:

List<Integer> list = new ArrayList<>();
list.add(1000);

如果将上述示例的字节码与带/不带泛型的字节码进行比较,那么两者将没有任何区别。显然,编译器删除了所有泛型信息。因此,上面的代码与下面没有使用泛型的代码非常相似。

List list = new ArrayList();
list.add(1000);

准确地说,Java 中的 "泛型 "只不过是为了类型安全而给代码添加的语法糖,所有这些类型信息都会被编译器的 "类型清除 "功能抹去。

泛型的分类

现在,我们对通用语有了一些了解。现在开始探索围绕泛型的其他重要概念。首先,我将介绍将属类应用于源代码的各种方法。

类或接口

如果一个类声明了一个或多个类型变量,那么这个类就是泛型。这些类型变量被称为类的类型参数。让我们通过一个例子来理解。
DemoClass 是一个简单的类,它有一个属性 t(也可以多个),属性类型是对象。

class DemoClass {private Object t;public void set(Object t) { this.t = t; }public Object get() { return t; }
}

例如,如果我们希望类的一个实例持有 "字符串 "类型的值 t,那么程序员就应该设置和获取唯一的字符串类型。

由于我们已将属性类型声明为对象,因此无法强制执行这一限制。程序员可以设置任何对象,也可以期望从 get() 方法中得到任何返回值类型,因为所有 Java 类型都是对象类的子类型。

为了实现这种限制,我们可以使用下面的泛型:

class DemoClass<T> {//T stands for "Type"private T t;public void set(T t) { this.t = t; }public T get() { return t; }
}

现在我们可以放心,类不会被错误地使用。DemoClass 的使用示例如下:

DemoClass<String> instance = new DemoClass<>();
instance.set("lokesh");   //Correct usage
instance.set(1);        //This will raise compile time error

上述类比同样适用于接口。让我们快速看一个例子,了解接口中如何使用泛型类型信息。

//Generic interface definition
interface DemoInterface<T1, T2>
{T2 doSomeOperation(T1 t);T1 doReverseOperation(T2 t);
}
//A class implementing generic interface
class DemoClass implements DemoInterface<String, Integer>
{public Integer doSomeOperation(String t){//some code}public String doReverseOperation(Integer t){//some code}
}

我希望我已经说得足够清楚,让大家对泛型类和接口有了一些了解。现在我们来看看泛型方法和构造函数。

泛型方法和构造函数

泛型方法与泛型类非常相似。它们只有一点不同,即类型信息的范围只在方法(或构造函数)内部。泛型方法是引入自己的类型参数的方法。

让我们通过一个例子来理解这一点。下面是一个泛型方法的代码示例,该方法可用于查找类型参数在该类型变量列表中的所有出现次数

public static <T> int countAllOccurrences(T[] list, T item) {int count = 0;if (item == null) {for ( T listItem : list )if (listItem == null)count++;}else {for ( T listItem : list )if (item.equals(listItem))count++;}return count;
}

如果在此方法中传递一个字符串列表和另一个要搜索的字符串,它将正常工作。但如果试图在字符串列表中查找一个 Number,则会在编译时出错。

让我们再举一个泛型构造函数的例子。

public class MyClass<T> {private T value;// 泛型构造函数public MyClass(T value) {this.value = value;}public T getValue() {return value;}public void setValue(T value) {this.value = value;}
}
MyClass<String> myString = new MyClass<>("Hello");
MyClass<Integer> myInt = new MyClass<>(42);

泛型数组

任何语言中的数组都有相同的含义,即数组是相似类型元素的集合。在 Java 中,运行时在数组中推送任何不兼容的类型都会引发 ArrayStoreException。这意味着数组会在运行时保留其类型信息,而泛型会在运行时使用类型擦除或删除任何类型信息。由于上述冲突,不允许实例化泛型数组。

public class GenericArray<T> {// this one is finepublic T[] notYetInstantiatedArray;// causes compiler error; Cannot create a generic array of Tpublic T[] array = new T[5];
}

与上述通用类型类和方法相同,我们也可以使用通用数组。我们知道,数组是相似类型元素的集合,推送任何不兼容的类型都会在运行时抛出 ArrayStoreException;而集合类则不会出现这种情况。

Object[] array = new String[10];
array[0] = "lokesh";
array[1] = 10;      //This will throw ArrayStoreException

上述错误并不难犯。它随时都可能发生。因此,最好也为数组提供类型信息,以便在编译时就能发现错误。

数组不支持泛型的另一个原因是数组是共变的,这意味着超类型引用数组是子类型引用数组的超类型。也就是说,Object[] 是 String[] 的超类型,可以通过 Object[] 类型的引用变量访问字符串数组。

Object[] objArr = new String[10];  // fine
objArr[0] = new String();

泛型通配符

在泛型代码中,问号(?)被称为通配符,代表未知类型。通配符参数化类型是泛型类型的实例化,其中至少有一个类型参数是通配符。通配符参数化类型的例子有 Collection<?<、List<? extends Number<、Comparator<? super String> 和 Pair

在不同位置放置通配符也有不同的含义,例如:

Collection 表示 Collection 接口的所有实例,与类型参数无关。
List 表示元素类型为 Number 子类型的所有列表类型。
Comparator<? super String< 表示类型参数类型为 String 的超类型的比较器接口的所有实例。
通配符参数化类型并不是可以出现在新表达式中的具体类型。它只是暗示了泛型执行的规则,即在使用了通配符的任何特定场景中,哪些类型是有效的。

例如,下面是涉及通配符的有效声明:

Collection<?> coll = new ArrayList<String>();
//OR
List<? extends Number> list = new ArrayList<Long>();
//OR
Pair<String,?> pair = new Pair<String,Integer>();

以下是通配符的无效使用,编译时会出错。

List<? extends Number> list = new ArrayList<String>();  //String is not subclass of Number; so error
//OR
Comparator<? super String> cmp = new RuleBasedCollator(new Integer(100)); //Integer is not superclass of String

泛型中的通配符可以是无界的,也可以是有界的。让我们从不同的术语中找出区别。

无界通配符参数化类型

通用类型,所有类型参数都是无限制通配符"?",对类型变量没有任何限制,例如:

ArrayList<?>  list = new ArrayList<Long>();
//or
ArrayList<?>  list = new ArrayList<String>();
//or
ArrayList<?>  list = new ArrayList<Employee>();

有界通配符参数化类型

有界通配符对我们可以用来实例化参数化类型的可能类型施加了一些限制。这种限制通过关键字 "super "和 "extends "来实现。为了更清楚地区分,我们把它们分为上界通配符和下界通配符。

上界通配符

例如,如果您想编写一个适用于 List、List 和 List 的方法,您可以通过使用有上界的通配符来实现,例如,您可以指定 List<? extends Number>。这里,Integer 和 Double 是 Number 类的子类型。通俗地说,如果您想让泛型表达式接受某一特定类型的所有子类,您可以使用关键字 "extends "来使用上界通配符:

public class GenericsExample<T>
{public static void main(String[] args){//List of IntegersList<Integer> ints = Arrays.asList(1,2,3,4,5);System.out.println(sum(ints));//List of DoublesList<Double> doubles = Arrays.asList(1.5d,2d,3d);System.out.println(sum(doubles));List<String> strings = Arrays.asList("1","2");//This will give compilation error as :: The method sum(List<? extends Number>) in the //type GenericsExample<T> is not applicable for the arguments (List<String>)System.out.println(sum(strings));}//Method will accept private static Number sum (List<? extends Number> numbers){double s = 0.0;for (Number n : numbers)s += n.doubleValue();return s;}
}

下界通配符

如果想让泛型表达式接受所有类型,这些类型都是某个特定类型的 "超级 "类型或某个特定类的父类,那么就可以使用 "super "关键字的下界通配符来实现这一目的。

在下面的示例中,我创建了三个类,即 SuperClassChildClassGrandChildClass。它们之间的关系如下代码所示。现在,我们必须创建一个方法,以某种方式获取 GrandChildClass 的信息(例如,从数据库中获取)并创建一个实例。我们希望将这个新的 GrandChildClass 存储在已经存在的 GrandChildClasses 列表中。

这里的问题是,GrandChildClass 是 ChildClass 和 SuperClass 的子类型。因此,任何 SuperClasses 和 ChildClasses 的通用列表都可以容纳 GrandChildClasses。在这里,我们必须使用 "super "关键字,借助下界通配符。

public class GenericsExample<T>
{public static void main(String[] args){//List of grand childrenList<GrandChildClass> grandChildren = new ArrayList<GrandChildClass>();grandChildren.add(new GrandChildClass());addGrandChildren(grandChildren);//List of grand childsList<ChildClass> childs = new ArrayList<ChildClass>();childs.add(new GrandChildClass());addGrandChildren(childs);//List of grand supersList<SuperClass> supers = new ArrayList<SuperClass>();supers.add(new GrandChildClass());addGrandChildren(supers);}public static void addGrandChildren(List<? super GrandChildClass> grandChildren){grandChildren.add(new GrandChildClass());System.out.println(grandChildren);}
}
class SuperClass{
}
class ChildClass extends SuperClass{
}
class GrandChildClass extends ChildClass{
}

哪些行为是不允许的?

到目前为止,我们已经了解了一些使用泛型可以避免在应用程序中出现大量 ClassCastException 实例的方法。我们还了解了通配符的用法。现在,我们要确定一些不允许使用泛型的行为。

静态泛型成员

我们不能在类中定义静态泛型参数化成员。任何这样的尝试都会在编译时产生错误:无法对非静态类型 T 进行静态引用。

public class GenericsExample<T>
{private static T member; //This is not allowed
}

不能实例化泛型

任何创建 T 实例的尝试都会失败,并显示错误:无法实例化 T 类型。

public class GenericsExample<T>
{public GenericsExample(){new T();}
}

泛型与声明中的原始类型不兼容

是的,没错。您不能声明 List 或 Map<String, double> 这样的泛型表达式。当然,您可以使用包装类代替基本类型,然后在传递实际值时使用基本类型。这些基本类型值可以通过使用自动装箱将基本类型转换为相应的包装类来接受。

final List<int> ids = new ArrayList<>();    //Not allowed
final List<Integer> ids = new ArrayList<>(); //Allowed

我们无法创建泛型异常类

有时,程序员可能需要在抛出异常的同时传递一个泛型类型的实例。这在 Java 中是做不到的。

// causes compiler error
public class GenericException<T> extends Exception {}

当您尝试创建这样一个异常时,您将看到这样一条信息:通用类 GenericException 可能无法子类化 java.lang.Throwable。

关于 Java 泛型的先写到这里,凡事还是需要多实践!

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

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

相关文章

分享axios+MQTT简单封装示例

MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&#xff09;模式的"轻量级"通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c;由IBM在19…

k8s发布nacos-server,nodeport配置注意事项

k8s发布nacos-server注册不上问题 问题描述&#xff1a;分析过程&#xff1a; 问题描述&#xff1a; k8s发布nacos-server做服务公用使用&#xff0c;nodeport暴漏服务给客户端注册&#xff0c; nacos:端口 8848&#xff1a;30601 9848&#xff1a;30701 分析过程&#xff1a…

二维码门楼牌管理系统在教育领域的应用及其优势

文章目录 前言一、二维码门楼牌管理系统概述二、教育领域的应用场景三、二维码门楼牌管理系统的优势四、结语 前言 随着信息技术的快速发展&#xff0c;二维码门楼牌管理系统在教育领域的应用越来越广泛。该系统不仅提高了地址信息的准确性&#xff0c;还为学校、家长和教育工…

关于华为昇腾(Ascend)AI芯片,CANN计算架构,MindSpore深度学习框架,MindStudio开发工具

1、华为昇腾生态 深度学习之前的配置都是&#xff1a;NVIDIA GPU / CPU CUDA Tensorflow/PyTorch 后来老美禁止 NVIDIA 卖GPU芯片给我们&#xff0c;于是国内企业开始发力CPU和GPU硬件&#xff0c;成果丰硕&#xff0c;虽然与NVIDIA顶级GPU还有一些差距&#xff0c;但是也不…

npm包停止了对 require 导入方式的支持,只允许使用import 导入方式,怎么解决

如果你发现一个npm包已经停止支持require导入方式&#xff0c;并且只允许使用ES6的import语法&#xff0c;你可以尝试以下几种方法来解决这个问题&#xff1a; 更新你的Node.js版本&#xff1a;确保你使用的Node.js版本是支持ES6模块导入的。Node.js 从 v13.2.0 开始默认支持ES…

eclipse搭建java web项目

准备条件 eclipsejdk1.8 &#xff08;配置jdk环境&#xff09;apache-tomcat-8.5.97&#xff08;记住安装位置&#xff09; 一 点击完成 开始创建javaweb项目 import java.io.IOException; import java.io.PrintWriter;import javax.servlet.ServletException; import javax.s…

【经典案例】某大型公园构建检查监督机制项目纪实

——引入网格化监督管理机制&#xff0c;实现责任、人员、信息三位一体 公园管理由于其本身地域范围广的特性在工作中很难进行有效的监督检查&#xff0c;该公园的监督检查由不同的部门分别负责&#xff0c;同部门检查时往往会处于情面而使检查流于形式&#xff0c;并且公园的监…

Media Encoder 2024:未来媒体编码的新纪元 mac/win版

随着科技的飞速发展&#xff0c;媒体内容已成为我们日常生活中不可或缺的一部分。为了满足用户对高质量视频内容不断增长的需求&#xff0c;Media Encoder 2024应运而生&#xff0c;它凭借卓越的技术和创新的特性&#xff0c;重塑了媒体编码的未来。 Media Encoder 2024软件获…

嵌入式学习day34 网络

TCP包头: 1.序号:发送端发送数据包的编号 2.确认号:已经确认接收到的数据的编号(只有当ACK为1时,确认号才有用) TCP为什么安全可靠: 1.在通信前建立三次握手连接 SYN SYNACK ACK 2.在通信过程中通过序列号和确认号保障数据传输的完整性 本次发送序列号:上次…

LeetCode Python - 42.接雨水

目录 题目答案运行结果 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组…

【网站项目】096实验室开放管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

错误与异常之为何要异常

为什么用异常 毫无疑问,错误的存在会伴随着软件的存在.区别在于当今快节奏的计算世界, 我们的执行环境 已经改变, 所以我们需要改变错误处理, 以准确反映我们软件的开发环境. 就现今应用来说, 普遍 的是自洽(self-contained)的图形用户界面(GUIs)或是客户机/服务器体系, 例如 …

精通ChatGPT:掌握高效Prompt设计的艺术与科学

精通ChatGPT&#xff1a;掌握高效Prompt设计的艺术与科学 在人工智能的迅猛发展中&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术已经成为了一个热门的话题。OpenAI推出的ChatGPT是基于强大的GPT-3模型&#xff0c;它通过深度学习的方法训练出能够理解和生成人类语…

Java面试——Netty

优质博文&#xff1a;IT-BLOG-CN 一、BIO、NIO 和 AIO 【1】阻塞 IO(Blocking I/O)&#xff1a; 同步阻塞I/O模式&#xff0c;当一条线程执行 read() 或者 write() 方法时&#xff0c;这条线程会一直阻塞直到读取一些数据或者写出去的数据已经全部写出&#xff0c;在这期间这条…

泛微OA服务器获取 token

泛微OA服务器获取 token 文章目录 泛微OA服务器获取 token一、泛微官方方法1 ecology 系统配置2 发放/生成许可证(appid)3 限制许可证使用ip地址&#xff08;该步骤也可以跳过&#xff09;4 使用 postman 注册5 获取 token6 访问业务系统接口 二、java 代码获取 token三、封装到…

ospf介绍

Ospf首先是一种内部网关协议,其描述的路由信息属于同一个自制系统。所谓自制系统,即属于该自制系统的路由器都必须使用同一种内部网关协议。在这里,即为ospf。 Ospf的一些常用术语:Ospf协议的内容比较丰富,涉及的术语、定义较多。在这里有一些常用的定义: 自制系统(AS)…

4 AI系统的测试关键技术和实践

4 AI系统的测试关键技术和实践 4.1 功能测试的困局 在传统系统的功能测试中,测试工程师无论是做手工测试还是自动化测试,都必须先设计和开发测试用例,然后才能利用测试用例完成测试工作,给出测试结论。从这里可以看出,测试用例是测试工作中很重要的产出物。IEEE 610在19…

20240308-2-校招前端面试常见问题-网络及浏览器

校招前端面试常见问题【4】——网络及浏览器 1、网络相关 Q&#xff1a;请简述一下 HTTP 协议&#xff0c;以及 HTTP1.0/1.1/2.0/3.0 的区别&#xff1f; HTTP 协议&#xff1a;超文本传输协议&#xff0c;使用 TCP/IP 协议传输数据。是一个应用层的协议。 HTTP1.0&#xff…

特性螺旋面的刀具设计记录

最近和成型类刀具杠上了&#xff0c;这不最近有小伙伴提供了两个比较特殊的螺旋面工件&#xff0c;通常称作阴、阳转子。具体形状如下&#xff1a; 阴转子 阴转子端面齿形没看出有什么特殊的&#xff0c;但是在轴剖面齿形是内凹的&#xff0c;这个是比较特殊的形式。 阳转子…

FFmpeg--音频解码流程:aac解码pcm

文章目录 音频解码流程API分析&#xff1a;伪代码code: 音频解码流程 aac—音频解码器–pcm数据 API avcodec_find_decoder&#xff1a;根据指定的AVCodecID查找注册的解码器 av_parser_init&#xff1a;初始化AVCodecParserContext avcodec_alloc_context3&#xff1a;为AV…