Java泛型学习总结
文章目录
一、什么是泛型
泛型,即“参数化类型”。就是把具体类型参数化。
泛型的本质就是为了参数化类型,在不创建新类型的情况下,通过泛型指定的不同类型来控制形参限制的类型。
当确定具体类型后,泛型提供一种类型检测机制,只有相匹配的数据才能正常赋值,否则编译器不会通过。
泛型提高了代码可读性,不必进行强制类型转换
二、类型擦除
泛型信息只存在于代码编译阶段,在进入JVM之前,于泛型相关信息会被擦除,称为类型擦除。
List<String> list1=new ArrayList<>();
List<Integer> list2=new ArrayList<>();
Class cl1=list1.getClass();
Class cl2=list2.getClass();
Field[] fs1 = cl1.getDeclaredFields();
for ( Field f:fs1) {
System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
代码中得到的 cl1 和 cl2 是一样的都为class java.util.ArrayList.
那么String和Integer去哪里了呢?
答案是类型转译
在泛型被类型擦除的时候,之前泛型类中的类型参数如果没有指定上限,< T>会被转译为Object类型,如果指定了上限< T extends Father>则会被转译为上限类型
三、泛型的使用
泛型有三种使用方式:泛型类、泛型接口、泛型方法
1.泛型类
public class Demo<T>{
T a;
public Demo(T a){this.a=a;}//这不是一个泛型方法
//可以使用T是因为在声明泛型类时已经声明了泛型类型T
}
public class Demo2<T,E>{
T a;
E b;
public Demo(T a){this.a=a;}
}
Demo d1=new Demo(aaa);
Demo<Integer> d2=new Demo(123);
<>中的T就是类型参数,这只是惯用写法
但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:
T 代表一般的任何类。
E 代表 Element 的意思,或者 Exception 异常的意思。
K 代表 Key 的意思。
V 代表 Value 的意思,通常与 K 一起配合使用。
S 代表 Subtype 的意思,文章后面部分会讲解示意。
泛型类可以接收多个类型参数。
在实例化泛型类时,必需为类型参数指定具体的类型,泛型的类型参数只能是类或者自定义类,不能是简单类型。
当然,定义的泛型类在实例化时也可以不传入参数,此时便失去了泛型的限制作用,泛型类中的泛型方法和成员变量类型可以为任意类型
不能对确切泛型类型使用instanceof操作
2.泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型
public <T> void method1(){
T a;
}
< T>非常重要,可以理解为声明此方法为泛型方法,当然声明的泛型数量可以为多个。在形参中或方法中使用泛型或返回泛型都不能说明它是泛型方法。
只有声明了< T>才是泛型方法,方法中才可以使用泛型类型T。对于泛型类中使用了泛型类声明的泛型的成员方法并不是泛型方法。
类中的泛型方法
class Demo<T>{
public <E> void fun1(E e){};
public <T> void fun1(T t){};
}
< E>在声明泛型方法时声明,所以可以在方法中使用,但在外部就不行了,E也可以与类声明的T是相同的。
泛型方法声明的< T>与类声明的< T>不是同一个,两者可以相同也可以不同。可以这么想,泛型方法的T是在调用时说明的,而类的T在实例化就声明了,所以两者并不是同一回事。
静态方法和泛型
public class Demo<T>{
static <T> void fun(){
T a;
}
}
静态类是无法访问泛型类声明的泛型T的,所以静态方法要使用泛型的话,必需将静态方法定义为泛型方法
3.泛型接口
public interface Demo<T>{
public T fun();
}
public class Demo1<T,E> implements Demo<T>{
E a;
public T fun(){};
}
public class Demo1<T> implements Demo<String>{
T a;
public String fun(){};
}
当未向泛型接口传入实参,在声明类时,同样要声明泛型,否则会报错
当传入实参时,则所有使用泛型的地方都要替换为传入的实参类型
四、通配符 ?
除了< T>表示泛型外,还有<?>形式,?被称为通配符。
看一个例子:
有Animal父类和Dog子类
void fun(List<? extends Anmial> animals){}
void fun1(List<Animal> animals){}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
// 不会报错
fun( dogs );
// 报错
fun1(dogs);
}
因为LIst< Dog>和List< Animal>不没有继承关系,由此看出:同一种泛型(List的)多个版本(参数不同),对应的泛型类实例时无关的,不兼容的。
总不能每次都定义新方法来处理不同参数的泛型类,如何解决?就可以使用通配符?。
通配符的出现是为了指定泛型中的类型范围。
通配符的意义就是他是一个未知类型,可以代表任意类型。
类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型。
可以解决当具体类型不确定的时候,这个通配符就是 ? ,当操作类型时,不需要使用类型的具体功能时,只使用Animal类中的功能。那么可以用 ? 通配符来表未知类型。
无限定通配符< ?>表示未知的类型,所以涉及到?的操作一定要与具体类型无关。
1.上界通配符<? extends E>
public <E extends A,E extends B> fun(){}
public <E extends A> fun(){}
在类型参数中使用extends表示这个泛型中的参数必须是A或A的子类,所以泛型中可以使用A的方法。
2.下界通配符<? super E>
public <E super A> fun(){}
在类型参数中使用super表示这个泛型中的参数必须是A或A的父类。
上界通配符主要用于读数据,下界通配符主要用于写数据。
对于< ? extends E>,我们并不知道?的具体类型,我们只知道它的范围。所以就没有了增删能力,只能保留读的操作。
对于< ? super E>,它拥有一定的写的能力,因为由于向上转型,添加?的某个具体子类E是安全的。
3.T 和 ?的区别
?和 T虽然都是未知类型,但我们可以对T进行操作,但对?不可以。
T其实是个确定的类型,通常用于泛型类和泛型方法的定义,而?是一个不确定的类型(是个范围),通常用于泛型方法的调用代码和形参,不能用于定义泛型类和泛型方法。
1.通过T可以确保泛型参数的一致性,而?不行
void fun(List<T> list)
void fun(List<?> list)
2.类型参数可以多重限定但通配符不行
interface A{}
interface B{}
public <T extends A&B> void fun(){}
//public <? extends A&B> void fun(){}
通过&设定多重边界,指定泛型T必须是A,B的共有子类。对于通配符,他不是一个确定的类型,所以不能继续多重限定。
3.通配符可以使用超类限定但T不行
T extends A
? extends A
? super A
4 .通配符只能用于填充通配符T,不能用于定义变量
List<?> list=new ArrayList<String>();
? x;//错误
list=new ArrayList<?>();//错误
4.List和List<?>
List list = new ArrayList<String>();
List<?> list = new ArrayList<String>();
一个是省略了泛型标识;一个是使用了通配符(无界通配符,所以可以表示任意类型),两者是对等的。
构造泛型实例时,如果省略了填充类型,则默认填充为无边界通配符!
5.Class< T>和Class<?>
Class类是实例表示Java应用运行时的类或接口。
每个java类运行时都在JVM里表现为一个Class对象,可通过类名.class,类型.getClass(),Class.forName(“类名”)等方法获取Class对象。
单独的T 代表一个类型;而 Class< T>和Class<?>代表这个类型所对应的类。
Object类中包含一个方法名叫getClass,利用这个方法就可以获得一个实例的类型类。类型类指的是代表一个类型的类,在Java使用类型类来表示一个类型。所有的类型类都是Class类的实例。getClass() 会看到返回Class<?>。
普通的Class.newInstance()方法的定义返回Object,要将该返回类型强制转换为另一种类型;
但是使用泛型的Class< T>,Class.newInstance()方法会返回特定类型,同样也能在编译期间直接检查到类型返回是否正确的问题。
class A{}
class B{}
public class Demo{
public static <T> T creatInstance(Class<T> clazz){
return clazz.newInstance();
}
public static void main(String[] args){
A a=creatInstance(A.class);
B b=creatInstance(B.class);
//如果直接反射得到,需要类型转换,因为返回是Object。在运行期,如果反射的类型不是A类,那么一定会报 java.lang.ClassCastException 错误
//Class clazz2 = A.class;
//A a2=(A)clazz2.newInstance();
}
}
五、参考
https://www.cnblogs.com/minikobe/p/11547220.html
https://blog.csdn.net/s10461/article/details/53941091
https://blog.csdn.net/briblue/article/details/76736356
https://blog.csdn.net/witewater/article/details/53462385
https://blog.csdn.net/harvic880925/article/details/49883589