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. iOS安全攻防(一):Hack必备的命令与工具

    转自:http://blog.csdn.net/yiyaaixuexi/article/details/8288077 你的应用正在被其他对手反向工程.跟踪和操作!你的应用是否依旧裸奔豪不防御? 郑重 ...

  2. 1. Linux基本命令

    1. Linux 基本操作 1 基本命令 序号 命令 对应英文 作用 1 ls list 查看当前文件夹下的内容 2 pwd print work directory 查看当前所在文件夹 3 Cd [ ...

  3. 6.1 Spark SQL

    一.从shark到Spark SQL Hive能够把SQL程序转换成map-reduce程序   可以把Hadoop中的Hive看作是一个接口,主要起到了转换的功能,并没有实际存储数据. Shark即 ...

  4. 详解Vue生命周期---1

    目录 Vue实例的生命周期全过程(图) 在beforeCreate和created钩子函数间的生命周期 created钩子函数和beforeMount间的生命周期 el选项的有无对生命周期过程的影响 ...

  5. word保存为pdf

    word保存为pdf word保存为pdf word保存为pdf

  6. Kvm命令集管理虚拟机

    KVM虚拟机配置文件位置 [root@localhost ~]# ll /etc/libvirt/qemu/ 总用量 drwxr-xr-x root root 12月 : autostart drwx ...

  7. [C1W2] Neural Networks and Deep Learning - Basics of Neural Network programming

    第二周:神经网络的编程基础(Basics of Neural Network programming) 二分类(Binary Classification) 这周我们将学习神经网络的基础知识,其中需要 ...

  8. jQuery核心(一)

    API文档:http://jquery.cuishifeng.cn/ 一. jQuery.extend(object) 用于扩展jQuery对象本身.用来在jQuery命名空间上增加新函数. jQue ...

  9. 用Python写Verilog(非HLS)

    https://blog.csdn.net/qq_32010099/article/details/81197171 前段时间玩Python的时候好奇, 既然Python这么强大, 那么能不能用Pyt ...

  10. luoguP4774 [NOI2018]屠龙勇士

    题意 考虑杀每只龙\(i\)时候用的剑是一定的,我们可以用multiset模拟一遍得到,设为\(b_i\). 发现我们要求一个\(x\)满足对每个\(i\)有:\(b_i*x\equiv a_i\pm ...