【一天一个基础系列】- java之泛型篇
简介
说起各种高级语言,不得不谈泛型,当我们在使用java集合的时候,会发现集合有个缺点:把一个对象“丢进”集合之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,改对象的编译类型就变成了
Object
类型- 问题1:集合对元素类型没有任何限制,这样可能会引发一些问题,比如创建一个用于保存
A
对象的集合,但不小心把B
对象放进去,会引发异常 - 问题2: 由于把对象放进去时,集合对视了对象的状态信息,集合只知道它盛装的是
Object
,因此去取集合元素后通常还需要进行强制类型装换,这个过程不仅增加了编程的复杂度,还可能引发CLassCastException
异常
- 问题1:集合对元素类型没有任何限制,这样可能会引发一些问题,比如创建一个用于保存
为解决以上问题,便引入“泛型”
java 5以后,java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型
java 7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型
- 比如
//java 7之前
List<String> list = new ArrayList<String>();//后面的<String>是必须带上的
//java 7之后,"菱形"语法
List<String> list = new ArrayList<>();注:java 9允许在使用匿名内部类时使用菱形语法
概念定义:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方式动态地指定
我们来看一下定义泛型接口、类
/**
* 定义泛型接口,实质:允许在定义接口、类时什么类型形参,
* 类型形参在整个接口、类体内可当成类型使用,几乎所有可
* 使用普通类型的地方都可以使用这种类型形参
*/
public interface List<T> {
void add(T x);
} /**
* 定义
*
*
*/
@Data
public class Clazz<T> {
private T a;
public Clazz(T a){
this.a = a;
}
} //使用Clazz
pulic void method(){
Clazz<String> clazz = new Clazz<>("");
}
从泛型类派生子类
- 当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含泛型形参
//定义类Son类继承Parent类
public class Son extends Parenet<T>{
} //使用Parent类时为T形参传入String类型
public class Son extends Parent<String>{
} //使用Parent类时,没有为T形参传入实际的类型参数
public class Son extends Parent{
}
像这种使用Parent类时省略泛型的形式被称为原始类型(raw type)
如果从Parent类派生子类,则在Parent类中所有使用T类型的地方都将被替换成String类型并不存在泛型类
List<String>
与List<Integer>
创建出来的是同样class文件,它们在运行时总有同样的类,故在静态方法、静态初始化块或者静态变量的生命和初始化中不允许使用泛型形参
public class R<T>{
//错误,不能在静态变量声明中使用泛型形参
static T info;
//错误,不能再静态方法声明中使用泛型形参
public void foo(T p){
}
}
类型通配符
- 定义:为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:
List<?>
(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型 - 类型通配符的上限
- 定义:当直接使用
List<?>·这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,程序不希望这个
List<?>`是任何泛型List的父类,只希望它代表某一类泛型List的父类
//定义上限为Parent类,表示泛型形参必须是Parent子类
List<? extends Parent>
- 协变:对于更广泛的泛型类来说,指定通配符上限就是为了支持类型型变。比如Foo是Bar的子类,这样
A<Foo>
就相当于A<? extends Bar>
的子类,可以将A<Foo>
赋值给A<? extends Bar>
类型的变量,这种型变方式被称为协变
- 定义:当直接使用
- 类型通配符的下限
- 定义:通配符的下限用
<? super类型>
的方式来指定,通配符下限的作用与通配符上限的作用恰好相反
//定义下限为Parent类
List<? super Parent>
- 逆变:比如Foo是Bar的子类,当程序需要一个
A<? super Foo>
变量时,程序可以将A<Bar>
、A<Object>
赋值给A<? super Foo>
类型的变量,这种型变方式被称为逆变
对于逆变的泛型而言,它只能调用泛型类型作为参数的方法;而不能调用泛型类型作为返回值类型的方法。口诀是:逆变只进不出
- 定义:通配符的下限用
- 定义:为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:
泛型方法
- 定义:所谓泛型方法,就是在声明方法时定义一个或多个泛型形参,与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数
修饰符<T,S>返回值类型 方法名(形参列表){
//TODO
}
- 泛型方法和类型通配符的区别
- 使用通配符比使用泛型方法(在方法签名中显式声明泛型形参)更加清晰和准确
- 类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的泛型形参必须在对应方法中显式声明
- 大多数时候都可以使用泛型方法来代替类型通配符
//使用类型通配符
public interface Collection<E>{
void add(Collection<?> p);
void delete(Collection<? extends E> p)
} //使用泛型方法
public interface Collection<E>{
<T> void add(Collection<T> p);
<T extends E> void delete(Collection<T> p)
}
- 也可以同时使用泛型方法和通配符
public class Collections{
public static <T> void copy(List<T> dest,List<? extends T> src){}
}
- 定义:所谓泛型方法,就是在声明方法时定义一个或多个泛型形参,与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数
“菱形”语法与泛型构造器
- “菱形”语法前面已经提到,不再赘述,说一下啥是泛型构造器,其实就是java允许构造器签名中声明泛型形参
class Foo{
public <T> Foo(T t){
}
} public void method(){
//泛型构造器中T类型为String
new Foo("");
//也可以这么定义,显示指定T类型为String
new<String> Foo("");
//泛型构造器中T类型为Integer
new Foo(10);
}
- “菱形”语法前面已经提到,不再赘述,说一下啥是泛型构造器,其实就是java允许构造器签名中声明泛型形参
泛型方法与方法重载
- 因为泛型既允许设定通配符的上限,也允许设定通配符的下限,从而允许在一个类里包含以下两种方法的定义
<T> void copy(Collection<T> des,Collection<? extends T> src){};
<T> T copy(Collection<? super T> des,Collection<T> src){};
- 重载的情况
public void method(List<String> list){}
public void method(List<Integer> list){}
- 上述这段代码是不能被编译的,因为参数
List<Integer>
和List<String>
编译之后都被擦除了, 变成了同一种的裸类型List
,类型擦除导致这两个方法的特征签名变得一模一样(下面会提到类型擦除)
- 上述这段代码是不能被编译的,因为参数
- 因为泛型既允许设定通配符的上限,也允许设定通配符的下限,从而允许在一个类里包含以下两种方法的定义
类型推断
- java 8改进了泛型方法的类型推断能力,类型推断主要有如下两方面
- 1)可通过调用方法的上下文来推断泛型的目标类型
- 2)可在方法调用链中,将推断得到的泛型传递到最后一个方法
- java 8改进了泛型方法的类型推断能力,类型推断主要有如下两方面
泛型擦除和转换
- 擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉;Java代码编译成Class文件, 然后再用字节码反编译工具进行反编译后, 将会发现泛型都不见了, 程序又变回了Java泛型出现之前的写法, 泛型类型都变回了裸类型(
List<String>
对应的裸类型就是List
)- 比如:
List<String>
类型会被转换成List,则该List对集合元素的类型检查变成了泛型参数的上限(Object),那么在使用,比如插入的时候,又会出现从Object到String的强制转型代码 - 擦除法所谓的擦除, 仅仅是对方法的Code属性中的字节码进行擦除, 实际上元数据中还是保留了泛型信息, 这也是我们在编码时能通过反射手段取得参数化类型的根本依据
- 比如:
- java不支持原生类型的泛型,即是不支持 int/long等,
List<int>
这种是不支持的,那么一旦把泛型信息擦除后,遇到原生类型时把装箱、 拆箱也自动做了,这也成为Java泛型慢的重要原因
- 擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉;Java代码编译成Class文件, 然后再用字节码反编译工具进行反编译后, 将会发现泛型都不见了, 程序又变回了Java泛型出现之前的写法, 泛型类型都变回了裸类型(
泛型与数组
- 数组元素的类型不能包含泛型变量或泛型形参,除非是无上限的类型通配符,但可以声明元素类型包含泛型变量或泛型形参的数组。也就是说,只能声明
List<String>[]
形式的数组,但不能创建ArrayList<String>[10]
这样的数组对象
- 数组元素的类型不能包含泛型变量或泛型形参,除非是无上限的类型通配符,但可以声明元素类型包含泛型变量或泛型形参的数组。也就是说,只能声明
总结:Java的泛型在使用期间需要更加注意泛型擦除的情况,总体而言,其写法也并不优雅。也希望未来的泛型会支持基本类型。
【一天一个基础系列】- java之泛型篇的更多相关文章
- C#基础系列——小话泛型
前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且 ...
- 【转载】C#基础系列——小话泛型
前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且 ...
- Java基础系列 - JAVA集合ArrayList,Vector,HashMap,HashTable等使用
package com.test4; import java.util.*; /** * JAVA集合ArrayList,Vector,HashMap,HashTable等使用 */ public c ...
- 夯实Java基础系列13:深入理解Java中的泛型
目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试 ...
- 夯实Java基础系列9:深入理解Class类和Object类
目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...
- 夯实Java基础系列14:深入理解Java枚举类
目录 初探枚举类 枚举类-语法 枚举类的具体使用 使用枚举类的注意事项 枚举类的实现原理 枚举类实战 实战一无参 实战二有一参 实战三有两参 枚举类总结 枚举 API 总结 参考文章 微信公众号 Ja ...
- 夯实Java基础系列15:Java注解简介和最佳实践
Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...
- 夯实Java基础系列1:Java面向对象三大特性(基础篇)
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...
- 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!
目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...
随机推荐
- 【Oracle】如果有一个Oracle中的用户,想知道他有什么权限,怎么查看?
假如那到了一个新用户,test,想查看这test的所有权限都有哪些,可以这么操作 登录到test用户上,执行下面的SQL 1.查看test用户被赋予了哪些角色. select * from user_ ...
- 1.2V转3V芯片,电路图很少就三个元件
1.2V的镍氢电池由于稳定高,应用产品也是很广,但是由于电压低,需要1.2V转3V芯片,来将1.2V的电压升压转3V,稳定输出供电. 一般性的1.2V转3V芯片,都是用PW5100比较多,固定输出电压 ...
- [已完结]CMU数据库(15-445)实验2-B+树索引实现(下)
4. Index_Iterator实现 这里就是需要实现迭代器的一些操作,比如begin.end.isend等等 下面是对于IndexIterator的构造函数 template <typena ...
- 转 Jmeter测试实践:文件上传接口
Jmeter测试实践:文件上传接口 1.打开jmeter4.0,新建测试计划,添加线程组.根据实际情况配置线程属性. 2.添加HTTP请求. Basic部分修改如下: Advanced部分我做任何 ...
- 如何配置 Slf4j
一,前言 日常开发中经常需要在控制台输出一些信息,如果这些东西不加管理,那么很容易就被输出信息淹没.幸好,我们有日志相关的库来帮助我们格式化控制台的输出. 这篇文章将介绍如何配置 Slf4j 及其具体 ...
- Vue基础之插值表达式的另一种用法!附加变量的监听!
Vue基础之插值表达式的另一种用法!附加变量的监听! 讲这个之前我们先回顾一下原来的用法! <body> <!-- Vue.js的应用可以分为两个重要的组成部分 一个是视图! 另一个 ...
- 一键配置 github 可用的 hosts
最近发现访问 Github 各种不畅通, 静态资源经常加载不出来. 写了一个一键脚本修改本机 /etc/hosts 文件, 切换到可用的 IP (数据来自 https://gitee.com/xuew ...
- 排查golang的性能问题 go pprof 实践
小结: 1.内存消耗分析 list peek 定位到函数 https://mp.weixin.qq.com/s/_LovnIqJYAuDpTm2QmUgrA 使用pprof和go-torch排查 ...
- Go is more about software engineering than programming language research.
https://talks.golang.org/2012/splash.article Go at Google: Language Design in the Service of Softwar ...
- WPF排版布局经验总结(干货)简短不疲倦
本文不过多讲述wpf的基础布局控件,本文只记录WPF排版的技巧,这是个人的总结,不能符合所有情况,如果有何不对的地方,请评论指正,谢谢. 1.区域划分 在接手一个界面的时候,先纵观全局,将眼见的区域划 ...