0、背景
JAVA推出以前,程序员可以构建一个元素类型为Object类型的集合,该集合能够存储任何类型的数据对象,而在使用该集合的过程中需要程序员明确的知道存储的每个元素的元素类型,否则很容易引发ClassCastException
异常。
1、泛型概述
泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型
它的本质是参数化类型,也就是说将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型。
这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
泛型的好处:
类型安全
避免了强制类型转换
注意事项
泛型类,如果没有指定具体的数据类型,此时的操作类型为
Object
泛型类的参数只能是类型,不能是基本数据类型
泛型类在逻辑上可以看成多个不同的类型,但实际上都是相同类型!
泛型类派生子类,子类也是泛型类,那么子类的泛型标识包含父类泛型标识
泛型类泛生子类,如果子类不是泛型类,那么父类要明确数据类型
类型
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(表示Java 类,包括基本的类和我们自定义的类)
- K - Key(表示键,比如HashMap中的key)
- V - Value(表示值)
- N - Number(表示数值类型)
- ? - (表示不确定的java类型)
- S、U、V - 2nd、3rd、4th types
2、泛型格式
Java1.7以后,后面的<>中的具体的数据类型可以省略不写类名<具体的数据类型> 对象名 = new 类名<>(); 菱形语法
2.1 泛型类(Generic)
修饰符 class类名 <类型> { }
注意:
此处T可以随便写为任意表示,常见的如T、E、K、V等形式的参数用于泛型
泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
泛型的类型参数只能是类 类型,不能是基本数据类型
泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
从泛型类派生子类中:
子类也是泛型类,子类和父类的泛型类型要一致class ChildGeneric<T> extends Generic<T>
子类不是泛型类,父类要明确泛型的数据类型class ChildGeneric extends Generic<String>
public class Generic<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
Generic<String> strGeneric = new Generic<>();
Generic<Integer> intGeneric = new Generic<>();
intGeneric.getClass() == strGeneric.getClass(); //True
可以结合后面的泛型擦除理解,泛型擦除之后 Generic < String > 和Generic< Integer >都是Generic类。
2.2 泛型方法:
泛型方法是在调用方法的时候指明泛型的具体类型。 修饰符<T,E,...> 返回值类型 方法名(形参列表){方法体}
在Generic类中创建泛型方法
说明:
只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
< T >表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
public class Generic {
public <T> void show(T t){
System.out.println(t);
}
//测试
public static void main(String[] args) {
Generic1 gen = new Generic1();
gen.show(8);
gen.show("hello world");
}
}
泛型方法与可变参数
可变参数 参数个数可变,用作方法的形参出现 E... e
这里的变量其实是一个数组
如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public class ArgsDemo {
public static void main(String[] args) {
System.out.println(sum(10, 20));
System.out.println(sum(10, 20, 30));
System.out.println(sum(10, 20, 30, 40));
System.out.println(sum(10, 20, 30, 40, 50));
System.out.println(sum2(10, 20));
System.out.println(sum2(10, 20, 30));
System.out.println(sum2(10, 20, 30, 40));
System.out.println(sum2(10, 20, 30, 40, 50));
}
//单个可变参数
public static int sum(int... a) {
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
//多个参数含可变参数
public static int sum2(int a, int... b) {
int flag = 0;
for (int i : b) {
flag += i;
}
int sum = a + flag;
return sum;
}
}
总结:
- 泛型方法能使方法独立于类而产生变化
- 如果static方法要使用泛型能力,就必须使其成为泛型方法
2.3 泛型接口:
修饰符 interface 接口名 <泛型标识,泛型标识,...> { }
实现类也是泛型类,实现类和接口的泛型类型要一致
实现类不是泛型类,接口要明确数据类型
/**
* 泛型接口
* @param <T>
*/
public interface Generator<T> {
T getKey();
}
/**
* 泛型接口的实现类,是一个泛型类,
* 那么要保证实现接口的泛型类泛型标识包含泛型接口的泛型标识
* @param <T>
* @param <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 key;
}
public E getValue() {
return value;
}
}
实现类
/**
* 实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型。
*/
public class Apple implements Generator<String> {
@Override
public String getKey() {
return "hello generic";
}
}
3、类型通配符
类型通配符一般时使用
?
代替具体的参数类型,所以类型通配符是类型实参,而不是类型形参 !
类型通配符的上限类/接口<? extends 实参类型>
,要求该泛型的类型名,只能是该实参类型,或该实参类型的子类型
类型通配符的下限类/接口<? super 实参类型>
要求该泛型的类型名,只能是该实参类型,或该实参类型的父类型
import java.util.ArrayList;
import java.util.List;
public class GenericTest {
public static void main(String[] args) {
//类型通配符 List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
//但是这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
//类型通配符上限
// List<? extends Number> list4 = new ArrayList<object>();
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
//类型通配符下限
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
// List<? super Number> list9 = new ArrayList<Integer>();
}
}
4、类型擦除
泛型是JAVA1.5之后才引入的概念,在这之前是没有泛型的,但是反省代码能够很好的和之前代码兼容。因为泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,这一特性被称之为类型擦除。
- 无限类型擦除
- 有限类型擦除(定义类型上限)
- 擦除方法中类型定义的参数(定义上限)
- 桥接方法
没有写类型上限,默认的擦除类型为Object;方法同理
ArrayList<Integer> integers = new ArrayList<>();
ArrayList<String> strings= new ArrayList<>();
System.out.println(integers.getClass());//得到对象类的模板示例
System.out.println(integers.getClass().getSimpleName());//得到类的简写名称
System.out.println(integers.getClass() == strings.getClass());//True 编译完成之后,类型被擦除,两者类型一致,均为ArrayList
5、泛型与数组
- 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
ArrayList<String>[] listArr = new ArrayList<5>(); //报错
//以下不会报错
ArrayList[] list = new ArrayList[5];
ArrayList<String>[] listArr = list;
或者
ArrayList<String>[] listArr = new ArrayList[5];
- 可以通过
java.lang.reflect.Array的newInstance(Class,int)
创建T[]
数组
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> clz, int length){
//通过Array.newInstance创建泛型数组
array = (T[])Array.newInstance(clz, length);
}
}
6、泛型与反射
反射常用的泛型类
Class
Constructor
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 泛型与反射
*/
public class Test11 {
public static void main(String[] args) throws Exception {
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
}
}
参考:
https://blog.csdn.net/Beyondczn/article/details/107093693