1、泛型-概述
JDK1.5开始引入泛型
1.1、泛型的本质
1、为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。
2、在创建对象或调用方法的时候才明确下具体的类型。
1.2、泛型的意义
1、代码复用:适用于多种数据类型执行相同的代码。
2、泛型-简介
2.1、泛型基本使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
2.1.1、泛型类使用
###多元泛型:指定了两个泛型类型
class Notepad<K,V>{
private K key; // 此变量的类型由外部决定
private V value; // 此变量的类型由外部决定
public K getKey(){
return this.key;
}
public V getValue(){
return this.value;
}
public void setKey(K key){
this.key = key;
}
public void setValue(V value){
this.value = value;
}
}
###测试方法
public class GenericsDemo{
public static void main(String args[]){
//定义两个泛型类型的对象
Notepad<String,Integer> t = new Notepad<String,Integer>();
t.setKey("汤姆");
t.setValue(20);
System.out.print("姓名;" + t.getKey());
System.out.print(",年龄;" + t.getValue());
}
}
2.1.2、泛型接口使用
###泛型接口定义
interface Demo<T>{
T getVar();
}
###泛型接口实现类
public class DemoImpl<T> implements Demo<T>{
private T var ; // 定义属性
public DemoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
###测试类
public class GenericsDemo01{
public static void main(String arsg[]){
//声明接口对象,通过子类实例化对象
Info<String> i = new InfoImpl<String>("汤姆");
System.out.println("内容:" + i.getVar());
}
}
2.1.3、泛型方法使用
1、泛型方法定义语法格式:
public class GenericDemo02<T> {
/**
<T> 声明此方法为泛型方法
T 指明该方法返回值为类型T
Class<T> 指明泛型T的具体类型
c 用来创建泛型T的类的对象
*/
public <T> T getObject(Class<T> c){
//创建泛型对象
T t = c.newInstance();
return t;
}
2、调用泛型方法的语法格式:
GenericDemo02 genericDemo = new GenericDemo02();
//调用泛型方法
Object obj = genericDemo.getObject(Class.forName("xxx.xxx.Demo"));
2.1.4、泛型上下限问题
2.1.4.1、问题抛出Demo
###泛型中隐含的转换问题
class A{}
class B extends A {}
// 如下两个方法不会报错
public static void funA(A a) {
// ...
}
public static void funB(B b) {
funA(b);
// ...
}
// 如下funD方法会报错
public static void funC(List<A> listA) {
// ...
}
public static void funD(List<B> listB) {
funC(listB); // Unresolved compilation problem: The method doPrint(List<A>) in the type test is not applicable for the arguments (List<B>)
// ...
}
==>引入类型参数的上下边界机制<? extends A>表示该类型参数可以是A(上边界)或者A的子类类型。编译时擦除到类型A,即用A类型代替类型参数。
public static void funC(List<? extends A> listA) {
// ...
}
public static void funD(List<B> listB) {
funC(listB); // OK
// ...
}
2.1.4.2、引入泛型上下限
###上限引入
class Info<T extends Number>{ // 此处泛型只能是数字类型
private T var;
public void setVar(T var){
this.var = var;
}
public T getVar(){
return this.var;
}
public String toString(){
return this.var.toString();
}
}
public class TestDemo1{
public static void main(String args[]){
Info<Integer> info = new Info<Integer>();
System.out.println(info.toString())
}
}
###下限引入
class Info<T>{
private T var; // 定义泛型变量
public void setVar(T var){
this.var = var;
}
public T getVar(){
return this.var;
}
public String toString(){
return this.var.toString();
}
}
public class DemoTest2{
public static void main(String args[]){
Info<String> i1 = new Info<String>(); // 声明String的泛型对象
Info<Object> i2 = new Info<Object>(); // 声明Object的泛型对象
i1.setVar("hello") ;
i2.setVar(new Object()) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? super String> temp){ // 只能接收String或Object类型的泛型,String类的父类只有Object类
System.out.print(temp + ", ") ;
}
}
2.1.4.2、泛型上下限-小结
<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类。
2.1.5、多个参数限制泛型
###使用 & 符号
public class Client {
//工资低于2500元的上斑族并且站立的乘客车票打8折
public static <T extends Staff & Passenger> void discount(T t){
if(t.getSalary()<2500 && t.isStanding()){
System.out.println("恭喜你!您的车票打八折!");
}
}
public static void main(String[] args) {
discount(new Me());
}
}
2.1.6、泛型数组
2.1.6.1、泛型数组的声明
List<String>[] list13 = (List<String>[]) new ArrayList<?>[10]; //OK,但是会有警告
List<?>[] list15 = new ArrayList<?>[10]; //OK
List<String>[] list6 = new ArrayList[10]; //OK,但是会有警告
2.2、泛型扩展概念
2.2.1、伪泛型
2.2.1.1、伪泛型-概述
1、伪泛型-概述
1.1、为了兼容jdk1.5之前的版本,Java泛型的实现采取了"伪泛型"的策略:语法上支持泛型,在编译阶段会进行"类型擦除",将所有泛型替换为具体的类型。
2.2.1.2、泛型的类型擦除原则
1、泛型的类型擦除原则
1.1、消除类型参数声明,即删除<>及其包围的部分。
1.2、根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
1.3、自动产生"桥接方法"以保证擦除类型后的代码仍然具有泛型的"多态性"。
2.2.1.3、类型擦除后原始类型指什么?
1、原始类型指:在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
2.2.2、泛型编译器检查
1、Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
2、类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
2.1、举例说明:
public class DemoTest {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList();
list1.add("1"); //编译通过
list1.add(1); //编译错误
String str1 = list1.get(0); //返回类型就是String
ArrayList list2 = new ArrayList<String>();
list2.add("1"); //编译通过
list2.add(1); //编译通过
Object object = list2.get(0); //返回类型就是Object
new ArrayList<String>().add("11"); //编译通过
new ArrayList<String>().add(22); //编译错误
String str2 = new ArrayList<String>().get(0); //返回类型就是String
}
}
2.2.3、泛型的多态(重写的方式)
1、将父类的泛型类型限定为Date,那么父类里面的两个方法的参数都为Date类型;实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object;而子类的两个重写的方法的类型为Date;导致方法名相同,参数类型不一样,进行了重载而不是重写。
1.1、demo示例
###泛型父类
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
###子类继承泛型父类
class DateInteger extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
2、JVM处理泛型多态:桥接方式
2.1、编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。
2.2、虚拟机通过参数类型和返回类型来确定一个方法,交给虚拟器去区别。
2.3、javap -c className的方式反编译下DateInter子类的字节码
2.3.1、javap命令:https://www.yuque.com/moercheng/eig6e7/wvqkek
2.2.3.1、javap -c DateInteger命令结果
public class DateInteger extends Pair<java.util.Date> {
public DateInteger();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>":()V
4: return
public void setValue(java.util.Date); //我们重写的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;
4: areturn
public void setValue(java.lang.Object); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V
8: return
}
2.2.4、基本类型不能作为泛型类型!
1、因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。
2、我们能够使用list.add(1)是因为Java基础类型的自动装箱拆箱操作。
2.2.5、泛型类型不能进行实例化!
1、因为在 Java 编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件。
2、由于T 被擦除为 Object,如果可以 new T() 则就变成了 new Object()。
3、T test = new T(); // ERROR
4、可通过反射实现实例化一个泛型
static <T> T newTclass (Class < T > clazz) throws InstantiationException, IllegalAccessException {
T obj = clazz.newInstance();
return obj;
}
2.2.6、泛型数组不能采用具体泛型类型进行初始化
1、采用通配符的方式初始化泛型数组是允许的,因为对于通配符的方式最后取出数据是要做显式类型转换的。
2、Java 的泛型数组初始化时数组类型不能是具体的泛型类型,只能是通配符的形式,因为具体类型会导致可存入任意类型对象,在取出时会发生类型转换异常。
3、一些demo
List<String>[] list11 = new ArrayList<String>[10]; //编译错误,非法创建
List<String>[] list12 = new ArrayList<?>[10]; //编译错误,需要强转类型
List<String>[] list13 = (List<String>[]) new ArrayList<?>[10]; //OK,但是会有警告
List<?>[] list14 = new ArrayList<String>[10]; //编译错误,非法创建
List<?>[] list15 = new ArrayList<?>[10]; //OK
List<String>[] list6 = new ArrayList[10]; //OK,但是会有警告
2.2.7、泛型类中的静态变量和静态方法
1、泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数,
1.1、示例:
public class TestDemo<T> {
/**
泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型。
*/
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
/**
泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T。
*/
public static <T >T show(T one){ //这是正确的
return null;
}
}
2.2.8、异常中泛型使用
1、泛型类中,不能抛出也不能捕获泛型类的异常
1.1、原因:
1.1.1、泛型类扩展Throwable都不合法;异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。
1.1.2、错误示例:
public class Problem<T> extends Exception {
}
2、不能在catach子句中使用泛型变量
2.1、原因:
2.1.1、因为泛型信息在编译的时候已经变为原始类型
2.1.2、错误示例:
public static <T extends Throwable> void doWork(Class<T> t) {
try {
...
} catch(T e) { //编译错误
...
}
}
3、在异常声明中是可以使用泛型类型变量
3.1、示例
public static<T extends Throwable> void doWork(T t) throws T {
try{
...
} catch(Throwable realCause) {
t.initCause(realCause);
throw t;
}
}
2.2.9、获取泛型参数类型
1、通过反射(java.lang.reflect.Type)获取泛型
1.1、java.lang.reflect.Type是Java中所有类型的公共高级接口, 代表了Java中的所有类型. Type体系中类型的包括:数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class), 以上这些类型都实现Type接口。
1.2、代码示例
public class GenericType<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
GenericType<String> genericType = new GenericType<String>() {};
Type superclass = genericType.getClass().getGenericSuperclass();
//getActualTypeArguments 返回确切的泛型参数, 如Map<String, Integer>返回[String, Integer]
Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
System.out.println(type);//class java.lang.String
}
}