之前提到泛型,更多的时候只是在容器类中用到这个,知道他是参数化类型,除此之外别无其它了,甚至自己写也不一定能写的明白
今天写业务需求刚好需要用到泛型
趁此机会参考一个博客系统性的学习一下泛型相关的知识点
这边以自定义一个简单的通用容器 GenericContainer 为例开始学习
泛型的特性
Java中的泛型,特性是只会在编译期生效
对于定义了参数化类型的泛型类,不一定需要传入参数,例如常见的容器类型,泛型参数只是一个约束限制,如果没有传也可以,但是在编译期间得不到对应类型的结果,具体在 IDEA 中就是会爆黄
1 2 3 4 5 6
| List list = new ArrayList(); Object o = list.get(0);
List<String> paramiterizedList = new ArrayList<>(); String s = paramiterizedList.get(0);
|
从泛型类开始
知道了上面泛型的特性之后我们可以自定义做一个容器,来运用泛型类的特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class GenericContainer<T> {
private T item;
T getItem() { return item; }
void setItem(T item) { this.item = item; }
GenericContainer() { }
GenericContainer(T item) { this.item = item; } }
|
我们在声明这个泛型类的时候,编译阶段就锁死了容器中泛型的类型
1 2 3 4 5 6
| public static void main(String[] args) { GenericContainer<String> strContainer = new GenericContainer<>(); GenericContainer<Integer> intContainer = new GenericContainer<>(); }
|
泛型接口
1 2 3 4 5 6 7 8 9 10 11
| public interface Generator <T>{
T duplicateItem(T item); } public class GeneratorImpl implements Generator{ @Override public Object duplicateItem(Object item) { return null; } }
|
如果没有传递类型作为参数约束,那么类型就没有特定化,只能返回 Object 类型的通用对象
当然更常见的是将参数设置权抛出给上游,在实现类中继续接收类型参数
1 2 3 4 5 6
| public class GeneratorImpl<T> implements Generator<T>{ @Override public T duplicateItem(T item) { return null; } }
|
泛型通配符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void main(String[] args) { GenericContainer<String> strContainer = new GenericContainer<>(); GenericContainer<Integer> intContainer = new GenericContainer<>(); GenericContainer<Number> numberContainer = new GenericContainer<>(); showItem(intContainer); showItem(numberContainer); }
public static void showItem(GenericContainer<?> numberContainer){ Object item = numberContainer.getItem(); System.out.println(item); }
|
我们通过 ? 来标识,这个类型参数我们不知道,但是为了适配兼容具有多态通用性,使用通配符 ?作为类型实参
但是需要注意由于使用了通配符,兼容性很强,可以把?看作是所有参数类型的父类,相当于是参数类型中的Object,因此只能当作 Object 用,可用的方法在编译期间十分有限
可以解决当具体类型不确定的时候,这个通配符就是 ?
当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用 ? 通配符来表未知类型
泛型方法
泛型类和泛型方法差别很大,并且这两个都会更加常见,一开始学习的时候也是把泛型方法和泛型类混淆了,尤其是泛型方法看着很吃力
泛型类,是在实例化类的时候指明泛型的具体类型
泛型方法,是在调用方法的时候指明泛型的具体类型
具体看一下代码就知道了,泛型方法需要在 范围修饰符 和 返回值之间通过一个 <> 来限定作用在这个方法中的所有参数类型
1 2 3 4 5 6 7
| public static <T,E> E changeItem(GenericContainer<T> container){ GenericContainer<E> changedContainer = new GenericContainer<>(); changedContainer.setItem((E)container.getItem()); return changedContainer.getItem(); }
|
需要注意的是泛型方法 和带有通配符的泛型方法之间很容易在初学的时候被混淆
下面这两个都是和泛型确实有关的方法,但是只是确定参数类型的泛型类对象 作为方法参数的 一个普通方法
1 2 3 4 5 6 7 8 9 10
| public static void showItem(GenericContainer<?> trueGenericContainer){ Object item = trueGenericContainer.getItem(); System.out.println(item); } public static void showIntItem(GenericContainer<Integer> intContainer){ Integer item = intContainer.getItem(); System.out.println(item); }
|
但是如果你想要在调用方法的时候,传递不确定的参数,就需要声明泛型方法👆
泛型类中的泛型方法
我们在之前的 GenericContainer 泛型类中新增三个方法,当然第一个不是泛型方法,只是带有泛型类型参数的 在泛型类中存在的 普通方法,他的 T 一定等于 泛型类初始化的时候的 T
1 2 3 4 5 6 7 8 9 10 11 12
| public void showContainerItem(T item){ System.out.println("item is " + item); }
public <E> void showParameterizedItem(E item){ System.out.println("item is " + item); }
public <T> void showAnotherParameterizedItem(T item){ System.out.println("item is " + item); }
|
之后我们测试调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| GenericContainer<Number> numberGenericContainer = new GenericContainer<Number>(); Number numberItem = 0;
numberGenericContainer.showContainerItem(numberItem); Integer intItem = 1;
numberGenericContainer.showContainerItem(intItem); String strItem = "str";
numberGenericContainer.showContainerItem(strItem);
numberGenericContainer.showParameterizedItem(strItem);
numberGenericContainer.showClassSameParameterizedItem(strItem);
|
泛型方法和可变参数
可变参数可以看作是 Object[]
那么我们可以编写一个通用的工具方法打印不同类型数据
1 2 3 4 5 6 7 8 9
| public class GenericUtils { public static <E> void printData(E... data){ for (E e : data) { System.out.println(e); } } } GenericUtils.printData(1,"2",3L,4.0f,5.0);
|
泛型方法和静态
当静态方法想要使用泛型的时候,不可以存在之前那样将泛型类型作为形参,而本身不是泛型方法的情况
我们上面的工具方法就是一个静态的泛型方法, 不可删除
小总结
- 泛型方法可以使得方法独立于类做到动态接收不同类型的变化,在设计一些通用的工具方法和设计模式中十分好用
- 泛型方法定义在泛型类中时,泛型方法自身定义的泛型类型 和 类定义的泛型类型 可以重名会覆盖,但是在设计时应当尽量避免重名减少歧义(T E V 不够你用了吗XD)
- 需要警惕区分 在泛型类中存在的,泛型类型作为函数接收形参(本质上和泛型类初始化强绑定)的普通方法 和正常的泛型方法,自己在定义泛型方法的时候也应当先看看所在类是否已经是泛型类
泛型上下边界
在实际使用的时候,我们往往希望限定传入的类型实参 是某个特定的父类或者子类,这样在对类型实参对象操作的时候能力会更多
- 在通配符(特定不知道的类型实参限定,从Object下更加细化)
1 2 3 4 5
| public static void showNumberItem(GenericContainer<? extends Number> trueGenericNumberContainer){ Number item = trueGenericNumberContainer.getItem(); System.out.println(item.longValue()); }
|
1
| public class GenericContainer<T extends Number> {}
|
1 2 3
| public <E extends Number> void showParameterizedNumberItem(E item){ System.out.println("item is " + item.longValue()); }
|