Java泛型学习总结

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值