介绍
什么是泛型
泛型,即参数化类型
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在调用时传入具体的类型(类型实参)
为什么要引入泛型
使用泛型进行声明可以让编译器会在编译阶段就能够帮我们发现错误类型对象
1 2 3 4 5 6 final List<Object> list = new ArrayList <>();list.add("string" ); final Student student = (Student)list.get(0 );
泛型擦除
泛型是一种语法糖,在编译阶段就会进行泛型擦除,泛型信息不会进入到运行时阶段(事实上有些泛型信息会被保留,见下文的
API 介绍)
1 2 3 4 5 6 7 8 List<String> stringArrayList = new ArrayList <String>(); List<Integer> integerArrayList = new ArrayList <Integer>(); Class classStringArrayList = stringArrayList.getClass();Class classIntegerArrayList = integerArrayList.getClass();final boolean equals = Objects.equals(classStringArrayList, classIntegerArrayList);
类型
泛型的使用分为三种类型:泛型类、泛型接口、泛型方法
泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口
最典型的就是各种集合类,如:List、Set、Map
简单的泛型类
泛型类
1 2 3 4 5 6 7 8 9 10 11 12 public class GenericsClass <T> { private T key; public GenericsClass (T key) { this .key = key; } public T getKey () { return key; } }
测试
1 2 3 4 5 6 final GenericsClass<String> genericsClass = new GenericsClass <>("张三" );final String key = genericsClass.getKey();
不指明泛型则没有限定
1 2 3 4 5 6 7 final GenericsClass genericsClass1 = new GenericsClass <>("张三" );final GenericsClass genericsClass2 = new GenericsClass <>(123 );final Object key1 = genericsClass1.getKey();final Object key2 = genericsClass2.getKey();
泛型不能使用 instanceof 操作
编译错误 Expression expected
1 2 3 4 5 6 7 public T getKey () { if (T instanceof String) { return key; } return key; }
泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
简单的泛型接口
接口
1 2 3 public interface GenericsIntf <T> { public T get () ; }
实现类,未声明泛型实参,依赖创建对象时声明的泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class GenericsIntfImpl <T> implements GenericsIntf <T> { private final T t; public GenericsIntfImpl (final T t) { this .t = t; } @Override public T get () { return t; } }
测试
1 2 final GenericsIntfImpl<String> impl = new GenericsIntfImpl <String>("张三" );final String s = impl.get();
也可以在实现类中向泛型接口声明固定的泛型实参
1 2 3 4 5 6 7 8 9 10 11 12 13 public class GenericsIntfImpl2 implements GenericsIntf <String> { @Override public String get () { return "Hello World" ; } }
测试
1 2 3 final GenericsIntfImpl2 impl2 = new GenericsIntfImpl2 ();final String s = impl2.get();Assert.assertEquals(s, "Hello World" );
泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型
而泛型方法,是在调用方法的时候指明泛型的具体类型
简单使用
泛型方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class GenericsMethod { public <T> T genericsMethod (final Class<T> tClass) throws InstantiationException, IllegalAccessException { return tClass.newInstance(); } }
测试
1 2 3 4 5 final GenericsMethod genericsMethod = new GenericsMethod ();final Student student = genericsMethod.genericsMethod(Student.class);final Dog dog = genericsMethod.genericsMethod(Dog.class);
泛型方法的特征很容易混淆,定义一个复杂些的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class GenericsComplex <T> { private final T key; public GenericsComplex (final T key) { this .key = key; } public T getKey () { return key; } public <T> T getKeyName (final GenericsClass<T> generics) { System.out.println("container key :" + generics.getKey()); final T test = generics.getKey(); return test; } public void get (final GenericsClass<Number> obj) { System.out.println(obj.getKey()); } }
类中泛型方法
定义一个泛型类,其中包含普通方法使用泛型类声明的泛型 和泛型方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class GenericsMethod2 <T> { public void show1 (final T t) { System.out.println(t.toString()); } public <E> void show2 (final E t) { System.out.println(t.toString()); } public <T> void show3 (final T t) { System.out.println(t.toString()); } }
测试
1 2 3 4 5 6 7 8 9 10 11 final GenericsMethod2<String> method2 = new GenericsMethod2 <>();method2.show1("Hello World" ); method2.show2(new Dog ("旺财" , 3 )); method2.show3(new Student ("李四" , 20 ));
可变参数
泛型方法可以使用在可变参数上
定义方法
1 2 3 4 5 private <T> void printMsg (final T... args) { for (final T t : args) { System.out.print(t + " " ); } }
测试
1 2 this .printMsg("张三" , 200 , 4L , "李四" );
静态方法
静态方法无法直接使用定义在类上的泛型,因为泛型在对象实例化时传入,静态方法无法获取类上的泛型信息
所以如果静态方法想使用泛型,就必须定义成泛型方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class StaticGenericsMethod <T extends Animal > { public static <T extends Animal > void staticShow (final T obj) { System.out.println("static" ); System.out.println(obj.toString()); } public void show (final T obj) { System.out.println("param" ); System.out.println(obj.toString()); } }
调用
1 2 3 4 5 6 7 final Dog dog = new Dog ("旺财" , 5 );StaticGenericsMethod.staticShow(dog); final StaticGenericsMethod<Dog> method = new StaticGenericsMethod <>();method.show(dog);
需要注意,静态泛型方法会导致类型转换异常
正常的类型转换,都会在编译阶段提示错误
Inconvertible types; cannot cast 'java.lang.Integer' to 'java.lang.String'
1 2 3 final Integer integer = new Integer (10 );final String s = (String)integer;
构造一个静态泛型方法
1 2 3 private static <T> T convert (final Object obj) { return (T)obj; }
调用方法后再进行转换,则只会在运行时抛出
java.lang.ClassCastException
1 2 3 final Integer integer = new Integer (10 );final Object convert = convert(integer);final String s = (String)convert;
原因是因为 Java
的泛型方法属于伪泛型,在编译的时候将进行类型擦除
普通的泛型方法在类构建时已经明确制定了对应的类型,而在静态泛型方法中,类型是无法直接推测的,缺少了明确的类型,最终造成类型转化异常
编译后的 convert 方法
1 2 3 private static <T extends String > T convert (final Object obj) { return (T)obj; }
特点
泛型通配符
子类和父类的泛型不能被认为是父类的同一种泛型
定义一个方法,参数要求 GenericsClass 对象且泛型为 Number
1 2 3 private void get (final GenericsClass<Number> num) { System.out.println(num.getKey()); }
创建一个泛型为 Integer 类型的对象,则无法调用该方法
1 2 3 4 final GenericsClass<Integer> genericsClass = new GenericsClass (10 );this .get(genericsClass);
虽然 Integer
是 Number
的子类,但明显
GenericsClass <Integer>
不能被看作
GenericsClass<Number>
同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的
可以使用泛型通配符 ?
及 extends
和
super
解决此类问题
注意:?
是实参,并不是形参,它代表所有 Object
类
修改上面的代码,GenericsClass<? extends Number>
表示对象的泛型可以是继承 Number
的任何类型
1 2 3 private void get (final GenericsClass<? extends Number> num) { System.out.println(num.getKey()); }
extends
和 super
区别:
extends
规定了上界,即规定了泛型实参的最底层父类
super
规定了下界,即规定了泛型实参最上层的子类(实现类)
Java API
ParameterizedType
当泛型为类上泛型时,通过 Class 对象的
getGenericSuperclass
方法将会获取到
ParameterizedType
的 Type
接口对象,即为类上定义的泛型信息
可以使用 getActualTypeArguments
获取真实的泛型类型参数
定义泛型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class GenericClass <T> { private T obj; public void save (T obj) { this .obj = obj; } public T get () { return obj; } public void showGenericInfo () { Type genericSuperclass = this .getClass().getGenericSuperclass(); System.out.println(genericSuperclass.getClass()); Type type = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0 ]; System.out.println(type); } }
实现类
1 2 3 4 5 6 public class Run extends GenericClass <Student> { public static void main (String[] args) { Run run = new Run (); run.showGenericInfo(); } }
1 2 class sun .reflect.generics.reflectiveObjects.ParameterizedTypeImpljava.util.List<generic.Student>
从 getGenericSuperclass
获取到的对象是
ParameterizedTypeImpl
,即 ParameterizedType
的实现类
从中还可以获取到真实的泛型信息 Student
如果是一个嵌套的泛型结构呢
修改下 showGenericInfo
方法
1 2 3 4 5 6 7 8 9 10 11 12 public void showGenericInfo () { Type genericSuperclass = this .getClass().getGenericSuperclass(); System.out.println(genericSuperclass.getClass()); System.out.println("--------------------------" ); while (genericSuperclass instanceof ParameterizedType) { Type actualTypeArgument = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0 ]; System.out.println(actualTypeArgument); genericSuperclass = actualTypeArgument; } }
1 2 3 4 5 6 public class Run extends GenericClass <List<Student>> { public static void main (String[] args) { Run run = new Run (); run.showGenericInfo(); } }
1 2 3 4 class sun .reflect.generics.reflectiveObjects.ParameterizedTypeImpl-------------------------- java.util.List<generic.Student> class generic .Student
可以看出泛型 List<Student>
,第一次获取到的
ParameterizedType
是 List
信息,它依然是一个
ParameterizedType
对象,而后还可以获取到下面定义的
Student
getGenericInterfaces
泛型接口同理,不过需要通过 getGenericInterfaces
方法进行获取
public Type[] getGenericInterfaces()
因为接口是一对多,所以这里获取到的是一个数组,也是
ParameterizedType
定义泛型接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface GenericInterface <T> { default void showGenericInfo () { Type genericSuperclass = this .getClass().getGenericInterfaces()[0 ]; System.out.println(genericSuperclass.getClass()); System.out.println("--------------------------" ); while (genericSuperclass instanceof ParameterizedType) { Type actualTypeArgument = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0 ]; System.out.println(actualTypeArgument); genericSuperclass = actualTypeArgument; } } }
实现
1 2 3 4 5 6 public class Run implements GenericInterface <List<Student>> { public static void main (String[] args) { Run run = new Run (); run.showGenericInfo(); } }
1 2 3 4 class sun .reflect.generics.reflectiveObjects.ParameterizedTypeImpl-------------------------- java.util.List<generic.Student> class generic .Student
TypeVariable
当使用泛型方法时,其参数或者返回值拿到的泛型信息为
TypeVariable
这时只能拿到泛型定义时的边界信息,如果没有边界信息则会擦除为
Object
定义泛型方法
1 2 3 4 5 6 7 8 9 10 11 12 public class GenericMethod { @SneakyThrows public <T> void method (T obj) { Method method = this .getClass().getMethods()[0 ]; Type genericParameterType = method.getGenericParameterTypes()[0 ]; TypeVariable typeVariable = (TypeVariable)genericParameterType; System.out.println(typeVariable.getName()); System.out.println(Arrays.toString(typeVariable.getBounds())); } }
调用
1 2 3 4 5 6 public class Run { public static void main (String[] args) { GenericMethod genericMethod = new GenericMethod (); genericMethod.method(new ArrayList <Student>()); } }
1 2 T [class java .lang.Object]
如果规定了边界
1 public <T extends String > void method (T obj)
1 2 T [class java .lang.String]
应用
很多 JSON 工具都定义了类似名为 TypeReference
的类,其作用就是通过泛型类来传递需要反序列化的泛型信息,避免泛型擦除
如 fastjson 中的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class TypeReference <T> { static ConcurrentMap<Type, Type> classTypeCache = new ConcurrentHashMap <Type, Type>(16 , 0.75f , 1 ); protected final Type type; protected TypeReference () { Type superClass = getClass().getGenericSuperclass(); Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0 ]; Type cachedType = classTypeCache.get(type); if (cachedType == null ) { classTypeCache.putIfAbsent(type, type); cachedType = classTypeCache.get(type); } this .type = cachedType; } ... }
这样我们就可以通过 TypeReference
对象保存需要反序列化的泛型信息
1 2 3 4 5 6 7 8 9 10 public class Run { public static void main (String[] args) { Student student1 = new Student ("张三" , 20 ); Student student2 = new Student ("李四" , 21 ); String jsonStr = JSON.toJSONString(Arrays.asList(student1, student2)); List<Student> list = JSON.parseObject(jsonStr, new TypeReference <List<Student>>() {}); System.out.println(list); } }
1 [Student(name=张三, age=20 ), Student(name=李四, age=21 )]
参考
java
泛型详解-绝对是对泛型方法讲解最详细的,没有之一_VieLei的博客-CSDN博客_java泛型
Java泛型 | Jackson
TypeReference获取泛型类型信息 - 知乎 (zhihu.com)