泛型在设计模式和源码分析中有重要的写法支持,学习设计模式和源码分析必须掌握泛型的使用原理
泛型
概述
什么是泛型?为什么需要泛型?如何使用?是什么原理?如何改进? 这基本上就是我们学习一项技术的正确套路,本文将按照以上顺序展开,由于水平有限,肯定会有不足之处,请多包含和指教。
什么是泛型
泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
为什么需要泛型
Java中引入泛型最主要的目的是将类型检查工作提到编译时期
,将类型强转(cast)工作交给编译器,从而让你在编译时期就获得类型转换异常以及去掉源码中的类型强转代码。
- 没有引入泛型
public class Test {
public static void main(String[] args) {
//没有引入泛型时处理
ArrayList list = CollUtil.newArrayList();
list.add("这是字符串");
list.add(10);
list.stream().forEach(v -> {
if(v instanceof String) {
String s = (String) v;
System.out.println("测试v: " + v);
}else if(v instanceof Integer) {
Integer i = (Integer) v;
System.out.println("测试v:" + i);
}
});
}
}
如上代码所示,在没有泛型之前类型的检查和类型的强转都必须由我们程序员自己负责,一旦我们犯了错(谁还能不犯错?)
- 有了泛型
ArrayList<String> list = CollUtil.newArrayList();
list.add("这是字符串");
list.add(10); //(参数不匹配:int 无法转换为String)
泛型作用对象
泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。
- 泛型类
在类的申明时指定参数,即构成了泛型类,例如下面代码中就指定T为类型参数
,那么在这个类里面就可以使用这个类型了。例如申明T类型的变量name,申明T类型的形参param等操作。
//T 类型参数
public class Generic<T> {
public T name;
public Generic(T name) {
this.name = name;
}
public T getName() {
return this.name;
}
}
public class Test2 {
public static void main(String[] args) {
Generic<String> str = new Generic("这是字符串");
Generic<Integer> i = new Generic(10);
Generic<Boolean> bl = new Generic(true);
//在类的申明时指定参数,即构成了泛型类,例如下面代码中就指定T为类型参数,那么在这个类里面就可以使用这个类型了。例如申明T类型的变量name,申明T类型的形参param等操作。
}
}
- 泛型接口
泛型接口与泛型类的定义基本一致
public interface Generator<T> {
public T produce();
}
- 泛型方法
这个相对来说就比较复杂,当我首次接触时也是一脸懵逼,抓住特点后也就没有那么难了。
//T 类型参数
public class Generic<T> {
public T name;
public Generic(T name) {
this.name = name;
}
public T getName() {
return this.name;
}
// 泛型方法 没有返回值;和构建对象T没有关系
public <E> void m1(E e) {
System.out.println(this.name);
System.out.println(e);
}
// public <T> T m2(T e){ }
// 泛型方法 有返回值 T,这个T和构建对象 T是有关系的,如果构建对象时 T 是字符串,那么泛型方法入参也是字符串
public T m2 (T t) {
return t;
}
// 泛型方法 有返回值,这个T和构建对象 T是没关系的,根据调用方法入参类型决定的
public <E> E m3 (E e) {
return e;
}
}
//test
Generic<String> str = new Generic("这是字符串");
Generic<Integer> i = new Generic(10);
Generic<Boolean> bl = new Generic(true);
//在类的申明时指定参数,即构成了泛型类,例如下面代码中就指定T为类型参数,那么在这个类里面就可以使用这个类型了。例如申明T类型的变量name,申明T类型的形参param等操作。
str.m1("fsd");
String aaa = str.m2("aaa");
System.out.println(aaa);
Integer integer = str.m3(123);
System.out.println(integer);
/*
这是字符串
fsd
aaa
123
*/
- 泛型的使用方法
如何继承一个泛型类
如果不传入具体的类型,则子类也需要指定类型参数
class Son<T> extends Generic<T>{}
如果传入具体参数,则子类不需要指定类型参数
class Son extends Generic<String>{}
- 通配符 使用 ?
泛型类使用
泛型接口使用
- 泛型接口
public interface Generator<T> {
T getKey();
}
- 实现类不是泛型类,需要明确数据类型
/**
* 实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型。
* Apple:不是泛型类,只是普通类
*/
public class Apple implements Generator<String> {
@Override
public String getKey() {
return "helle generic";
}
}
- 实现类也是泛型类,实现类和接口的泛型标识要一致,或者要包含泛型接口的泛型标识
/**
* 定义一个泛型类,去实现泛型接口;在泛型类进行一个扩充,<T, E>
*泛型接口的实现类,是一个泛型类,那么要保证实现接口的泛型类泛型标识,要包含泛型接口的泛型标识
*/
public class Pair<T, E> implements Generator<T> {
private T key;
private E value;
public Pair(T key, E value) {
this.key = key;
this.value = value;
}
@Override
public T getKey() {
return null;
}
public E getValue() {
return value;
}
}
- 测试
public class Test {
public static void main(String[] args) {
Apple apple = new Apple();
String key = apple.getKey();
System.out.println(key);
// key.get
System.out.println("--------------");
Pair<String, Integer> pair = new Pair<>("lg", 100);
String key1 = pair.getKey();
Integer value = pair.getValue();
System.out.println(key1 + "=" + value);
// System.out.println(String.format("%=%", key1, value));
/**
* 总结
* 泛型接口使用:
* 实现类不是泛型类,需要明确数据类型
* 实现类也是泛型类,实现类和接口的泛型类型标识要一致
* */
}
}
- 实现类不是泛型类,需要明确数据类型
- 实现类也是泛型类,实现类和接口的泛型类型标识要一致
泛型方法使用
泛型类,是在实例化类的时候指明泛型的具体类型。
泛型方法,是在调用方法的时候指明泛型的具体类型。
- 语法
修饰符<T,E,...> 返回值类型 方法名(形参列表){
方法体...
}
* public 与返回值中间<T>非常重要, 可以理解为声明此方法为泛型方法。
* 只有声明了<T>的方法才是泛型方法, 泛型类 中的使用成员方法并不是泛型方法。
* <T>表明该方法将使用泛型类型T, 此时才可以在方法体重使用泛型类型T.
* 与泛型类的定义一样, 此处T可以随便写为任意标识, 常见的如T,E,K,V等形式的参数常用于表示泛型。
- 泛型方法与泛型类的成员方法
//泛型类(抽奖的)
public class ProductGetter<T> {
static Random random = new Random();
//奖品
private T product;
//奖品池
ArrayList<T> list = new ArrayList<>();
//添加奖品
public void addProduct(T t) {
list.add(t);
}
//抽奖
//只是泛型类的成员方法,只不过返回值是泛型类的泛型类型; 不是泛型方法
//不能定义成静态方法
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
//泛型方法
//定义一个泛型方法
//可以支持静态方法
/**
* 定义一个泛型方法
* @param list 参数
* @param <E> 泛型标识,具体类型,由调用方法的时候来指定
* */
public <E> E getProduct(ArrayList<E> list) {
return list.get(random.nextInt(list.size()));
}
//此时泛型方法和泛型类的泛型标识没有任何关系
// public <T> T getProduct(ArrayList<T> list) {
// return list.get(random.nextInt(list.size()));
// }
//定义一个静态的泛型方法,采用多个泛型类型
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());
}
//泛型方法与可变参数
public static <E> void print(E... es) {
for (E e : es) {
System.out.println(e);
}
}
}
- 测试
public class Test {
public static void main(String[] args) {
ProductGetter<Integer> productGetter = new ProductGetter<>();
int[] products = {100,200,400};
for (int product : products) {
productGetter.addProduct(product);
}
//泛型类的成员方法的调用
Integer product1 = productGetter.getProduct();
System.out.println(product1);
//
ArrayList<String> strList = new ArrayList<>();
strList.add("电脑");
strList.add("手机");
strList.add("机器人");
//泛型方法的调用,类型是通过调用方法的时候来指定的。
String product = productGetter.getProduct(strList);
System.out.println(product + "\t" + product.getClass().getSimpleName());
System.out.println("-----------------------------");
ArrayList<Integer> intList = new ArrayList<>();
intList.add(200);
intList.add(1000);
intList.add(5000);
Integer product2 = productGetter.getProduct(intList);
System.out.println(product2 + "\t" + product2.getClass().getSimpleName());
/**
* 运行结果:
* 手机 String
-----------------------------
200 Integer
* */
System.out.println("--------------------");
//调用多个泛型类型的静态泛型方法
ProductGetter.printType(100, "java", true);
ProductGetter.printType(false, true, true);
System.out.println("--------------------");
ProductGetter.print(1,2,3,"java", false);
}
}
泛型方法总结
- 泛型方法能使方法独立于类而产生的变化
- 如果static 方法要使用泛型能力, 就必须使其成为泛型方法
泛型通配符
前言
public class Box<E> {//泛型类
private E first;
public E getFirst() {
return first;
}
public void setFirst(E first) {
this.first = first;
}
}
public class Test {
public static void main(String[] args) {
//正常思维
Box<Number> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);
//Integer extends Number : 一般情况:派生子类不能转换成父类
Box<Integer> box2 = new Box<>();
box2.setFirst(10);
showBox(box2);
//Error:(18, 17) java: 不兼容的类型: com.kuang.demo08_fanxing.generic_wildcard.test01.Box<java.lang.Integer>无法转换为com.kuang.demo08_fanxing.generic_wildcard.test01.Box<java.lang.Number>
Box<String> box3 = new Box<>();
box3.setFirst("abc");
// showBox(box3);//error 不属于Number子类
}
// public static void showBox(Box<Number> box) {
// Number first = box.getFirst();
// System.out.println(first);
// }
//不能使用方法重载,因为对于方法参数都是 Box
// public static void showBox(Box<Integer> box) {
// Integer first = box.getFirst();
// System.out.println(first);
// }
//使用泛型的通配符 ? 表示任意类型
// public static void showBox(Box<?> box) {
// Object first = box.getFirst();
// System.out.println(first);
// }
//如果我们想让该方法只能传 最大上限是Number类型;也就是可以传入 Number类型和Number子类;此时可以使用上限通配符
public static void showBox(Box<? extends Number> box) {
Number first = box.getFirst();//传入的类型是Number子类,可以强转
System.out.println(first);
}
}
类型通配符
- 类型通配符语法
上限:
类/接口<? extends 实参类型>
//该泛型的类型,只能实参类型,或者实参类型的子类型
下限:
类/接口<? super 实参类型>
//该泛型的类型,只能实参类型,或者实参类型的父类型
- 代码理解
public class Animal {
}
public class Cat extends Animal {
}
public class MinCat extends Cat {
}
public class Test {
public static void main(String[] args) {
ArrayList<Animal> Animals = new ArrayList<>();
ArrayList<Cat> Cats = new ArrayList<>();
ArrayList<MinCat> MinCats = new ArrayList<>();
// show(Animals);//当使用上限通配符时,Animal不是Cat子类
show(Cats);
// show(MinCats);//当使用下限通配符时,MinCat不是Cat父类
}
/**
*泛型上限通配符,传递的集合类型,只能是Cat类型,或者是Cat类型的子类
* x <= Cat => {Cat MinCat}
* */
// public static void show(List<? extends Cat> list) {
//// list.add(new Animal());//error: 因为list是在一个范围内不确定的类型,没办法添加指定的类型元素;所以一般下限通配符
//// list.add(new Cat());
//// list.add(new MinCat());
// for (Cat cat : list) {
// System.out.println(cat);
// }
// }
/**
*泛型下限通配符,传递的集合类型,只能是Cat类型,或者是Cat类型的子类
* x >= Cat => {Cat Animals}
* */
public static void show(List<? super Cat> list) {
// list.add(new Animal());// 泛型下限通配符可以添加Cat以及Cat子类的元素;因为下限通配符最小是Cat类,所以一定可以添加Cat以下的类。
list.add(new Cat());
list.add(new MinCat());
for (Object o : list) {
System.out.println(o);
}
}
}
- 总结
因为list是在一个范围内不确定的类型,没办法添加指定的类型元素;所以一般下限通配符
泛型下限通配符可以添加Cat以及Cat子类的元素;因为下限通配符最小是Cat类,所以一定可以添加Cat以下的类。
类型擦除
概念
泛型时java1.5版本引进的概念,在这之前是没有泛型的,但是泛型代码能够很好和之前版本的代码兼容。是因为泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除
。
- 无限制类型擦除
[图片上传失败...(image-b51b6c-1639063557293)]
public class Erasure<T> {
private T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
public class Test {
public static void main(String[] args) {
ArrayList<Integer> intList = new ArrayList<>();
ArrayList<String> strList = new ArrayList<>();
// System.out.println(intList.getClass().getSimpleName());
// System.out.println(strList.getClass().getSimpleName());
System.out.println(strList.getClass() == intList.getClass());//true
Erasure<Integer> erasure = new Erasure<>();
//利用反射,获取Erasure类的字节码文件的Class类对象
Class<? extends Erasure> aClass = erasure.getClass();//字节码文件
//获取所有的成员变量
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 打印成员变量的名称和类型
System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
}
//在编译成字节码 变成Object
//key: Object
}
}
- 有限制类型擦除
[图片上传失败...(image-8e265-1639063557294)]
public class Erasure<T extends Number> {
private T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
public class Test {
public static void main(String[] args) {
Erasure<Integer> erasure = new Erasure<>();
//利用反射,获取Erasure类的字节码文件的Class类对象
Class<? extends Erasure> aClass = erasure.getClass();//字节码文件
//获取所有的成员变量
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 打印成员变量的名称和类型
System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
}
//在编译成字节码 变成Number 上限类型
}
}
- 擦除方法中类型定义的参数
[图片上传失败...(image-a13980-1639063557294)]
泛型类型转成Object
泛型上限通配符最后转成上限的类型;泛型下限通配符最后转成Object
//获取所有的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
}
/**
* getKey:Number
setKey:void
show:List
* */
//转成上限类型如果是下限类型转换成object
- 桥接方法
[图片上传失败...(image-2bbbdb-1639063557294)]
Class<InfoImpl> infoClass = InfoImpl.class;
Method[] InfoImplMethods = infoClass.getDeclaredMethods();
for (Method infoImplMethod : InfoImplMethods) {
System.out.println(infoImplMethod.getName() + ":" + infoImplMethod.getReturnType().getSimpleName());
}
/**
* info:Integer
info:Object 桥接方法
* */
泛型与数组
泛型数组的创建
- 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
- 可以通过java.lang.reflect.Array的newInstance(Class<T>, int) 创建T[]数组
反射常用的泛型类
- Class<T>
- Constructor<T>
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) throws Exception {
// Class<Person> personClass = Person.class;
// Constructor<Person> constructor = personClass.getConstructor();
// Person person = constructor.newInstance(); 方便
// Class personClass = Person.class;
// Constructor constructor = personClass.getConstructor();
// Object o = constructor.newInstance(); 不方便
}
}
网友评论