Java学习——泛型
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学习——泛型的更多相关文章
- Java学习--泛型
个人理解,所谓的泛型就是将数据类型像参数(称为类型参数或者泛型参数)一样传入类,接口或者方法中,这个类型参数可以当作普通的数据类型,进行变量的声明(成员变量,局部变量(包括方法参数)),指明返回值类型 ...
- Java 学习(17): Java 泛型
Java 泛型 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说将 ...
- Java学习笔记——泛型
假定T不仅要指定接口的类继承.使用下面的方式: public class some<T extends Iterable<T> & Comparable<T>&g ...
- 【Java】泛型学习笔记
参考书籍 <Java核心技术:卷1> 泛型, 先睹为快 先通过一个简单的例子说明下Java中泛型的用法: 泛型的基本形式类似于模板, 通过一个类型参数T, 你可以"私人定制&qu ...
- Java学习点滴——泛型
基于<Java编程思想>第四版 前言 虽然Java的泛型在语法上和C++相比是类似的,但在实现上两者是全然不同的. 语法 Java只需要一个<>就可定义泛型.在<> ...
- Java学习日记基础篇(九) —— 集合框架,泛型,异常
集合框架 有事我们会需要一个能够动态的调整大小的数组,比如说要添加新员工但是数组已经满了,并且数组的大小是在定义的时候定死的,所以我们就需要一个能够动态调整大小的数组或者用链表解决,而java中提供了 ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- 关于JAVA学习计划和感想
学习计划第一阶段: JAVA语言基础知识.包括异常.IO流.多线程.集合类. 要求:异常------掌握try-catch-finally的使用 IO流------掌握字 ...
- 转:Java学习路线图
作者: nuanyangyang 标 题: Java学习路线图(整理中,欢迎纠正) 发信站: 北邮人论坛 (Mon Aug 11 19:28:16 2014), 站内 [以下肯定是不完整的列表, ...
随机推荐
- HTML颜色名称大全
所有浏览器支持的颜色名称,所有现代浏览器都支持以下140种颜色名称(单击颜色名称或十六进制值,以将颜色视为背景颜色以及不同的文本颜色): 有关HTML颜色的完整概述,请访问我们的颜色教程. 颜色名称 ...
- async/await简单使用
function process(i) { var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.l ...
- 微服务(入门学习五):identityServer4+ocelot+consul实现简单客户端模式
简介 主要是采用identity Server4 和ocelot 加上consul 实现简单的客户端模式 开发准备 环境准备 下载并安装Consul具体请参考前几篇的内容 项目介绍 创建ocelot ...
- 如何优雅地停止Spark Streaming Job
由于streaming流程序一旦运行起来,基本上是无休止的状态,除非是特殊情况,否则是不会停的.因为每时每刻都有可能在处理数据,如果要停止也需要确认当前正在处理的数据执行完毕,并且不能再接受新的数据, ...
- mac上安装npm
检查brew -v是否安装了homebrew这个macOS 缺失的软件包的管理器.如果安装,跳转到第3步,否则跳转到第二步: 安装homebrew.安装跳转到官网指导.等待安装好之后,输入brew - ...
- 6.3 使用Spark SQL读写数据库
Spark SQL可以支持Parquet.JSON.Hive等数据源,并且可以通过JDBC连接外部数据源 一.通过JDBC连接数据库 1.准备工作 ubuntu安装mysql教程 在Linux中启动M ...
- Go命令行库Cobra的核心文件root.go
因为docker及Kubernetes都在用cobra库,所以记录一下. 自定义的地方,高红标出. root.go /* Copyright © 2019 NAME HERE <EMAIL AD ...
- 初学JavaScript正则表达式(三)
正则表达式由两种基本的字符类型组成 原义文本字符 //a abc 1 元字符 元字符是有特使含义的非字母字符 * 匹配前面的子表达式零次或多次 + 匹配前面的子表达式一次或多次 ? 匹配前面的子表达式 ...
- Python字符串内置方法使用及年龄游戏深入探究
目录 作业 ==程序代码自上往下运行,建议自上而下的完成下列任务== 作业 使用代码实现以下业务逻辑: 写代码,有如下变量name = " aleX",请按照要求实现每个功能: 移 ...
- [C2] 逻辑回归(Logistic Regression)
逻辑回归(Logistic Regression) 假设函数(Hypothesis Function) \(h_\theta(x)=g(\theta^Tx)=g(z)=\frac{1}{1+e^{-z ...