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中的泛型设计的更多相关文章

  1. Java中的泛型 (上) - 基本概念和原理

    本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以 ...

  2. Java开发知识之Java中的泛型

    Java开发知识之Java中的泛型 一丶简介什么是泛型. 泛型就是指泛指任何数据类型. 就是把数据类型用泛型替代了. 这样是可以的. 二丶Java中的泛型 Java中,所有类的父类都是Object类. ...

  3. Java中的泛型 --- Java 编程思想

    前言 ​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...

  4. java中的泛型2--注意的一些问题和面试题

    前言 这里总结一下泛型中需要注意的一些地方和面试题,通过面试题可以让你掌握的更清楚一些. 泛型相关问题 1.泛型类型引用传递问题 在Java中,像下面形式的引用传递是不允许的: ArrayList&l ...

  5. Java中的泛型 - 细节篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的泛型 - 细节篇>,希望对大家有帮助,谢谢 细心的观众朋友们可能发现了,现在的标题不再是入门篇,而是各种详细篇,细节篇: 是因为之 ...

  6. [JavaCore]JAVA中的泛型

    JAVA中的泛型 [更新总结] 泛型就是定义在类里面的一个类型,这个类型在编写类的时候是不确定的,而在初始化对象时,必须确定该类型:这个类型可以在一个在里定义多个:在一旦使用某种类型,在类方法中,那么 ...

  7. Java 中的泛型详解-Java编程思想

    Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限. 2.简单泛型 促成泛型出现最引人注目的一个原因就是为了创造容器类. 首先看一个只能持有单个对象的类,这个类可以明确指定其持有的 ...

  8. 【Java入门提高篇】Day14 Java中的泛型初探

    泛型是一个很有意思也很重要的概念,本篇将简单介绍Java中的泛型特性,主要从以下角度讲解: 1.什么是泛型. 2.如何使用泛型. 3.泛型的好处. 1.什么是泛型? 泛型,字面意思便是参数化类型,平时 ...

  9. Java 中的泛型

    泛型的一般意义: 泛型,又叫 参数多态或者类型参数多态.在强类型的编程语言中普遍作用是:加强编译时的类型安全(类型检查),以及减少类型转换的次数. Java 中的 泛型: 编译时进行 类型擦除 生成与 ...

随机推荐

  1. 对象继承深入、call_apply、圣杯模式、构造函数和闭包,企业模块化

    一个实现加减乘除的插件:   原型其实是在构造函数之上的,构造函数变成实例化函数的时候才会有原型, 原型实际上是构造函数的一个属性 原型无非就是2个字:继承 原型中继承父类所有方法是很不合理的,因为没 ...

  2. Charles的breakpoint功能

    修改请求报文 比如,前端已经控制了输入内容,而我们需要验证接口是否做了校验,这时候怎么测试? 可以通过charles抓包,修改请求报文,修改为在页面上无法输入的内容,发出去然后看后台怎么处理. 修改返 ...

  3. css 样式设定

    阴影: --可以同时设定多个阴影.用逗号隔开 http://www.fly63.com/article/detial/4726 div { box-shadow: 10px 10px 5px #888 ...

  4. Ybt#452-序列合并【期望dp】

    正题 题目链接:https://www.ybtoj.com.cn/contest/113/problem/2 题目大意 一个空序列,每次往末尾加入一个\([1,m]\)中的随机一个数.如果末尾两个数相 ...

  5. CF585E-Present for Vitalik the Philatelist【莫比乌斯反演,狄利克雷前缀和】

    正题 题目链接:https://www.luogu.com.cn/problem/CF585E 题目大意 给出一个大小为\(n\)的可重集\(T\),求有多少个它的非空子集\(S\)和元素\(x\)满 ...

  6. 《HelloGitHub》第 66 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这里有实战项目.入门教程.黑科技.开源书籍.大厂开源项目等,涵盖多种编程语言 Pyt ...

  7. Pycharm软件学生和老师可申请免费专业版激活码

    有一种邮箱,叫做教育邮箱,这东西在这个互联网的世界有很大的优惠及特权,在 Jetbrain 这里, 如果你有教育邮箱(没有教育邮箱怎么办?.edu.cn后缀的邮箱)但很多学生.甚至老师都未必有. 你只 ...

  8. 借助Cookie实现是否第一次登陆/显示上次登陆时间

    Cookie实现是否第一次登陆/显示上次登陆时间 最近刚好看到Cookie这方面知识,对Servlet部分知识已经生疏,重新翻出已经遗弃角落的<JavaWeb开发实战经典>,重新温习了Co ...

  9. 10.13 Nginx 负载均衡

    七层负载均衡server { listen 80; server_name localhost; location / { proxy_pass http://name; //调用集群 } } ups ...

  10. 极简SpringBoot指南-Chapter04-基于SpringBoot的书籍管理Web服务

    仓库地址 w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started ...