1 泛型概述
1.1 泛型的定义
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,使代码可以应用于多种类型。
public class ClassType<T>{
private T a;
public static void main(String[] args){
ArrayList<T> arr;// T 称为类型参数变量,ArrayList<T> 称为泛型类型
ArrayList<Integer> arr2;// Integer 称为实际类型参数,ArrayList<Integer>参数化的类型
}
}
1.2 泛型的作用
- 简化代码(不用强制类型转换)
- 健壮代码(编译时期没有警告,运行时就不会出现ClassCastExceptin异常)
-
1.3 泛型的核心
1.4 泛型设计原则
只要在编译时期没有出现警告,那么运行时期就不会出现**。**
1.5 泛型的类型擦除机制
1.5.1 类型擦除的概念
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
以上代码的输出结果是:True
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除**。**因此泛型类和普通类在JVM内部没有任何区别。1.5.2 类型擦除的局限性
类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。
泛型类被类型擦除时。如果没有指定上限如<T>
,则会被转译成普通的Object类型。如果指定了上限如<T extends String>
,则类型参数被替换成上限类型。1.5.3 如何绕开类型擦除的局限性
正常情况下,以下代码是无法通过编译的:
public class Class1{
public static void main(String[] args){
List<Integer> ls = new ArrayList<>();
ls.add(23);
ls.add("Test");//这行是无法通过编译的。
}
}
List中关于add的定义如下:
public interface List<E> extends Collection<E>{
boolean add(E e);
}
因为E代表任意类型,因此类型擦除时等同于
**boolean add(Object obj)**
。
需要通过反射机制绕过类型擦除限制:public class ToolTest {
public static void main(String[] args) {
List<Integer> ls = new ArrayList<>();
ls.add(23);
// ls.add("text");
try {
Method method = ls.getClass().getDeclaredMethod("add",Object.class);//这里设置了类型转换方法,将数据类型转换成Object类型,这是Java中对象的最基本类型,可以宽泛的理解为C++中的bit数据。
method.invoke(ls, "test");//这里将"test"反射为object,类型擦除检查就可以绕过,成功正常存入。
method.invoke(ls, 42.9f);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for ( Object o: ls){
System.out.println(o);
}
}
}
```java import java.lang.reflect.Method; import java.util.Arrays; import java.util.Objects;
public class Test
* 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
*/
objectTool.setObj(10);
int i = objectTool.getObj();
System.out.println(i);
try {
Method method = objectTool.getClass().getDeclaredMethod("setObj", Object.class);
method.invoke(objectTool, "拜拜");
}catch (Exception e){
e.printStackTrace();
}
System.out.println(objectTool.getObj());
}
}
运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1750844/1612273040469-4d75ba87-0098-467b-b50e-348cab814c1b.png#align=left&display=inline&height=107&margin=%5Bobject%20Object%5D&name=image.png&originHeight=107&originWidth=326&size=2781&status=done&style=none&width=326)
<a name="1JIhV"></a>
### 1.5.4 泛型的注意事项
- 基本类型不能作为类型参数,使用包装类型
- 同一个类不能实现泛型接口的两种变体。
- 带泛型类型参数的转型或**instanceof**不会有任何效果。`b = (T)a;`
- 不能重载泛型类型
```java
public class ClassA<T, K> {
void f(T t);
void f(K k);
}
这是不允许的。
-
2 泛型的使用方式
2.1 泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。
用户使用时就直接将类型定义,不存在强制转换,运行时类型异常。public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public static void main(String[] args) {
//创建对象并指定元素类型
ObjectTool<String> tool = new ObjectTool<>();
tool.setObj(new String("钟福成"));
String s = tool.getObj();
System.out.println(s);
//创建对象并指定元素类型
ObjectTool<Integer> objectTool = new ObjectTool<>();
/**
* 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
*/
objectTool.setObj(10);
int i = objectTool.getObj();
System.out.println(i);
}
}
这里面可以存在多个泛型
public class ObjectTool<K, V, U, P>{};
2.2 泛型接口
与泛型类相似,只是将泛型定义在接口上。
//定义一个泛型接口 public interface Generator<T>{ public T text(); } //实现这个接口 public class NumGenerator implements Generator<Integer>{ int[] a = {0, 181, 50}; public Integer text{ Random rand = new Random(); return a[rand.nextInt(3)]; } public static void main(String[] args){ NumGenerator num = new NumGenerator(); System.out.println(num.text()); } } //再次实现这个接口 public class StringGenerator implements Generator<String>{ String[] a = {"hello", "hi", "nice to meet"}; public String text{ Random rand = new Random(); return a[rand.nextInt(3)]; } public static void main(String[] args){ StringGenerator dialogue = new StringGenerator(); System.out.println(dialogue.text()); } }
2.3 泛型方法
仅仅在某一个方法上需要使用泛型….外界仅仅是关心该方法,不关心类其他的属性。
泛型类与泛型方法无关,一个类可以是泛型类,而类中的方法也可以是泛型方法,两个没有联系。
原则:如果可以使用泛型方法替代泛型类,那么尽可能使用泛型方法。这样可以使得泛型化更加具体、清楚。2.3.1 泛型方法的使用
public class Class1{ //定义泛型方法.注意该样例与后续样例泛型方法的格式 public <T> void show(T t) { System.out.println(t); } public static void main(String[] args) { //创建对象 Class1 object1 = new Class1(); //调用方法,传入的参数是什么类型,返回值就是什么类型 object1.show("hello"); object1.show(12); object1.show(12.5); } }
2.3.2 泛型方法与可变参数
public class Class1{ public static <T> List<T> makeList(T... args){ List<T> result = new ArrayList<T>(); for(T item : agrs){ result.add(item); } return result; } public static void main(String[] args){ List<String> ls = makeList("A"); System.out.println(ls); ls = makeList("A", "B", "C"); System.out.println(ls); ls = makeList("ABCDERFG".split(""); System.out.println(ls); } }
2.4 泛型数组
不存在泛型数组,因为类型擦除之后无法识别该数组的类型。
List<Integer>[] li2 = new ArrayList<Integer>[]; List<Boolean> li3 = new ArrayList<Boolean>[]; 这两行代码都是无法编译通过的。
3 泛型的上下边界
泛型默认会进行类型擦除,如果不设置边界,会出现擦除为object类型的情况。
3.1 上界通配符:extends
3.2 下界通配符(超类型通配符):super
4 泛型的通配符
4.1 无限定通配符:?
代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。
public class Test2 <T,E extends T>{ T value1; E value2; public <T> void test(T t,Collection<? extends T> collection){ } }
4.2 捕获转换
public class ClassA{ static <T> void f1(Holder<T> holder){ } static void f2(Holder<?> holder){ f1(holder);//未指定类型的通配符捕获转换 } }