Java学习——泛型

摘要:本文主要介绍了什么是泛型,为什么要用泛型,以及如何使用泛型。

部分内容来自以下博客:

https://www.cnblogs.com/lwbqqyumidi/p/3837629.html

https://blog.csdn.net/s10461/article/details/53941091

概述

什么是泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

为什么要使用泛型

先来看一个使用集合的例子:

  1. List list = new ArrayList();
  2. list.add("abc");
  3. list.add(100);
  4. for (int i = 0; i < list.size(); i++) {
  5. String item = (String) list.get(i);
  6. System.out.println(item);
  7. }

运行时会报异常,结果如下:

  1. abc
  2. Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

原因是因为List在进行实例化的时候没有指定集合里的元素类型,所以使用默认的Object类型,所以能放下String类型和Integer类型的数据。但是在使用的时候,如果不知道存放了Integer类型的数据,将所有的数据都转换成String类型的数据,就有可能报类型转换失败的异常。

解决办法是,在实例化时指定元素的类型,比如指定类型为String,那么如果存入了Integer类型的数据,在编译期间进行检查发现不是String类型的数据,就会进行错误提示。

  1. List<String> list = new ArrayList<String>();
  2. list.add("abc");
  3. //list.add(100);// 编译报错
  4. for (int i = 0; i < list.size(); i++) {
  5. String item = (String) list.get(i);
  6. System.out.println(item);
  7. }

泛型的特性

还是拿List举例,既然使用泛型规定了List内元素的类型,那么,两个不同泛型的List的类型是不是也不同呢,我们可以验证一下:

  1. List<String> strList = new ArrayList<String>();
  2. List<Integer> intList = new ArrayList<Integer>();
  3. System.out.println("strList getClass() >>> " + strList.getClass());
  4. System.out.println("intList getClass() >>> " + intList.getClass());

运行结果如下:

  1. strList getClass() >>> class java.util.ArrayList
  2. intList getClass() >>> class java.util.ArrayList

运行之后可以发现,虽然指定了不同泛型,但他们的类型都是ArrayList。也就是说Java中的泛型,只在编译阶段有效。

使用泛型

泛型的使用有三种形式:泛型类,泛型接口,泛型方法。

泛型类

泛型类,是在实例化类的时候指明泛型的具体类型。

在使用和定义泛型类时,需要使用泛型标识来代替普通类中的类型。

  1. public class 类名<泛型标识> {
  2. private 泛型标识 成员变量名;
  3.  
  4. ...
  5. }

其中类名和成员变量名都可以任意取值,泛型标识可以使用诸如T、E、K、V等字母作为参数。

定义泛型类:

  1. public class Generic<T> {
  2. private T generic;
  3.  
  4. public T getGeneric() {
  5. return generic;
  6. }
  7.  
  8. public void setGeneric(T generic) {
  9. this.generic = generic;
  10. }
  11. }

使用定义的泛型类:

  1. public static void main(String[] args) {
  2. Generic<String> generic = new Generic<String>();
  3. generic.setGeneric("test");
  4. System.out.println("getGeneric() >>> " + generic.getGeneric());
  5. }

泛型接口

泛型接口与泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中。

定义泛型接口:

  1. public interface Generator<T> {
  2. public T showGeneric();
  3. }

如果一个类实现了泛型接口,但没有指定泛型的类型,那么这个类也需要按照泛型类的方式去定义,即也需要使用泛型标识定义类:

  1. public class Generic<T> implements Generator<T> {
  2. @Override
  3. public T showGeneric() {
  4. return null;
  5. }
  6. }

如果一个类实现了泛型接口,并且指定了泛型的类型,那么这个类中有关泛型的方法都需要换成指定的泛型的类型,并且不需要使用泛型标识定义类:

  1. public class Generic implements Generator<String> {
  2. @Override
  3. public String showGeneric() {
  4. return null;
  5. }
  6. }

泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型。

