java中的泛型设计
1.为什么要使用泛型程序设计
ArrayList<String> files = new ArrayList<>() 等价于 var files = new ArrayList<String>()
2.定义简单泛型类
public class Pair<T>{
private T first;
private T second;
public Pair(){first = null; second = null;}
public Pair(T first, T second){this.first = first; this.second = second;}
public T getFirst(){return first;}
public void setFirst(T newValue){first = newValue;}
泛型类后面可以有多个类型变量 如 public class pair<T, U>{}
3.泛型方法
可以在普通类中定义泛型方法 类型变量放在修饰符后面(public static) 在返回类型的前面(T) 如
class ArrayAlg{
public static <T> T getmiddle(T... a){
return a[a.length / 2];
}
}
当你调用一个泛型方法时 可以把具体类型放在尖括号中 放在方法名前面 如
String middle = ArrayAlg.<String>getmiddle("John", "Q", "Public");
也可以不写具体类型 编译器会自动推断 如
String middle = ArrayAlg.getmiddle("John", "Q", "Public");
4.类型变量的限定
class ArrayAlg{
public static <T> T min(T[] a){
if(a == null || a.length == 0) return null;
T smallest = a[0];
for(int i = 1; i < a.length; i++)
if(smallest.compareTo(a[i]) > 0) smallest = a[i];
return smallest;
}
}
注意 这里的min方法中 smallest的类型为T 而T必须是实现了Comparable接口的类 可以通过对T设置一个限定来实现 如
public static <T extends Comparable> T min(T[] a){}
类型变量的限定格式:<T extends BoundingType>
一个类型变量或通配符可以有多个限定 限定类型用"&"分隔 而逗号用来分隔类型变量 如 T extends Comparable & Serializable
为了提高效率 应将标签接口(即没有方法的接口)放在限定列表的末尾
5.泛型代码与虚拟机(仅需了解原理即可)
虚拟机没有泛型类型对象 所有对象都属于普通类
1.类型擦除:无论何时定义一个泛型类型 都会自动提供一个相应的原始类型 这个原始类型的名字就是去掉类型参数<T>的泛型类型名
类型变量 T 会被擦除 并替换为其限定类型(对于无限定的变量则替换为Object)
原始类型用第一个限定来替换类型变量T 若没有给定限定 就替换为Object
如:public class Interval<T extends Comparable & Serializable> implements Serializable{
private T lower;
}
在虚拟机中类型擦除后为:public class Interval implements Serializable{
private Comparable lower;
}
2.转换泛型表达式:编写一个泛型方法调用时 如果擦除了返回类型 编译器会插入强制类型转换 如下:
对于这个语句序列:
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
getFirst擦除类型后的返回类型为Object 编译器自动插入了转换到Employee的强制类型转换
3.转换泛型方法:类型擦除也会出现在泛型方法中 但是可能引发一些多态的问题 虚拟机是如何解决的呢?
如:class DateInterval extends Pair<LocalDate>{
public void setSecond(LocalDate Second){
if(Second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
}
这个类进行擦除后变为class DateInterval extends Pair{
public void setSecond(LocalDate Second){ ...}
}
然而还有一个从Pair继承来的setSecond方法 即public void setSecond(Object Second)
考虑下面的语句序列:
var interval = new DateInterval( ...);
Pair<LocalDate> pair = interval;
pair.setSecond(aDate);
那么 调用时会调用哪个呢?
实际上 编译器会在类中生成一个桥方法 public void setSecond(Object Second){setSecond((LocalDate) second);}
通过桥方法 会调用setSecond((LocalDate) second 这样就实现了多态
总之 对于java泛型的转换 需要记住以下几个事实:
虚拟机中没有泛型 只有普通的类和方法
所有的类型参数都会体会为它们的限定类型
会合成桥方法来保持多态
为保持类型安全性 必要时会插入强制类型转换
4.调用遗留代码:可用注解 @SuppressWarnings("unchecked") 消除警告 这个注解会关闭对接下来语句的检查
6.限制与局限性:
1.不能用基本类型来实例化类型参数
类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)
如 没有Pair<double> 只能用Pair<Double> 即基本类型使用时需要使用他们的包装类
2.运行时类型查询只适用于原始类型
所有的类型查询都返回原始类型 如 instanceof检查和getClass方法对于Pair<T>的检查都返回Pair
3.不能创建参数化类型的数组
不能实例化参数化类型的数组 如 var table = new Pair<String>[10]是错误的
虽然不允许创建这些数组 但声明类型为Pair<String>[]的变量仍是合法的 不过不能用new Pair<String>[10]初始化这个变量
可以声明通配符类型的数组 然后强转 如 var table = (Pair<String>[]) new Pair<?>[10]; 这样虽然能通过 但是是不安全的 建议不用
如果需要收集参数化类型对象 简单的使用ArrayList:ArrayList<Pair<String>>更安全有效
4.Varargs警告
public static <T> void addAll(Collection<T> coll, T... ts){
for(T t : ts) coll.add(t);
}
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table, pair1, pair2);
为了调用这个方法 java虚拟机必须建立一个Pair<String>数组 但是虚拟机只会给你提供一个警告 而不是错误
可用两种方法来抑制这个警告 一种是为addAll方法增加注解@SuppressWarnings("unchecked") 另一种是用@SafeVarargs直接注解addAll方法
5.不能实例化类型变量
不能在类似new T(...)的表达式中使用类型变量 如 public Pair(){first = new T();}就是非法的
在java8之后 最好的解决办法是让调用者提供一个构造器表达式 如
Pair<String> p = Pair.makePair(String::new);
makePair方法接收一个Supplier<T> 这是一个函数式接口 表示一个无参数而且返回类型为T的函数
public static <T> Pair<T> makePair(Supplier<T> constr){
return new Pair<>(constr.get());
传统方法是通过反射调用Constructor.newInstance方法来构造泛型对象
public static <T> Pair<T> makePair(Class<T> cl){
try{
return new Pair<>(cl.getConstructor().newInstance());
}
catch(Exception e){return null;}
}
这个方法可以如下调用
Pair<String> p = Pair.makePair(String.class)
注意 Class类本身是泛型的 因此 makePair方法能够推断出Pair的类型
6.不能构造泛型数组
直接构造并强制转换会出现异常 最好让用户提供一个数组构造器表达式 如
String[] names = ArrayAlg.minmax(String[]::new, "Tom", "Dick", "Harry");
public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr, T... a){
T[] result = constr.apply(2);
......
}
比较老式的办法是利用反射 并调用Array.newInstance:
public static <T extends Comparable> T[] minmax(T... a){
var result = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
......
}
7.泛型类的静态上下文中类型变量无效
不能再静态字段或静态方法中引用类型变量!
8.不能抛出或捕获泛型类的实例
既不能抛出也不能捕获泛型类的对象 如不能扩展Throwable类(一个泛型类) catch子句中也不能使用类型变量 如
public class Problem<T> extends Exception{...}是不合法的
9.可以取消对检查型异常的检查
可使用以下方法将所有异常都转化为编译器所认为的非检查型异常
@SuppressWarnings("unchecked")
static <T extends Throwable> void throwAs(Throwable t) throws T{
throw(T) t;
}
假设这个方法包含在接口Task中 如果有一个检查型异常e 可使用如下代码
try{
......
}
catch(Throwable t){
Task.<RuntimeException>throwAs(t);
}
10.注意擦除后的冲突
注意不要和擦除后的Object中的方法同名 擦除后会引起冲突
不允许实现对同一接口的不同参数化 可能会引起桥方法的冲突
7.泛型类型的继承规则:
无论S与T有什么关系(如S是T的子类或原始类型等等) Pair<S>与Pair<T>都没有任何关系
8.通配符类型
1.通配符概念
在通配符类型中 允许类型参数发生变化 如 Pair<? extends Employee>表示任何泛型Pair类型 它的类型参数是Employee的子类
如 public static void printBuddies(Pair<Employee> p){} 不能将Pair<Manager>传递给这个方法 不过可以使用一个通配符类型
public static void printBuddies(Pair<? extends Employee> p) 类型Pair<Manager>是Pair<? extends Employee>的子类型
不能调用setFirst方法 因为无法知道具体是什么类型 但是可以调用getFirst方法
2.通配符的超类型限定
? super Manager 这个通配符限制为Manager的所有超类型
与上一个相反 这种可以为方法提供参数 但不能使用返回值 即可以用setFirst 但不能用getFirst
直观的讲 带有超类型限定的通配符允许你写入一个泛型对象 而带有子类型限定的通配符允许你读取一个泛型对象
3.无限定通配符
Pair<?> 此时 getFirst的返回值只能赋给一个Object setFirst方法不能被调用 甚至不能用Object调用
Pair<?>与Pair本质的不同在于:可以用任意Object对象调用原始Pair类的setFirst方法
这种类型可能对一些简单操作非常有用 如测试一个对组是否包含一个null引用 如下
public static boolean hadNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}
4.通配符捕获
public static void swap(Pair<?> p) 注意 不能再编写代码中用?作为一种类型 那怎么实现这个方法呢?
可以写一个辅助方法swapHelper 再被上面的方法调用即可 这就是捕获通配符
public static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
这时可由swap调用swapHelper 如public static void swap(Pair<?> p){swapHelper(p)}
在这种情况下swapHelper中的参数T捕获通配符
注意:编译器必须保证通配符表示单个确定的类型 例如 ArrayList<Pair<T>>中的T永远不能捕获ArrayList<Pair<?>>中的通配符
java中的泛型设计的更多相关文章
- Java中的泛型 (上) - 基本概念和原理
本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以 ...
- Java开发知识之Java中的泛型
Java开发知识之Java中的泛型 一丶简介什么是泛型. 泛型就是指泛指任何数据类型. 就是把数据类型用泛型替代了. 这样是可以的. 二丶Java中的泛型 Java中,所有类的父类都是Object类. ...
- Java中的泛型 --- Java 编程思想
前言 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...
- java中的泛型2--注意的一些问题和面试题
前言 这里总结一下泛型中需要注意的一些地方和面试题,通过面试题可以让你掌握的更清楚一些. 泛型相关问题 1.泛型类型引用传递问题 在Java中,像下面形式的引用传递是不允许的: ArrayList&l ...
- Java中的泛型 - 细节篇
前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的泛型 - 细节篇>,希望对大家有帮助,谢谢 细心的观众朋友们可能发现了,现在的标题不再是入门篇,而是各种详细篇,细节篇: 是因为之 ...
- [JavaCore]JAVA中的泛型
JAVA中的泛型 [更新总结] 泛型就是定义在类里面的一个类型,这个类型在编写类的时候是不确定的,而在初始化对象时,必须确定该类型:这个类型可以在一个在里定义多个:在一旦使用某种类型,在类方法中,那么 ...
- Java 中的泛型详解-Java编程思想
Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限. 2.简单泛型 促成泛型出现最引人注目的一个原因就是为了创造容器类. 首先看一个只能持有单个对象的类,这个类可以明确指定其持有的 ...
- 【Java入门提高篇】Day14 Java中的泛型初探
泛型是一个很有意思也很重要的概念,本篇将简单介绍Java中的泛型特性,主要从以下角度讲解: 1.什么是泛型. 2.如何使用泛型. 3.泛型的好处. 1.什么是泛型? 泛型,字面意思便是参数化类型,平时 ...
- Java 中的泛型
泛型的一般意义: 泛型,又叫 参数多态或者类型参数多态.在强类型的编程语言中普遍作用是:加强编译时的类型安全(类型检查),以及减少类型转换的次数. Java 中的 泛型: 编译时进行 类型擦除 生成与 ...
随机推荐
- php/awk 处理csv 使用 SplFileObject 操作文件
取第5列,去掉开头结尾的引号,匹配以http://, https://, ftp://开头的行 * awk awk -F"," 'str=gsub(/(^\"*)|(\& ...
- NWERC2020J-Joint Excavation【构造,贪心】
正题 题目链接:https://codeforces.com/gym/103049/problem/J 题目大意 \(n\)个点\(m\)条边的一张无向图,选出一条路径后去掉路径上的点,然后将剩下的点 ...
- Python3入门系列之-----列表
列表 列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类型,但最常见的是列表和元组. ...
- MFC获取文件路径和文件夹路径
MFC的界面中,需要实现这样两个功能: 1.在界面上加一个按钮,单击按钮弹出一个对话框选择文件,在工程中获得文件的路径: 2.在界面上加一个按钮,单击按钮弹出一个对话框选择文件夹,在工程中获取文件夹的 ...
- 数据结构与算法——克鲁斯卡尔(Kruskal)算法
目录 应用场景-公交站问题 克鲁斯卡尔算法介绍 克鲁斯卡尔算法图解 克鲁斯卡尔算法分析 如何判断回路? 代码实现 无向图构建 克鲁斯卡尔算法实现 获取一个点的终点解释 应用场景-公交站问题 某城市新增 ...
- 对于caffe程序中出现的Unknown database backend问题的报错怎么办?
在预处理器中添加USE_LMDB,因为caffe需要一种数据输入格式 这样,在db.cpp中#ifdef USE_LMDB就会变亮,显示使用的数据格式为LMDB
- 构建idea父工程
构建idea父工程 首先通过idea新建一个Maven项目: 选择本地Maven版本: 工程名称: 选择字符编码:utf-8 file -> Settings -> Editor -> ...
- Serverless 架构到底要不要服务器?
作者 | aoho 来源 | Serverless 公众号 Serverless 是什么? Serverless 架构是不是就不要服务器了?回答这个问题,我们需要了解下 Serverless 是什么. ...
- bzoj4821 && luogu3707 SDOI2017相关分析(线段树,数学)
题目大意 给定n个元素的数列,每一个元素有x和y两种元素,现在有三种操作: \(1\ L\ R\) 设\(xx\)为\([l,r]\)的元素的\(x_i\)的平均值,\(yy\)同理 求 \(\fra ...
- Eureka使用总结
关于Eureka: 提供基于 REST的服务,在集群中主要用于服务管理.使用该框架,可以将业务组件注册到Eureka容器中,这些组件可进行集群部署,Eureka主要维护这些服务的列表并自动检查他们的状 ...