Java学习——泛型

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

部分内容来自以下博客:

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

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

概述

什么是泛型

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

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

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

为什么要使用泛型

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

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

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

 abc
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类型的数据,就会进行错误提示。

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

泛型的特性

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

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

运行结果如下:

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

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

使用泛型

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

泛型类

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

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

 public class 类名<泛型标识> {
private 泛型标识 成员变量名; ...
}

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

定义泛型类:

 public class Generic<T> {
private T generic; public T getGeneric() {
return generic;
} public void setGeneric(T generic) {
this.generic = generic;
}
}

使用定义的泛型类:

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

泛型接口

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

定义泛型接口:

 public interface Generator<T> {
public T showGeneric();
}

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

 public class Generic<T> implements Generator<T> {
@Override
public T showGeneric() {
return null;
}
}

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

 public class Generic implements Generator<String> {
@Override
public String showGeneric() {
return null;
}
}

泛型方法

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

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

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

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

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

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

定义普通的泛型方法:

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

使用泛型方法:

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

运行结果如下:

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

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

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

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

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

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

使用泛型类中的方法:

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

运行结果如下:

 t getClass >>> class java.lang.Integer

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

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

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

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

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

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

运行结果如下:

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

泛型与可变参数

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

 public <T> void show(T... ts) {
System.out.println("ts getClass >>> " + ts.getClass());
for (T t : ts) {
System.out.println("t getClass >>> " + t.getClass());
}
}

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

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

运行结果如下:

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

泛型和静态方法

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

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

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

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

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

泛型通配符

为什么要使用类型通配符

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

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

 public static void main(String[] args) {
testGenericFunc(new Generic<Number>());
// testGenericFunc(new Generic<Integer>());// 编译报错
} public static void testGenericFunc(Generic<Number> generic) {
System.out.println("testGenericFunc ...");
}

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

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

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

如何使用类型通配符

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

 public static void main(String[] args) {
testGenericFunc(new Generic<Number>());
testGenericFunc(new Generic<Integer>());
} public static void testGenericFunc(Generic<?> generic) {
System.out.println("testGenericFunc ...");
}

设置通配符的上下限

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

 public static void main(String[] args) {
testGenericFunc(new Generic<Number>());
testGenericFunc(new Generic<Integer>());
// testGenericFunc(new Generic<Object>());// 编译报错
} public static void testGenericFunc(Generic<? extends Number> generic) {
System.out.println("testGenericFunc ...");
}

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

 public static void main(String[] args) {
testGenericFunc(new Generic<Number>());
// testGenericFunc(new Generic<Integer>());// 编译报错
testGenericFunc(new Generic<Object>());
} public static void testGenericFunc(Generic<? super Number> generic) {
System.out.println("testGenericFunc ...");
}

泛型类型的数组

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

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

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

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

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

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

 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. NSURLSession的文件下载

    小文件的下载,代码示例: //NSURLSession的下载 [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@ ...

  2. Linux中raid磁盘阵列

    一.磁盘阵列(Redundant Arrays of Independent Disks,RAID) 有“独立磁盘构成的具有冗余能力的阵列”之意. 磁盘阵列是由很多价格较便宜的磁盘,以硬件(RAID卡 ...

  3. Scala开发问题汇总

    1.JDK版本问题 Error:java.lang.VerifyError: Uninitialized Exception Details: Location: scala/collection/i ...

  4. Shell命令-用户用户组管理之passwd、chage

    文件及内容处理 - passwd.chage 1. passwd:修改用户密码 passwd命令的功能说明 passwd命令用来更改使用者的密码 passwd命令的语法格式 passwd [-k] [ ...

  5. C学习笔记(11)--- 可变参数,浅谈内存管理 【C基础概念系列完结】

    1.可变参数(variable arguments): 可变参数允许您定义一个函数,能根据具体的需求接受可变数量的参数. int func(int, ... )             (函数 fun ...

  6. VMWare虚拟机提示:锁定文件失败,打不开磁盘...模块"Disk"启动失败的解决办法

    我出现该问题的原因: 昨天电脑一下子卡死,于是我就重启了电脑,重启之后我没有打开VMware虚拟机,结果第二天一上班打开VMware就发现出现了“锁定文件失败,打不开磁盘......模块"D ...

  7. [C12] 大规模机器学习(Large Scale Machine Learning)

    大规模机器学习(Large Scale Machine Learning) 大型数据集的学习(Learning With Large Datasets) 如果你回顾一下最近5年或10年的机器学习历史. ...

  8. CF613B Skills

    CF613B Skills 洛谷评测传送门 题目描述 Lesha plays the recently published new version of the legendary game hack ...

  9. CF343D Water Tree 树链剖分

    问题描述 LG-CF343D 题解 树剖,线段树维护0-1序列 yzhang:用珂朵莉树维护多好 \(\mathrm{Code}\) #include<bits/stdc++.h> usi ...

  10. vs code 中配置git go

    { "window.zoomLevel": 1, "editor.fontSize": 15, //"files.autoSave": &q ...