泛型方法和普通方法的不同之处,就在于泛型方法在访问修饰符和返回类型中间有一个用于声明泛型的泛型标识<泛型标识>,然后在参数列表中就可以是用这个泛型类型的参数了。

  1. public <泛型标识> void show(泛型标识 generic) {
  2. System.out.println("getClass >>> " + generic.getClass());
  3. }

访问修饰符与返回类型中间的<T>非常重要,可以理解为声明此方法为泛型方法。

<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型,如果有多个的话使用逗号分隔。

定义普通的泛型方法:

  1. public <T, K> void show(T t, K k) {
  2. System.out.println("t getClass >>> " + t.getClass());
  3. System.out.println("k getClass >>> " + k.getClass());
  4. }

使用泛型方法:

  1. public static void main(String[] args) {
  2. Generic generic = new Generic();
  3. generic.show(123, "abc");
  4. }

运行结果如下:

  1. t getClass >>> class java.lang.Integer
  2. k getClass >>> class java.lang.String

泛型类中的方法不一定是泛型方法,是不是泛型方法要根据方法的访问修饰符与返回类型中间有没有生命的泛型标识来判断。

如下,在泛型类中的一个普通方法,虽然也用了泛型标识,但是在对类实例化的时候就已经确定了这个泛型的类型,并不是等到调用方法的时候才确定的泛型类型。

在泛型类中定义普通方法:

  1. public class Generic<T> {
  2. public void show(T t) {
  3. System.out.println("t getClass >>> " + t.getClass());
  4. }
  5. }

因为在实例化泛型时就明确了T的类型是Integer类型,所以在调用方法的时候就只能传入Integer类型的数据,否则会报错。

使用泛型类中的方法:

  1. public static void main(String[] args) {
  2. Generic<Integer> generic = new Generic<Integer>();
  3. generic.show(123);
  4. // generic.show("abc");// 编译报错
  5. }

运行结果如下:

  1. t getClass >>> class java.lang.Integer

如果要在泛型类中定义泛型方法,需要在方法的访问修饰符和返回类型中间加入对泛型类型的声明。

方法的泛型类型标识可以和类的泛型类型标识一样,也可以取其他值。

在泛型类中定义泛型方法:

  1. public class Generic<T> {
  2. public <T> void show(T t) {
  3. System.out.println("t getClass >>> " + t.getClass());
  4. }
  5. }

使用泛型类中的泛型方法:

  1. public static void main(String[] args) {
  2. Generic<Integer> generic = new Generic<Integer>();
  3. generic.show(123);
  4. generic.show("abc");
  5. }

运行结果如下:

  1. t getClass >>> class java.lang.Integer
  2. t getClass >>> class java.lang.String

泛型与可变参数

定义可变参数的类型为泛型的泛型方法:

  1. public <T> void show(T... ts) {
  2. System.out.println("ts getClass >>> " + ts.getClass());
  3. for (T t : ts) {
  4. System.out.println("t getClass >>> " + t.getClass());
  5. }
  6. }

使用可变参数的泛型方法:

  1. public static void main(String[] args) {
  2. Generic<Integer> generic = new Generic<Integer>();
  3. generic.show(123, "abc", 'a');
  4. }

