【Java基础】6.泛型

《Java基础》目录

例如:第一章 Python 机器学习入门之pandas的使用


文章目录

  • 《Java基础》目录
    • [6. 泛型](#6. 泛型)
      • [6.1 泛型类](#6.1 泛型类)
      • [6.2 泛型方法](#6.2 泛型方法)
      • [6.3 类型变量的限定](#6.3 类型变量的限定)
      • [6.4 泛型代码和虚拟机](#6.4 泛型代码和虚拟机)
        • [6.4.1 类型擦除](#6.4.1 类型擦除)
        • [6.4.2 转换泛型表达式](#6.4.2 转换泛型表达式)
        • [6.4.3 转换泛型方法](#6.4.3 转换泛型方法)
        • [6.4.4 总结](#6.4.4 总结)
      • [6.5 限制与局限性](#6.5 限制与局限性)
        • [6.5.1 不能用基本类型实例化类型参数](#6.5.1 不能用基本类型实例化类型参数)
        • [6.5.2 运行时类型查询只适用于原始类型](#6.5.2 运行时类型查询只适用于原始类型)
        • [6.5.3 不能创建参数化类型的数组](#6.5.3 不能创建参数化类型的数组)
        • [6.5.4 `Varargs`警告](#6.5.4 Varargs警告)
        • [6.5.5 不能实例化类型变量](#6.5.5 不能实例化类型变量)
        • [6.5.6 泛型类的静态上下文中类型变量无效](#6.5.6 泛型类的静态上下文中类型变量无效)
        • [6.5.7 不能抛出或捕获泛型类的实例](#6.5.7 不能抛出或捕获泛型类的实例)
        • [6.5.8 可以取消检查型异常的检查](#6.5.8 可以取消检查型异常的检查)
        • [6.5.9 擦除后的冲突](#6.5.9 擦除后的冲突)
      • [6.6 泛型类型的继承](#6.6 泛型类型的继承)
      • [6.7 通配符类型](#6.7 通配符类型)
        • [6.7.1 通配符概念](#6.7.1 通配符概念)
        • [6.7.2 通配符的超类限定](#6.7.2 通配符的超类限定)
        • [6.7.3 无限定通配符](#6.7.3 无限定通配符)
      • [6.8 反射和泛型](#6.8 反射和泛型)
        • [6.8.1 泛型`Class`类](#6.8.1 泛型Class类)

6. 泛型

泛型可以使代码对不同类型的对象重用。

6.1 泛型类

泛型类就是有一个或多个类型变量的类。该节使用Pair类进行示范,该类使我们只需要关注泛型,而不用为数据存储细节而分心。

java 复制代码
public class Pair<T>
{
    private T first;
    private T second;
    public Pair(T f,T s){first=f;second=s;}
    public T getFirst(){return first;}
}

Pair类引入了类型变量T,用尖括号括起来,放在类名的后面。泛型类可以有多个类型变量,用不同的变量表示。如public class Pair<T,U>

一般情况类型变量用短且大写的字母表示。

  • E:表示集合的元素类型
  • K,V:分别表示键和值的类型
  • T:必要时可以使用相邻的字母U或S表示任意类型

6.2 泛型方法

除了定义泛型类,该可以在普通类中定义带类型参数的泛型方法。

java 复制代码
class ArrayAlg{
    public static <T> T getMiddle(T... a){
        return a[a.length/2];
    }
}

类型变量要放在修饰符的后面,并且放在返回类型的签名。

当进行调用的时候,可以把具体类型放在尖括号中,放在方法名签名:

String s =ArrayAlg.<String>getMiddle("a","b","c")

在这种情况下,可以省略类型参数,编译器可以直接进行类型转换。

String s=ArrayAlg.getMiddle("a","b","c")

这个利用了Java的自动装箱机制,但当同时出现了不同类型的参数的时候,有可能会导致报错,只需要调整一下传递的参数类型即可。

6.3 类型变量的限定

如果定义了一个比较大小的泛型方法,但是如何确定这个具体类型中具有comparaTo的比较方法呢?

只需要在定义泛型方法的时候,对类型变量进行一个限定来实现这一要求。

public static <T extends Comparable> T min(T[] a)

实际上Comparable接口本身就是一个泛型类型,而这里使用的是extend而非implements

这里的extends是因为限定类型更接近于子类型的概念。同时多个限定可以用&来连接

6.4 泛型代码和虚拟机

虚拟机没有泛型类型,只有普通类,所以编译器在编译过程中会进行类型擦除。

6.4.1 类型擦除

无论如何定义一个泛型类型,都会自动提供一个与其对应的普通类,此时类型变量就会被擦除,替替换为其限定类型(若无限定类型则是Object类)。

6.4.2 转换泛型表达式

编写泛型方法时除了进行类型擦除,还有对其进行强制类型转换。

比如

java 复制代码
Pair<Empty> b=...
Empty d=b.getFirst();

其中getFirst进行类型擦除后返回的是Object类型,编译器自动插入转换到Empty的强制类型转换。

访问同一个泛型字段的时候也要进行强制类型转换。

6.4.3 转换泛型方法

类型擦除也会出现在泛型方法中。

例如:

public static <T extends Comparable> T min(T[] a)

进行类型擦除后只剩下一个方法

public static Comparable min(Comparable[] a)

类型擦除会和多态产生冲突

  1. 没有返回值类型
java 复制代码
class DateInterval extends Pair<LocalDate>{
    public void setFirst(LocalDate f){
        if(f.getFirst()>=0)
            super.setFirst(f);
    }
}

类型擦除后会出现两个同名方法:

  • public void setFirst(LocalDate f)
  • public void setFirst(Object f)

为解决这个方法,编译器在DateInterval类中生成一个桥方法

public void setFirst(Object f){setFirst((LocalDate) f)}

  1. 带有返回值类型

虚拟机中会由参数类型和返回值类型共同指定一个方法。所以如果只使用桥方法,在下面的情况就会很奇怪。

java 复制代码
class DateInterval extends Pair<LocalDate>{
    public LocalDate getFirst(){
        return (LocalDate) super.getFirst();
    }
}

但是就会出现两种方法:

  • public LocalDate getFirst()
  • public Object getFirst()

这种情况可以通过对不同的方法生成字节码,虚拟机就可以正确的处理这种情况。

6.4.4 总结

对于Java的类型转换,会遇到一下事实:

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都会替换为他们的限定类型
  • 会合成桥方法在类型擦除下保持多态
  • 为保持类型安全性,必要时会插入强制类型转换

6.5 限制与局限性

Java中泛型类型的限制,大部分都是由于编译器进行类型擦除所引起的。

6.5.1 不能用基本类型实例化类型参数

不能用基本类型替代类型参数,所以没有Pair<double>只有Pair<Double>

原因就是类型擦除后的Pair类只有Object字段,而Object不能存储double值。

6.5.2 运行时类型查询只适用于原始类型

虚拟机中的对象总有一个特定的非泛型类型。因此所有类型查询只产生原始类型。

例如a instanceof Pair<String>实际上测试a instanceof Pair<T>或强制类型转换

为了提示这一风险,在对泛型类型进行查询时,会得到一个编译器错误,或是警告。

6.5.3 不能创建参数化类型的数组

不能实例化参数化类型的数组。table=new Pair[10]

因为执行类型擦除后,就会变成Object[],数组会记住他的数据类型,如果数组试图存储其他类型的元素,就会抛出异常。

可以声明但不可以实例化创建

6.5.4 Varargs警告

向参数可变长方法传递泛型参数。

为了调用这个方法,Java虚拟机创建一个数组来存放参数,就违反了不能实例化类型数组的规则。但不会报错,只会提供警告。

可以采用两种方法来解决。

  1. 为调用的方法增加注解SuppressWarnings("unchecked")
  2. 直接使用SafeVarargs注解(该注解只能用于声明为staticfinalprivate的构造器或方法)
6.5.5 不能实例化类型变量

不能使用诸如new T()的构造方法,因为不会有人希望会new一个Object对象。

java8之后,最好的解决办法就是提供一个构造器表达式,比如

Pair<String> p=Pair.makePair(String::new);

该方法接受一个Supplier<T>,这是一个函数式接口,表示一个无参函数且返回类型为T的函数。

传统的方法是使用Java的反射机制,同时不能使用T.class,需要适当设计API

6.5.6 泛型类的静态上下文中类型变量无效

不能在静态字段或方法中引用类型变量。例如

java 复制代码
public class Singleton<T>{
    private static T singleInstance;
}

进行类型擦除后,只会剩下包含一个singleInstance字段的Singleton类,所以禁止使用带有类型变量的静态字段和方法。

6.5.7 不能抛出或捕获泛型类的实例

既不能抛出也不能捕获泛型类的对象,实际上泛型类可以扩展Throwable甚至都不是合法的。

catch语句中不能使用类型变量,因为会导致不能编译。

但是在异常规范中,使用类型变量是允许的。

不允许:

java 复制代码
public static <T extends Throwable> void do(){
    try{
        do...
    }catch(T e){
        ...
    }
}

允许:

java 复制代码
public static <T extends Throwable> void do(){
    try{
        do...
    }catch(Throwable e){
        ...
    }
}
6.5.8 可以取消检查型异常的检查

Java异常处理的原则就是,必须为所有检查型异常提供一个处理器。不过可以利用泛型取消该机制。

使用该方法

java 复制代码
@SuppressWarnings("unchecked")
static <T extends Throwable> void throws(Throwable t) throws T{
    throw (T) t;
}

通过使用泛型类,擦除和注解,实现欺骗编译器,使其将异常识别为非检查型异常,达到消除Java类型系统的部分基本限制。

6.5.9 擦除后的冲突

当泛型类型被擦除后,不允许创建引发冲突的条件。

为了支持擦除转换,倘若两个接口类型是同一个接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子类。

6.6 泛型类型的继承

泛型类可以继承泛型类,但是泛型类的具体类型没有继承关系。

比如:ManagerEmployee是继承关系,但是Pair<Manager>Pair<Employee>没有继承关系。

同理,ArrayList<T>继承了List<T>,但是ArrayList<Manager>List<Manager>没有继承关系。

6.7 通配符类型

6.7.1 通配符概念

在通配符类型中,允许类型参数发生变化,例如Pair<? extends Employee>表示任何泛型Pair类型,他的我类型参数是Employee的子类。

加入要实现这个方法:

java 复制代码
public static void print(Pair<Employee> p){
    ...
}

8.6 所说,不能将Pair<Manager>传递给这个方法,但是可以使用通配符解决这个问题。如:

java 复制代码
public static void print(Pair<? extends Employee> p){
    ...
}
6.7.2 通配符的超类限定

通配符限定和类型变量限定很相似,但是还有附加的功能,就是可以指定一个超类型限定,如:

? super Manager

可以限制为Manager的所有超类型。

6.7.3 无限定通配符

还可以使用根本无限顶的通配符,例如Pair<?>

带有无限定通配符的可以被任意Object对象调用原始类中的方法。

6.8 反射和泛型

6.8.1 泛型Class

Class类是泛型的,例如String.class实际就是Class<String>类的对象。

Class方法 说明
T newInstance() 返回无参构造器构造的一个新实例
T cast(Object obj) 如果obj为null或有可能转换成类型T,则返回obj;否则抛出异常
T[] getEnumConstants() 如果T是枚举类型,返回所有值组成的数组;否则返回null
Class<? super T> getSuperclass() 返回这个类的超类,如果不是类或Object对象,返回null
Constructor<T> getConstructor(Class... parameterTypes) 获得公共构造器
Constructor<T> getDeclaredConstructor(Class... parameterTypes) 获得有给定参数类型的构造器
TypeVariable[] getTypeParameters() 这个类型被声明为泛型,则获得泛型变量,否则获得长度为0的数组
Type getGenericSuperclass() 获得这个类型所声明的超类的泛型类型;否则返回null
Type[] getGenericInterfaces() 获得这个类所声明的接口的泛型类型;否则返回长度为0的数组
Constructor方法 说明
T newInstance(Object... parameters) 返回用指定参数构造的新实例
Method方法 声明
TypeVariable[] getTypeParameters() 获得泛型类型变量;否则返回长度0的数组
Type getGenericReturnType() 获得这个方法声明的泛型返回类型
Type[] getGenericParameterTypes() 获得声明的泛型参数类型;否则返回长度0数组
TypeVariable方法 声明
String getName() 获得这个类型变量的名字
Type[] getBounds() 获得这个类型变量的子类限定,否则返回长度0数组
WildcardType方法 声明
Type[] getUpperBounds() 获得子类限定;否则返回长度0数组
Type[] getLowerBounds() 获得超类限定;否则返回长度0数组
ParameterizedType方法 声明
Type getRawType() 获得原始类型
Type[] getActualTypeArguments() 获得参数化类型声明的类型参数
Type getOwnerType() 如果是内部类型,返回其外部类型;否则返回null
GenricArrayType方法 声明
Type getGenericComponentType 获得数组类型声明的泛型元素类型
相关推荐
骆晨学长5 分钟前
基于springboot的智慧社区微信小程序
java·数据库·spring boot·后端·微信小程序·小程序
LyaJpunov7 分钟前
C++中move和forword的区别
开发语言·c++
AskHarries10 分钟前
利用反射实现动态代理
java·后端·reflect
@月落11 分钟前
alibaba获得店铺的所有商品 API接口
java·大数据·数据库·人工智能·学习
程序猿练习生12 分钟前
C++速通LeetCode中等第9题-合并区间
开发语言·c++·leetcode
liuyang-neu16 分钟前
力扣 42.接雨水
java·算法·leetcode
z千鑫20 分钟前
【人工智能】如何利用AI轻松将java,c++等代码转换为Python语言?程序员必读
java·c++·人工智能·gpt·agent·ai编程·ai工具
一名路过的小码农22 分钟前
C/C++动态库函数导出 windows
c语言·开发语言·c++
m0_6312704024 分钟前
标准c语言(一)
c语言·开发语言·算法
万河归海42824 分钟前
C语言——二分法搜索数组中特定元素并返回下标
c语言·开发语言·数据结构·经验分享·笔记·算法·visualstudio