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<?>>中的通配符
最新文章
- ZooKeeper 笔记(1) 安装部署及hello world
- el表达式无法获取springmvc的model封装好的数据之解决方法
- Seafile内部云盘
- Linq join on 多条件
- Create executable jar
- php生成随机密码(php自定义函数)转自先锋教程网
- Untiy 接入 移动MM 详解
- 【KMP】Oulipo
- 【转】被误解的MVC和被神化的MVVM
- 强烈推荐一款CSS导航菜单
- 使用API创建AR 贷项通知单
- SICP-Elements of program
- spring +springmvc+mybatis组合applicationContext.xml文件配置
- thinkphp5源码解析(1)数据库
- C# - 设计模式 - 钩子模式
- Lodop调整打印项输出顺序 覆盖与层级
- canny 算子python实现
- 每日英语:Doc, Do I Need A Juice Cleanse?
- shell加密工具shc的安装和使用
- Appium简介和初步使用520-1