运行结果如下:

  1. ts getClass >>> class [Ljava.io.Serializable;
  2. t getClass >>> class java.lang.Integer
  3. t getClass >>> class java.lang.String
  4. t getClass >>> class java.lang.Character

泛型和静态方法

如果一个类似泛型类,那么这个类中的静态方法的参数不能使用泛型,如果要使用的话,需要将静态方法定义为泛型类。

在静态方法中使用泛型参数会在编译期间报错:

  1. public static void show(T t) {
  2. System.out.println("t getClass >>> " + t.getClass());
  3. }

可以将静态方法声明为泛型方法:

  1. public static <T> void show(T t) {
  2. System.out.println("t getClass >>> " + t.getClass());
  3. }

泛型通配符

为什么要使用类型通配符

在上文的学习中,我们知道了泛型类的类型和泛型类型无关,也就是说,Generic<Number>类型的对象和Generic<Integer>类型的对象,他们的类型都是Generic。

既然Number和Integer是父类和子类的关系,那么Generic<Number>和Generic<Integer>有没有父类和子类的关系呢,我们可以做一个测试。

  1. public static void main(String[] args) {
  2. testGenericFunc(new Generic<Number>());
  3. // testGenericFunc(new Generic<Integer>());// 编译报错
  4. }
  5.  
  6. public static void testGenericFunc(Generic<Number> generic) {
  7. System.out.println("testGenericFunc ...");
  8. }

如果在调用方法的时候传入了Generic<Integer>类型的参数,会在编译期间报错,也就是说不能将Generic<Number>看做Generic<Integer>的父类。

那么,如果要想在调用方法的时候,传入任何泛型类型的参数都能使用,就需要使用类型通配符来实现了。

类型通配符一般是使用?代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>、Box<Number>等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

如何使用类型通配符

使用方式很简单, 只需要将方法的参数列表中指定的泛型类型换成?就可以了。

  1. public static void main(String[] args) {
  2. testGenericFunc(new Generic<Number>());
  3. testGenericFunc(new Generic<Integer>());
  4. }
  5.  
  6. public static void testGenericFunc(Generic<?> generic) {
  7. System.out.println("testGenericFunc ...");
  8. }

设置通配符的上下限

如果需要定义一个方法,但对类型实参有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限,具体做法是将<?>换为<? extends Number>。

  1. public static void main(String[] args) {
  2. testGenericFunc(new Generic<Number>());
  3. testGenericFunc(new Generic<Integer>());
  4. // testGenericFunc(new Generic<Object>());// 编译报错
  5. }
  6.  
  7. public static void testGenericFunc(Generic<? extends Number> generic) {
  8. System.out.println("testGenericFunc ...");
  9. }

如果要限制只能是Number类及其父类,就需要设置通配符下限,将<?>换为<? super Number>。

  1. public static void main(String[] args) {
  2. testGenericFunc(new Generic<Number>());
  3. // testGenericFunc(new Generic<Integer>());// 编译报错
  4. testGenericFunc(new Generic<Object>());
  5. }
  6.  
  7. public static void testGenericFunc(Generic<? super Number> generic) {
  8. System.out.println("testGenericFunc ...");
  9. }

泛型类型的数组

在Java中是不能创建一个确切的泛型类型的数组的。

也就是说,下面这个例子是不允许的:

  1. List<String>[] listArr = new List<String>[10];

不过可以使用通配符的方式创建:

  1. List<?>[] listArr = new List<?>[10];

也可以不指定泛型类型创建:

  1. List<String>[] listArr = new List[10];

Java学习——泛型的更多相关文章

  1. Java学习--泛型

    个人理解,所谓的泛型就是将数据类型像参数(称为类型参数或者泛型参数)一样传入类,接口或者方法中,这个类型参数可以当作普通的数据类型,进行变量的声明(成员变量,局部变量(包括方法参数)),指明返回值类型 ...

  2. Java 学习(17): Java 泛型

    Java 泛型 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说将 ...

  3. Java学习笔记——泛型

    假定T不仅要指定接口的类继承.使用下面的方式: public class some<T extends Iterable<T> & Comparable<T>&g ...

  4. 【Java】泛型学习笔记

    参考书籍 <Java核心技术:卷1> 泛型, 先睹为快 先通过一个简单的例子说明下Java中泛型的用法: 泛型的基本形式类似于模板, 通过一个类型参数T, 你可以"私人定制&qu ...

  5. Java学习点滴——泛型

    基于<Java编程思想>第四版 前言 虽然Java的泛型在语法上和C++相比是类似的,但在实现上两者是全然不同的. 语法 Java只需要一个<>就可定义泛型.在<> ...

  6. Java学习日记基础篇(九) —— 集合框架,泛型,异常

    集合框架 有事我们会需要一个能够动态的调整大小的数组,比如说要添加新员工但是数组已经满了,并且数组的大小是在定义的时候定死的,所以我们就需要一个能够动态调整大小的数组或者用链表解决,而java中提供了 ...

  7. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  8. 关于JAVA学习计划和感想

    学习计划第一阶段:    JAVA语言基础知识.包括异常.IO流.多线程.集合类.    要求:异常------掌握try-catch-finally的使用          IO流------掌握字 ...

  9. 转:Java学习路线图

    作者: nuanyangyang 标  题: Java学习路线图(整理中,欢迎纠正) 发信站: 北邮人论坛 (Mon Aug 11 19:28:16 2014), 站内   [以下肯定是不完整的列表, ...

随机推荐

  1. 100款机械CAD图纸,想要出图快,勤练是最有效的方式之一!

    提升CAD出图效率最有效的方式就是勤加练习,所以跟着小匠每天练习3个,30天把这100个常用的CAD机械图纸练完,再看你的出图效率!贵在坚持! 100个机械CAD图纸,请收好

  2. MySQL数据库无法使用+号连接字符串的处理方法

    转自:http://www.maomao365.com/?p=10003 摘要: 下文讲述MySQL数据库,字符串连接的方法分享,如下所示:实现思路: 使用concat函数对两个字符串进行连接在MyS ...

  3. Python创建virtualenv虚拟环境方法

    一.通过virtualenv软件创建 安装:        -pip3 install virtualenv    创建虚拟环境:        -(1)virtualenv wannings-ms- ...

  4. [PHP] vscode配置SFTP扩展同步文件

    在我们的项目开发过程中,经常有一种模式,有一台linux的开发机作为我们的测试机器,上面安装了ftp服务.我们在windows系统的本地机器使用IDE编写代码,自动或者保存时同步上传到测试机下,这样就 ...

  5. [视频教程] 配置vscode的PHP自动补全提示与使用Xdebug进行远程调试debug

    默认下载安装完的vscode并不能准确提示和检测PHP的语法错误,需要手动指定一下本机的PHP程序路径.按下面的操作配置完后就能在文件保存的时候检测语法有无错误.打开文件->首选项->se ...

  6. Forethought Future Cup - Elimination Round C 二分 + 交互(求树的直径)

    https://codeforces.com/contest/1146/problem/C 题意 一颗大小为n的树,每次可以询问两个集合,返回两个集合中的点的最大距离,9次询问之内得出树的直径 题解 ...

  7. MySQL实战45讲学习笔记:第三十一讲

    一.本节概览 今天我要和你讨论的是一个沉重的话题:误删数据. 在前面几篇文章中,我们介绍了 MySQL 的高可用架构.当然,传统的高可用架构是不能预防误删数据的,因为主库的一个 drop table ...

  8. Note | 期刊答复(response)

    第一次TPAMI回复整理的经验. 190926:TPAMI中了,特回来补充. 简洁 尤其对于问答型问题,一定要在前三句话就回答审稿人的问题:yes or no. 不要绕弯子,专注于审稿人问题,解决就行 ...

  9. 第04组 Alpha冲刺(4/6)

    队名:new game 组长博客:戳 作业博客:戳 组员情况 鲍子涵(队长) 燃尽图 过去两天完成了哪些任务 才两天,也就是实现一些功能而已 复习 接下来的计划 实现更多的功能 为这周的比赛准备 还剩 ...

  10. [算法模版]Tarjan爷爷的几种图论算法

    [算法模版]Tarjan爷爷的几种图论算法 前言 Tarjan爷爷发明了很多图论算法,这些图论算法有很多相似之处(其中一个就是我都不会).这里会对这三种算法进行简单介绍. 定义 强连通(strongl ...