背景
先来看一个例子:
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("java");
list.add(100);
list.add(true);
for(int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
String str = (String)obj;
System.out.println(str);
}
}
以上程序在编译期间没有问题,而在运行出错,报java.lang.ClassCastException
,提示说Integer类型不能转换为String类型。
添加泛型:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("java");
list.add("python");
list.add("golang");
for(int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println(str);
}
}
声明集合中的类型为String类型,不会报错。
泛型类
定义一个泛型类的例子:
public class Generic<T> {
// key这个成员变量的类型为T,T的类型由外部类调用该类时指定
private T key;
// 泛型构造方法形参key的类型也为T,T的类型由外部指定
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
T:是由外部使用类使用该类对象时指定。
编写上面泛型类的测试方法:
// 只编写了一个泛型类 Generic,就达到了代码复用
Generic<String> gen_str = new Genertic<>("abc");
String k1 = gen_str.getKey();
System.out.println("k1: " + k1);
Generic<Integer> gen_int = new Genertic<>(123);
int k2 = gen_int.getKey();
System.out.println("k2: " + k2);
// 泛型类在创建对象时,没有指定类型,将按照Object类型来操作。
Generic gen_obj = new Genertic(123);
Object k3 = gen_obj.getKey();
System.out.println("k3: " + k3);
// 泛型类不支持基本数据类型,即<>中不能为int、long、float等
// 同一个泛型类,如Generic,根据不同的数据类型创建的对象,本质上是同一类型。
System.out.println(gen_str.getClass()); // 打印Generic的类路径
System.out.println(gen_int.getClass()); // 打印Generic的类路径(和上一句一样)
泛型类实际案例
需求:公司年会上有抽奖品的,也有抽现金的,编写一个抽奖器类完成功能
// 抽奖器类
public class ProGetter<T> {
Random random = new Random();
// 奖品,奖品(String)或奖金(int)
private T product;
// 奖品池
List<T> list = new ArrayList<>();
// 添加奖品到奖品池
public void addProduct(T t) {
list.add(t);
}
// 抽奖
public T getProduct(){
list.get(random.nextInt(list.size()));
return product;
}
}
// 测试类
class TestProduct{
// 创建抽奖器对象,指定数据类型
ProGetter<String> str_pro = new ProGetter<>();
String[] pros = {"iphone 13", "Huawei P50", "Lenovo Y900P", "Beats"};
for(String s : pros) {
str_pro.addProduct(s);
}
// 抽奖
String pro = str_pro.getProduct();
System.out.println("恭喜您,你抽中了: " + pro);
//-------------------------------------------------
ProGetter<Integer> int_pro = new ProGetter<>();
int[] cash = {3000, 5000, 666, 888, 10000};
for(int c : cash) {
int_pro.addProduct(c);
}
// 抽奖
int pro = int_pro.getProduct();
System.out.println("恭喜您,你抽中了: " + pro);
}
泛型类派生子类
泛型接口
看个具体例子:
// 定义一个泛型接口
public interface Generator<T> {
T getKey();
}
// 实现了泛型接口的类,实现类不是泛型类(上面的情况一)
public class Apple implements Generator<String> {
@Override
public String getKey() {
return "hello";
}
}
// 实现类也是泛型类(上面情况二)
// 泛型类的类型还可以扩充,如<T, E, K>,但是必须得包含接口的泛型类型,否则编译报错
public class Pair<T> implements Generator<T> {
private T key;
@Override
public T getKey() {
return key;
}
}
泛型方法
上面泛型类中定义的方法的返回值定义为T类型,在此声明,此方法不是泛型方法,如上面的代码块中定义的Pair泛型类中的getKey成员方法就不是泛型方法。
注意点:
例子:
// 抽奖器类
public class ProGetter<T> {
Random random = new Random();
// 奖品,奖品(String)或奖金(int)
private T product;
// 奖品池
List<T> list = new ArrayList<>();
// 添加奖品到奖品池
public void addProduct(T t) {
list.add(t);
}
// 抽奖,不是泛型方法
public T getProduct(){
list.get(random.nextInt(list.size()));
return product;
}
/**
定义一个泛型方法
<E> 是泛型标识,是具体类型,由调用方法的时候指定。
*/
public <E> getProduct(ArrayList<E> list) {
return list.get(random.nextInt(list.size()));
}
}
// 测试类
public class TestPro {
// 创建抽奖器对象,指定数据类型
ProGetter<Integer> pro = new ProGetter<>();
// 调用泛型类的成员方法,方法的返回值类型必须跟泛型类定义的类型Integer一致
int[] cash = {3000, 5000, 666, 888, 10000};
for(int c : cash) {
pro.addProduct(c);
}
Integer product = pro.getProduct();
System.out.println(product + "\t" + product.getClass().getSimpleName());
//*************************************************************************
ArrayList<String> strList = new ArrayList<>();
list.add("手机");
list.add("服务器");
list.add("显卡");
// 调用泛型方法,类型通过调用方法时指定
String prize = pro.getProduct(strList);
System.out.println(prize + "\t" + prize.getClass().getSimpleName());
}
泛型方法支持可变参数
// 定义一个是可变参数的且是静态的泛型方法
public static <E> void print(E... e) {
for(int i = 0; i < e.length; i++) {
System.out.print(i + "\t");
}
}
// 测试一下:
ProGetter。print(1, 2, 3, 4, 5);
// 静态的泛型方法,采用多个泛型
public static <T, E, K> void printType(T t, E e, K k) {
System.out.println(t + "\t" + t.getClass().getSimpleName());
System.out.println(e + "\t" + e.getClass().getSimpleName());
System.out.println(k + "\t" + k.getClass().getSimpleName());
}
// 测试一下
ProGetter.printType(100, "java", true);
类型通配符
例子:
public class Box<E> {
private E first;
public E getFirst() {
return first;
}
public void setFirst() {
this.first = first;
}
}
// 测试
public class TestBox{
public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
public static void showBox(Box<Integer> box) {
Number first = box.getFirst();
System.out.println(first);
}
// 上面两个方法不叫重载,会报错,只能存在一个,使用泛型通配符解决
// ? 表示可以传递任意类型
public static void showBox(Box<?> box) {
Object first = box.getFirst();
System.out.println(first);
}
}
类型通配符的上限
class Animal{
}
class Cat extends Animal{
}
class MiniCat extends Cat{
}
// 测试
public class TestUp{
public static void showAnimal(ArrayList<? extends Cat> list) {
for(int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);
System.out.println(cat);
}
list.add(new Cat()); // 报错,上线通配符不能添加元素
list.add(new miniCats); // 同样报错
}
public static void main(String[] args){
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(animals); //报错,上限只能是Cat
showAnimal(cats);
showAnimal(miniCats);
}
}
类型通配符的下限
class Animal{
}
class Cat extends Animal{
}
class MiniCat extends Cat{
}
// 测试
public class TestDown{
public static void showAnimal(ArrayList<? extends Cat> list) {
for(int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);
System.out.println(cat);
}
list.add(new Cat()); // 下限通配符添加元素不会报错
list.add(new Animal()); // 不会报错
}
public static void main(String[] args){
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(animals);
showAnimal(cats);
showAnimal(miniCats); //报错,下限只能是Cat
}
}
jdk中下线通配符的使用
类型擦除
java中所有的泛型信息只存在于编译阶段,编译完成后,所有的泛型信息将被擦除掉
无限制类型擦除
泛型无限制类型擦除,编译结束后,把T类型全部替换为Object类型
可以通过反射来得到编译后T类型被擦除后得到的类型:
// 通过反射,拿到Erasure的key的数据类型
class TestErasure{
psvm(){
Erasure<Integer> erasure = new Erasure<>();
// 利用反射,获取Erasure类的字节码文件的Class类对象
Class<? extends Erasure> clz = erasure.getClass();
// 获取所有成员变量
Field[] fields = clz.getDeclaredFields();
for(Field field : fields) {
// 打印成员变量的名称和类型
System.out.println(field.getName() + ":" + field.getType().getSimpleName());
}
}
}
运行结果:
// key:Object
有限制类型擦除
// 通过反射,拿到Erasure的key的数据类型
class TestErasure{
psvm(){
Erasure<Integer> erasure = new Erasure<>();
// 利用反射,获取Erasure类的字节码文件的Class类对象
Class<? extends Erasure> clz = erasure.getClass();
// 获取所有成员变量
Field[] fields = clz.getDeclaredFields();
for(Field field : fields) {
// 打印成员变量的名称和类型
System.out.println(field.getName() + ":" + field.getType().getSimpleName());
}
}
}
运行结果:
// key:Number
擦除泛型方法中类型定义的参数
public class Erasure<T extends Number> {
....;
// 添加泛型方法
public <T extends Number> T show(T t) {
return t;
}
}
class TestErasure{
psvm(){
Erasure<Integer> erasure = new Erasure<>();
// 利用反射,获取Erasure类的字节码文件的Class类对象
Class<? extends Erasure> clz = erasure.getClass();
// 获取所有成员方法
Methods[] methods = clz.getDeclaredMethods();
for(Method method : methods) {
// 打印成员方法的名称和返回值类型
System.out.println(method.getName() + ":" + mathod.getReturnType().getSimpleName());
}
}
}
// 结果:
getKey:Number
show:List
setKey:void
桥接方法
// 编写泛型接口和实现类
// 测试类
psvm{
// 利用反射,获取InfoImpl类的字节码文件的Class类对象
Class<InfoImpl> infoClass = InfoImpl.class;
// 获取所有成员方法
Method[] methods = infoClass.getDeclaredMethods();
//
for(Method method : methods) {
// 打印成员方法的名称和返回值类型
System.out.println(method.getName() + ":" + mathod.getReturnType().getSimpleName());
}
}
// 结果:
info:Integer
info:Object (多了个方法,编译器替我们去生成的)
泛型数组
真正开发中一般都是用泛型集合,很少使用泛型数组。