一、概念
泛型Java SE 1.5的新特性,泛型的本质是参数化类型,定义方法时形参不指定具体的 java类型,而是使用泛型代替类型,调用的时候实参传入具体的类型。定义类或者接口 时可以使用泛型,通过继承或者实现,减少了冗余代码,提高了代码的复用性。Java 不能创建具体类型的泛型数组
List[] li2 = new ArrayList[];
1.类型形参: 定义参数时不指定具体的类型,使用泛型通配符
2.类型实参: 在调用时传入具体的类型(类型实参)
二、特性
泛型只在编译阶段有效。javac 编译器在把java文件编译成计算机识别的二进制字节码文件时, 会首先进行类型检查,检查通过后进行类型擦除。JVM并不知道泛型的存在,因为泛型在 编译阶段就已经被处理成普通的类和方法;三、泛型的作用
1.类型安全
编译时的强类型检查,对集合等容器限制存储元素的类型,编译时检查存储的元素是否
是容器允许存储的类型。把运行时期的问题提前到了编译期间。
2.消除强制类型转换
没有泛型时可以存任意类型,取值时就需要强制类型转换。加了泛型,统一了容器中的元素类型,消除代码中强转,避免了强转时可能会出行的类型转换异常
3.更好的代码复用性:
在框架设计时,类可以使用泛型类,通过继承或者实现,实现了所有的公共方法或者定
义接口公共响应类时使用泛型类,减少了冗余代码,提高了代码的复用性。
4、潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类
型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。
四、泛型类
泛型类型用于类的定义中 T(可以使用泛型定义公共的响应实体) 如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。 如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。一、定义格式
访问修饰符 class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var; .....
}
二、继承情况下的泛型指定情况
1.子类继承泛型类,可以都不用指定泛型类型
2.子类可以指定泛型,父类可以不用指定
3.父类指定泛型,子类可以不用指定
@Data
public class Test<T> {
private final String name = "哈哈哈";
private T getT(T t){
return t;
}
static class MyTest extends Test<String>{
public String getStr(){
String nameStr = this.getName();//哈哈哈
System.out.println(nameStr);
return super.getT("Hello");
}
}
public static void main(String[] args) {
String str =new MyTest().getStr();
System.out.println(str);//Hello
}
}
注意:
1. 泛型的类型参数只能是引用数据类型,不能是基本数据类型。
2. 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
if(ex_num instanceof Generic<Number>){
}
五、泛型接口
泛型接口常被用在各种类的生产器中一.定义格式
访问修饰符 <泛型标识> 泛型标识 方法名(){
}
二.当实现泛型接口的类,未传入泛型实参时:
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中,如果不声明泛型,编译器会报错
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,
* 编译器会报错:"cannot be referenced from a static context"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
三、当实现泛型接口的类,传入泛型实参时:
实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Test<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Test接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Test<T>,MyTest<String>,getStr(T t);中的的T都要替换成传入的String类型。
*/
public interface Test<T> {
/**
* 获取T
* @param t
* @return
*/
String getStr(T t);
class MyTest<String> implements Test<String>{
@Override
public java.lang.String getStr(String string) {
return (java.lang.String) string;
}
}
static void main(String[] args) {
//Hello-World
System.out.println(new MyTest().getStr("Hello-World"));
}
}
六、泛型方法
类中要使用泛型属性,该类必须是泛型类
类中的方法要使用泛型,该泛型要么在类中声明,要么该方法是泛型方法(泛型方法会在方法中声明泛型)
调用方法的时候指明泛型的具体类型
格式:
访问修饰符 <T> 返回值类型 方法名(形参列表){
}
6.1 类中的泛型方法
泛型可以定义多个,根据需要使用或者返回哪个泛型
泛型属性未在类中声明,编译报错 Cannot resolve symbol ‘E’
泛型类和泛型方法的泛型两个相互不影响。即使两个声明的泛型是同一个,泛型方法的泛型也可以与泛型类中声明的T不是同一种类型
泛型类中的普通方法入参为类的泛型,调用方法时可以传入泛型本身和他子类
注:
静态方法无法访问类上定义的泛型,如果静态方法需要使用泛型,需要静态方法也是泛型方法
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("rawtypes")
public class DynamicProxy<T,E> implements Serializable {
private static final long serialVersionUID = 6733947012338693363L;
private String id;
private String name;
private T data;
//泛型类中未声明 编译报错 Cannot resolve symbol 'E'
private E e;
public String getString(T e){
return String.valueOf(e);
}
static class DynamicProxyChile extends DynamicProxy{
private static final long serialVersionUID = 226641748393219745L;
}
@SuperBuilder
@Data
static class Iphone{
}
public static <T,K> K getStr(T t,K k){
return k;
}
public <T> T getStr(T t){
return t;
}
public static void main(String[] args) {
String str = DynamicProxy.getStr("Hello World !","我爱我国");
System.out.println(str);//我爱我国
DynamicProxy dynamicProxy = DynamicProxy.getStr(DynamicProxy.builder().id("1").name("奥巴驴").build(),
DynamicProxy.builder().id("2").name(null).build());
System.out.println(dynamicProxy);//DynamicProxy(id=2, name=null, data=null, e=null)
//泛型类中的普通方法入参为类的泛型,调用方法时可以传入泛型本身和他子类
DynamicProxy<DynamicProxy, Integer> proxy = new DynamicProxy<>();
String string = proxy.getString(DynamicProxyChile.builder().build());//DynamicProxy子类
String proxyString = proxy.getString(proxy);//DynamicProxy本身
Iphone str1 = proxy.getStr(Iphone.builder().build());
System.out.println(str1);//DynamicProxy.Iphone()
}
}
6.2 泛型方法与可变参数
可变参数可以是统一类型,也可以是不同类型
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("rawtypes")
public class DynamicProxy<T,E> implements Serializable {
private static final long serialVersionUID = 6733947012338693363L;
private String id;
private String name;
private T data;
public static <T> T[] getStr(T... args){
return args;
}
public static void main(String[] args) {
Serializable[] str = getStr(DynamicProxy.builder().build(), "1", 123);
//DynamicProxy(id=null, name=null, data=null) 1 123
Arrays.stream(str).forEach(o -> System.out.println(o));
Integer[] integers = getStr(1, 2, 3, 4, 5);
//1 2 3 4 5
Arrays.stream(integers).forEach(o -> System.out.println(o));
}
}
七、泛型通配符
一、类型通配符
当具体类型不确定的时候,这个通配符就是 ? ,如:Generic<Integer>和Generic<Number> : Generic<?>
类型通配符一般是使用?代替具体的类型实参
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<Integer>不能被看作为`Generic<Number>的子类
同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的
E - Element 在集合中使用 或者 Exception 异常的意思。
T - Type Java 类
K - Key java中的键
V - Value java中的值
N - Number 数值类型
二、? 无界通配符
无界通配符,表示不确定的 java 类型
泛型List<?> 在没有赋值前,不可以add()新增元素,表示可以接受任何类型的集合赋值, 赋值之后不能往里面随便添加元素,但可以remove和clear。 List<?>一般作为参数来接收外部集合,或者返回一个具体元素类型的集合。
使用场景:
1.编写可使用 Object 类中提供的功能使用的方法时。
2.当代码使用不依赖于类型参数的泛型类中的方法时。
public static void printList(List<?> list) {
for (Object o : list) {
/* 方法体 */
}
}
三、限制通配符
1. 上限通配符 <? extends E>
放宽对变量的限制时使用
List<? extends A> 代表的是一个可以持有 A及其子类(如B和C)的实例的List集合。
上限通配符只能从结构中获取值而不能将值放入结构:取值类型是他本身因为你不知道结构中具体是什么元素
如下:只能从list中取值,不能往list中设置值
//取值是Number类型
public static Double sum(List<? extends Number> list){
final Double[] sum = {
(double) 0};
list.forEach(item ->{
sum[0] += item.doubleValue() ;
});
return sum[0];
}
public static void main(String[] args) {
System.out.println(Test.sum(Arrays.asList(1, 25, 34, 5)));//65.0
System.out.println(Test.sum(Arrays.asList(1.2, 2.5, 34.9, 5.0)));//43.6
}
2. 下限通配符 <? super E>
List<? super Integer> list代表的是Integer本身要么时Integer的父类的集合。
上限通配符能往结构中取值也能设置值:取值是Object的,
因为下限通配符要么本身或者他的父类,那么我们往结构中添加他本身和他的子类是合法的
//取值是Object
public static Integer sum(List<? super Integer> list){
final Integer[] sum = {
0};
list.forEach(o ->{
sum[0] += (Integer) o;
});
/**
* java.lang.UnsupportedOperationException
* Arrays.asList()生产的List的add、remove方法时报异常,这是由Arrays.asList() 返回的是Arrays的内部类ArrayList,
* 而不是java.util.ArrayList。Arrays的内部类ArrayList和java.util.ArrayList都是继承AbstractList,
* remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。
* java.util.ArrayList重新了这些方法而Arrays的内部类ArrayList没有重新,所以会抛出异常。
*/
list = new ArrayList<>(list);
list.add(100);
System.out.println(list);//[1, 2, 3, 4, 5, 100]
return sum[0];
}
public static void main(String[] args) {
System.out.println(Test.sum(Arrays.asList(1, 2, 3, 4, 5)));//15
}
不允许定义下限类
泛型在编译时都会被擦除,T 所代表的是一个 Fruit 的超类,但是具体是哪个类却是在运行时被决定的,编译器为了类型安全,
只能做最大限度的包容,因此所有的 T 类型都会在编译器变为 Object。所以,写 <T super Fruit> 等同于写 <Object>,
因此不支持 <T super Xx>
八、泛型擦除
javac 编译器在把java文件编译成计算机识别的二进制字节码文件时, 会首先进行类型检查,检查通过后进行类型擦除 JVM并不知道泛型的存在,因为泛型在编译阶段就已经被处理成普通的类和方法;擦除规则:
若泛型类型没有指定具体类型,用Object作为原始类型;
若有限定类型< T exnteds XClass >,使用XClass作为原始类型;
若有多个限定< T exnteds XClass1 & XClass2 >,使用第一个边界类型XClass1作为原始类型;
List<String> strings = new ArrayList<>();
// strings.add("你好,世界");
List<Integer> integers = new ArrayList<>();
// integers.add(100);
//空集合打印 新增元素后打印
//true false
System.out.println(strings.hashCode() == integers.hashCode());
//true false
System.out.println(strings.equals(integers));
//java.util.ArrayList java.util.ArrayList
System.out.println(strings.getClass().getName());
//java.util.ArrayList java.util.ArrayList
System.out.println(integers.getClass().getName());
//true true
System.out.println(strings.getClass().getName().hashCode() == integers.getClass().getName().hashCode());
类加载器把 ArrayList.class文件加载进jvm之后,只会存在一份地址相同的ArrayList的Class类,所以虽然编译时是List<String> 和 List<Integer>,
但是运行时泛型擦除了,他们的class类(也就是getClass()方法获得的)都是同一个,在编译完成后都会被编译器擦除成了 ArrayList(地址也相同)。
只不过该Class类有两个实例对象l1和l2而已,两个实例对象的地址不同。
没有指定泛型类型,泛型类被类型擦除后,相应的类型被替换成 Object 类型
@Data
public class Test<T> implements Serializable {
private static final long serialVersionUID = 157827475733995670L;
private T object;
public Test(T object) {
this.object = object;
}
public static void main(String[] args) {
Test<String> hello = new Test<>("hello");
Class<? extends Test> aClass = hello.getClass();
System.out.println(aClass.getName()); //com.cihai.sojourn.T11111111111.Test
//Field name serialVersionUID type:long
//Field name object type:java.lang.Object 没有指定泛型类型,泛型类被类型擦除后,相应的类型被替换成 Object 类型
for (Field declaredField : aClass.getDeclaredFields()) {
System.out.println("Field name "+declaredField.getName()+" type:"+declaredField.getType().getName());
}
}
}
指定了上限则类型参数就被替换成类型上限类型
@Data
public class Test<T extends Integer> implements Serializable {
private static final long serialVersionUID = 157827475733995670L;
private T object;
public Test(T object) {
this.object = object;
}
public void add(T object){
}
public static void main(String[] args) {
Test<Integer> hello = new Test<>(666);
Class<? extends Test> aClass = hello.getClass();
System.out.println(aClass.getName());//com.cihai.sojourn.T11111111111.Test
//Field name serialVersionUID type:long
//Field name object type:java.lang.Integer 指定了上限如 <T extends Integer>则类型参数就被替换成类型上限 Integer类型
for (Field declaredField : aClass.getDeclaredFields()) {
System.out.println("Field name "+declaredField.getName()+" type:"+declaredField.getType().getName());
}
Method[] declaredMethods = aClass.getDeclaredMethods();
// method:public void com.cihai.sojourn.T11111111111.Test.add(java.lang.Object) //擦除后add() 这个方法对应的 Method 的签名应该是 Object.class
for (Method declaredMethod : declaredMethods) {
System.out.println(" method:"+ declaredMethod);
}
//反射中找到 add 对应的 Method getDeclaredMethod("add", Object.class)
try {
String add = aClass.getDeclaredMethod("add", Object.class).getName();
System.out.println("add方法名" + add);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}