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<?>>中的通配符

最新文章

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

热门文章

  1. 开源ASR服务器vosk
  2. Java基础系列(8)- 数据类型
  3. css 常用语法
  4. 深入浅出WPF-02.WPF系列目录
  5. java 请求第三方接口 GET\POST 实现方法
  6. react-native移动端设置android闪屏页
  7. Spring源码之AOP的使用
  8. MySQL ENGINES 引擎
  9. FastAPI 学习之路(十四)响应模型
  10. AIbee 笔试