Java核心之细说泛型
泛型是什么?
等你使用java逐渐深入以后会了解或逐步使用到Java泛型。Java 中的泛型是 JDK 5 中引入的功能之一。"Java 泛型 "是一个技术术语,表示一组与定义和使用泛型类型和方法有关的语言特性。在 Java 中,泛型类型或方法与普通类型和方法的区别在于它们具有类型参数。
入门
如果仔细观察集合框架类,就会发现大多数类都使用对象类型的参数,并以对象形式从方法中返回值。现在,在这种形式下,它们可以将任何 Java 类型作为参数并返回相同的值。它们本质上是异构的,即不属于特定的相似类型。
像我们这样的程序员经常希望指定一个集合只包含某种类型的元素,例如Integer or String 或 Employee。在最初的集合框架中,如果不在代码中添加额外的检查,就不可能实现同质集合。引入泛型就是为了消除这一限制。它们会在编译时自动在代码中添加这种类型的参数检查。这样,我们就不必编写大量不必要的代码,如果编写得当,这些代码在运行时实际上不会增加任何价值。
泛型通过提供实际的类型参数来替代形式化的类型参数,从而实例化形成参数化的类型。例如下面这样:
public class LinkedList<E> ...
LinkedList<String> list = new LinkedList();
- 解释
- 像 LinkedList 这样的类是一种具有类型参数 E 的泛型。
- 像 LinkedList 或 LinkedList 这样的实例被称为参数化类型。
- 字符串和整数是各自的实际类型参数。
通俗地说,泛型强制保证了 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 fine
public T[] notYetInstantiatedArray;
// causes compiler error; Cannot create a generic array of T
public 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 和 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 Integers
List<Integer> ints = Arrays.asList(1,2,3,4,5);
System.out.println(sum(ints));
//List of Doubles
List<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 "关键字的下界通配符来实现这一目的。
在下面的示例中,我创建了三个类,即 SuperClass、ChildClass 和 GrandChildClass。它们之间的关系如下代码所示。现在,我们必须创建一个方法,以某种方式获取 GrandChildClass 的信息(例如,从数据库中获取)并创建一个实例。我们希望将这个新的 GrandChildClass 存储在已经存在的 GrandChildClasses 列表中。
这里的问题是,GrandChildClass 是 ChildClass 和 SuperClass 的子类型。因此,任何 SuperClasses 和 ChildClasses 的通用列表都可以容纳 GrandChildClasses。在这里,我们必须使用 "super "关键字,借助下界通配符。
public class GenericsExample<T>
{
public static void main(String[] args)
{
//List of grand children
List<GrandChildClass> grandChildren = new ArrayList<GrandChildClass>();
grandChildren.add(new GrandChildClass());
addGrandChildren(grandChildren);
//List of grand childs
List<ChildClass> childs = new ArrayList<ChildClass>();
childs.add(new GrandChildClass());
addGrandChildren(childs);
//List of grand supers
List<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 泛型的先写到这里,凡事还是需要多实践!
Java核心之细说泛型的更多相关文章
- java核心卷轴之泛型程序设计
本文根据<Java核心卷轴>第十二章总结而来,更加详细的内容请查看<Java核心卷轴> 1. 泛型类型只能是引用类型,不可以使用基本数据类型. 2. 类型变量含义 E : 集合 ...
- Java核心 --- 枚举
Java核心 --- 枚举 枚举把显示的变量与逻辑的数字绑定在一起在编译的时候,就会发现数据不合法也起到了使程序更加易读,规范代码的作用 一.用普通类的方式实现枚举 新建一个终态类Season,把构造 ...
- Java核心编程快速学习
Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示. 反射reflect是理解Java语言工作原理的基础,Java编译器首先需要将我们编写的 ...
- Java基础学习笔记二十三 Java核心语法之反射
类加载器 类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,链接,初始化三步来实现对这个类进行初始化. 加载就是指将class文件读入内存,并为之创建一个Class对象.任 ...
- Java Collections API和泛型
Java Collections API和泛型 数据结构和算法 学会一门编程语言,你可以写出一些可以工作的代码用计算机来解决一些问题,然而想要优雅而高效的解决问题,就要学习数据结构和算法了.当然对数据 ...
- Java核心编程快速入门
Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示. 反射reflect是理解Java语言工作原理的基础,Java编译器首先需要将我们编写的 ...
- Java核心编程快速学习(转载)
http://www.cnblogs.com/wanliwang01/p/java_core.html Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体 ...
- 2018.6.19 Java核心API与高级编程实践复习总结
Java 核心编程API与高级编程实践 第一章 异常 1.1 异常概述 在程序运行中,经常会出现一些意外情况,这些意外会导致程序出错或者崩溃而影响程序的正常执行,在java语言中,将这些程序意外称为异 ...
- 小白学Java:老师!泛型我懂了!
目录 小白学Java:老师!泛型我懂了! 泛型概述 定义泛型 泛型类的定义 泛型方法的定义 类型变量的限定 原生类型与向后兼容 通配泛型 非受限通配 受限通配 下限通配 泛型的擦除和限制 类型擦除 类 ...
- 剖根问底:Java 不能实现真正泛型的原因是什么?
大家好,我是二哥呀! 今天我来给大家讲一下,Java 不能实现真正泛型的原因是什么? 本文已同步至 GitHub <教妹学 Java>专栏,风趣幽默,通俗易懂,对 Java 初学者亲切友善 ...
随机推荐
- Windows 核心编程笔记 [2] 字符串
1. ANSI 和 Unicode Windows 中涉及字符串的函数有两个版本 1)ANSI版本的函数会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本 2)Unicode版本 ...
- [1] HEVD 学习笔记:HEVD 环境搭建
1. HEVD 概述 + 环境搭建 HEVD作为一个优秀的内核漏洞靶场受到大家的喜欢,这里选择x86的驱动来学习内核漏洞,作为学习笔记记录下来 实验环境 环境 备注 调试主机操作系统 Window ...
- 从零开始配置 vim(11)——插件管理
之前我们介绍了基础配置部分和快捷键配置部分.如果你配置了这两个部分,vim已经算是比较好用了.但是作为代码编辑器来讲还是显的比较简陋,用这些配置来完成日常的编码任务会显得力不从心.vim比较强大的一点 ...
- Gitee一个仓库存储多个项目
需求: 平时会做一些小项目,有时候一个小项目就几行代码,十几K的项目,给这些小项目建一个库保存太奢侈了太浪费了,所以换个思路,根据项目类型来创建库,然后每个小项目以孤立分支的方式存到该库中,这 ...
- .NET NativeAOT 指南
.NET NativeAOT 指南 随着 .NET 8 的发布,一种新的"时尚"应用模型 NativeAOT 开始在各种真实世界的应用中广泛使用. 除了对 NativeAOT 工具 ...
- MyISAM存储引擎的表级锁
MyISAM存储引擎的表级锁 如果了解过文件锁的用法,那理解数据库锁就简单了.锁其实就协调多个进程或线程并发时,处理访问同一个资源的机制.在项目开发中,表锁是MySQL中作用范围较大的一种锁,它锁定的 ...
- 自然语言开发AI应用,利用云雀大模型打造自己的专属AI机器人
如今,大模型层出不穷,这为自然语言处理.计算机视觉.语音识别和其他领域的人工智能任务带来了重大的突破和进展.大模型通常指那些参数量庞大.层数深.拥有巨大的计算能力和数据训练集的模型. 但不能不承认的是 ...
- FDConnection的事务测试讲解。。
总之用事务的宗旨是: 1.不用嵌套事务EnableNested设置为False 2.事务一定要回滚,避免发生异常的情况下,没有回滚 造成,不可估量的错误. try frmClientDm.MyMain ...
- NC16615 [NOIP2008]传纸条
题目链接 题目 题目描述 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接 ...
- STC MCU的软件和硬件PCA/PWM输出
软件方式输出PWM PWM用于输出强度的控制, 例如灯的亮度, 轮子速度等, STC89/90系列没有硬件PWM, 需要使用代码模拟 使用纯循环的方式实现PWM 非中断的实现(SDCC环境编译) #i ...