泛型是什么?

等你使用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 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 "关键字的下界通配符来实现这一目的。

在下面的示例中,我创建了三个类,即 SuperClassChildClassGrandChildClass。它们之间的关系如下代码所示。现在,我们必须创建一个方法,以某种方式获取 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核心之细说泛型的更多相关文章

  1. java核心卷轴之泛型程序设计

    本文根据<Java核心卷轴>第十二章总结而来,更加详细的内容请查看<Java核心卷轴> 1. 泛型类型只能是引用类型,不可以使用基本数据类型. 2. 类型变量含义 E : 集合 ...

  2. Java核心 --- 枚举

    Java核心 --- 枚举 枚举把显示的变量与逻辑的数字绑定在一起在编译的时候,就会发现数据不合法也起到了使程序更加易读,规范代码的作用 一.用普通类的方式实现枚举 新建一个终态类Season,把构造 ...

  3. Java核心编程快速学习

    Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示. 反射reflect是理解Java语言工作原理的基础,Java编译器首先需要将我们编写的 ...

  4. Java基础学习笔记二十三 Java核心语法之反射

    类加载器 类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,链接,初始化三步来实现对这个类进行初始化. 加载就是指将class文件读入内存,并为之创建一个Class对象.任 ...

  5. Java Collections API和泛型

    Java Collections API和泛型 数据结构和算法 学会一门编程语言,你可以写出一些可以工作的代码用计算机来解决一些问题,然而想要优雅而高效的解决问题,就要学习数据结构和算法了.当然对数据 ...

  6. Java核心编程快速入门

    Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示. 反射reflect是理解Java语言工作原理的基础,Java编译器首先需要将我们编写的 ...

  7. Java核心编程快速学习(转载)

    http://www.cnblogs.com/wanliwang01/p/java_core.html Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体 ...

  8. 2018.6.19 Java核心API与高级编程实践复习总结

    Java 核心编程API与高级编程实践 第一章 异常 1.1 异常概述 在程序运行中,经常会出现一些意外情况,这些意外会导致程序出错或者崩溃而影响程序的正常执行,在java语言中,将这些程序意外称为异 ...

  9. 小白学Java:老师!泛型我懂了!

    目录 小白学Java:老师!泛型我懂了! 泛型概述 定义泛型 泛型类的定义 泛型方法的定义 类型变量的限定 原生类型与向后兼容 通配泛型 非受限通配 受限通配 下限通配 泛型的擦除和限制 类型擦除 类 ...

  10. 剖根问底:Java 不能实现真正泛型的原因是什么?

    大家好,我是二哥呀! 今天我来给大家讲一下,Java 不能实现真正泛型的原因是什么? 本文已同步至 GitHub <教妹学 Java>专栏,风趣幽默,通俗易懂,对 Java 初学者亲切友善 ...

随机推荐

  1. Python使用Paramiko实现SSH管理

    paramiko 是一个用于在Python中实现SSHv2协议的库,它支持对远程服务器进行加密的通信.目前该模块支持所有平台架构且自身遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的连接,你 ...

  2. C/C++ 反汇编:数据类型与常量

    反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解.外挂技术.病毒分析.逆向工程.软件汉化等领域,学习和理解反汇编对软件调试.系统漏洞挖掘.内核原理及理解高级语言代码都有相当大的帮助, ...

  3. 多路转接高性能IO服务器|select|poll|epoll|模型详细实现

    前言 那么这里博主先安利一下一些干货满满的专栏啦! Linux专栏https://blog.csdn.net/yu_cblog/category_11786077.html?spm=1001.2014 ...

  4. 零基础入门学习JAVA课堂笔记 ——DAY07

    面向对象(下) 1. Instanceof 我们可以通过Instanceof关键词可以判断当前对象是否为某某类得父类 Object instanceof Student //true 注意:只有是两个 ...

  5. 阿里巴巴 ali1688 Date +0800的问题

    package com.example.testredis.controller; import java.text.DateFormat; import java.text.ParseExcepti ...

  6. PostgreSQL-可以通过localhost连接,无法通过IP地址连接。

    (1)如果PostgreSQL配置文件中没有允许访问该服务器的IP地址,则需要先添加允许访问的IP地址,并在防火墙中开放相应的端口.(2)在PostgreSQL配置文件postgresql.conf中 ...

  7. 文心一言 VS 讯飞星火 VS chatgpt (197)-- 算法导论14.3 5题

    五.用go语言,对区间树 T 和一个区间 i ,请修改有关区间树的过程来支持新的操作 INTERVALSEARCH-EXACTLY(T,i) ,它返回一个指向 T 中结点 x 的指针,使得 x.int ...

  8. typescript 实现enum枚举值定义为对象

    壹 ❀ 引 最近因为有一些闲散时间,所以一直在做将Class组件重构为typescript + hooks组件的工作,结果今天就遇到一个有趣的问题.我们知道react Class组件一般都会定义Com ...

  9. java 注解结合 spring aop 自动输出日志新增拦截器与过滤器

    auto-log auto-log 是一款为 java 设计的自动日志监控框架. 前面已经写过了两篇: java 注解结合 spring aop 实现自动输出日志 java 注解结合 spring a ...

  10. STM32F103C8T6与W5500的运行示例

    模块说明 W5500的厂商是韩国WIZnet, 特性如下 全硬件TCP/IP协议栈: TCP,UDP,ICMP,IPv4,ARP,IGMP,PPPoE -- 注意只有IPv4 支持SPI模式0,3, ...