深入了解JAVA泛型
什么是泛型
泛型的概念:Java泛型(generics)是JDK1.5中引入的一个新特性,泛型提供了编译时的类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。
泛型的本质就是类型参数化,也就是所操作的数据类型被指定为一个参数。
使用泛型的好处:
1 在编译期间提供了类型检查
2 取数据时无须进行类型装换
泛型类、接口
泛型类
语法:
class类名称<泛型标识,泛型标识,泛型标识,...>{
private泛型标识变量名;
//...
}
常用的泛型标识:T、E、K、V
使用语法:
类名<具体的数据类型>对象名=new类名<具体的数据类型>();
JDK1.7之后,后面的<>中的具体的数据类型可以省略不写。
定义一个简单的泛型类:
/** *泛型类T:类型形参,在类创建对象时,指定具体的数据类型 *@authorrainszj *2020/3/19 */ publicclassGenericDemo01{ privateTvalue; publicGenericDemo01(){ } publicGenericDemo01(Tvalue){ this.value=value; } @Override publicStringtoString(){ return"GenericDemo01{"+ "value="+value+ '}'; } publicTgetValue(){ returnvalue; } publicvoidsetValue(Tvalue){ this.value=value; } }
测试一下:
publicclassTest{
publicstaticvoidmain(String[]args){
//在创建对象时指定具体的数据类型
GenericDemo01genericDemo01=newGenericDemo01<>("java");
//泛型类不支持基本数据类型,但可以使用基本类型对应的包装类
GenericDemo01genericDemo02=newGenericDemo01<>(1);
//在泛型类对象时,不指定具体的数据类型,将会使用Object类型来接收
//同一个泛型类,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板
//classcom.rainszj.GenericDemo01
System.out.println(genericDemo01.getClass());
//classcom.rainszj.GenericDemo01
System.out.println(genericDemo02.getClass());
//true
System.out.println(genericDemo01.getClass()==genericDemo02.getClass());
}
}
注意事项:
泛型类,如果没有指定具体的数据类型,按Object类型来接收
泛型的类型参数只能是类类型,也就是引用数据类型,不能是基本数据类型
泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
/** *抽奖池 * *@authorrainszj *2020/3/19 */ publicclassProductGetter{ //奖品 privateTproduct; privateArrayList list=newArrayList<>(); /** *添加奖品 * *@paramproduct */ publicvoidaddProduct(Tproduct){ list.add(product); } /** *抽取随机奖品 * *@return */ publicTgetProduct(){ returnlist.get(newRandom().nextInt(list.size())); } @Override publicStringtoString(){ return"ProductGetter{"+ "product="+product+ '}'; } } publicstaticvoidmain(String[]args){ ProductGetter productGetter1=newProductGetter<>(); //奖品类型礼物 String[]products1={"华为手机","苹果手机","扫地机器人","微波炉"}; //添加奖品 for(inti=0,length=products1.length;i productGetter2=newProductGetter<>(); //奖品类型money Integer[]products2={1000,3000,10000,500}; for(Integermoney:products2){ productGetter2.addProduct(money); } Integerproduct2=productGetter2.getProduct(); System.out.println("恭喜您抽中了,"+product2.toString()); }
从泛型类派生子类
子类也是泛型类,子类的泛型标识T要和父类的泛型标识T保持一致,或者是包含关系,子类的泛型标识包含父类的泛型标识
classChildGenericextendsParentGeneric classChildGeneric extendsParentGeneric
子类不是泛型类,父类要明确泛型的数据类型
classChildGenericextendsParentGeneric
泛型接口
语法:
interface接口名称<泛型标识,泛型标识,...>{
泛型标识方法名();
}
实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型
publicclassAppleimplementsGeneric{}
实现类也是泛型类,实现类和接口的泛型类型要一致,或者是包含关系,实现类的泛型标识包含泛型接口的泛型标识
publicclassAppleimplementsGeneric {} publicclassApple implementsGeneric {}
定义一个泛型接口
publicinterfaceGeneric{ KgetKey(); }
实现其中方法:
/** *泛型接口的实现类,是一个泛型类, *那么要保证实现接口的泛型类的泛型标识包含泛型接口的泛型标识 */ publicclassPairimplementsGeneric { privateKkey; privateVvalue; publicPair(){ } publicPair(Kkey,Vvalue){ this.key=key; this.value=value; } @Override publicKgetKey(){ returnkey; } publicVgetValue(){ returnvalue; } @Override publicStringtoString(){ return"Pair{"+ "key="+key+ ",value="+value+ '}'; } }
测试:
publicclassMyTest{
publicstaticvoidmain(String[]args){
Pairpair=newPair<>("数学",100);
System.out.println(pair.toString());
//Pair{key=数学,value=100}
}
}
泛型方法
普通泛型方法
泛型类,是在实例化类时指明泛型的具体类型。
泛型方法,是在调用方法时,指明泛型的具体类型。
语法:
修饰符返回值类型方法名(形参列表){ //方法体... }
public与返回值中间
只有声明了
publicclassProductSetter{ privateTproduct; privateRandomrandom=newRandom(); privateArrayList list=newArrayList<>(); publicvoidaddProduct(Tproduct){ list.add(product); } /** *@paramlist *@param 泛型方法的类型,是在调用泛型方法时确定的 *@return */ public EgetProduct(ArrayList list){ returnlist.get(random.nextInt(list.size())); } publicTgetProduct(){ returnlist.get(random.nextInt(list.size())); } @Override publicStringtoString(){ return"ProductSetter{"+ "product="+product+ '}'; } }
测试:
publicstaticvoidmain(String[]args){
ProductSetterproductSetter=newProductSetter<>();
String[]products1={"华为手机","苹果手机","扫地机器人","微波炉"};
for(inti=0;ilist1=newArrayList<>();
list1.add("华硕电脑");
list1.add("苹果电脑");
list1.add("华为电脑");
Stringproduct1=productSetter.getProduct(list1);
System.out.println(product1+"\t"+product1.getClass().getSimpleName());
//华为电脑String
ArrayListlist2=newArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
Integerproduct2=productSetter.getProduct(list2);
System.out.println(product2+"\t"+product2.getClass().getSimpleName());
//2Integer
}
静态泛型方法
publicstaticvoidpringType(Tk1,Ek2,Kk3){ System.out.println(k1+"\t"+k1.getClass().getSimpleName()); System.out.println(k2+"\t"+k2.getClass().getSimpleName()); System.out.println(k3+"\t"+k3.getClass().getSimpleName()); } //方法的调用 ProductSetter.pringType(1,"hello",false);
//输出结果
1 Integer
hello String
false Boolean
注意:
//在泛型类中无法添加静态的带有泛型成员方法,但可以添加静态的泛型方法 publicclassTest{ //带有泛型的成员方法 //错误 publicstaticTgetKey(Tkey){ returnkey; } //泛型方法 //正确 publicstatic EgetKey(Ekey){ returnkey; } }
泛型方法中的可变参数
publicclassMyTest{
publicstaticvoidmain(String[]args){
MyTest.print(1,2,3);
}
/**
*泛型方法中的可变长参数
*@paramvalue
*@param
*/
publicstaticvoidprint(E...value){
for(inti=0;i
总结:
泛型方法能使方法独立于类而产生变化。
如果static方法要使用泛型能力,就必须使其成为泛型方法。
类型通配符
类型通配符一般是使用?代替具体的类型实参。
类型通配符是类型实参,而不是类型形参。
我们先来定义一个简单的泛型类:
publicclassBox{
privateTwidth;
publicstaticvoidshowBox(Boxbox){
Numberwidth=box.getWidth();
System.out.println(width);
}
publicTgetWidth(){
returnwidth;
}
publicvoidsetWidth(Twidth){
this.width=width;
}
}
main方法:
publicstaticvoidmain(String[]args){
Boxbox1=newBox();
box1.setWidth(100);
showBox(box1);
}
当我们在main方法中增加这一段代码时,就会报错
Boxbox2=newBox<>();
box2.setWidth(200);
showBox(box2);
虽然Integer类继承自Number类,但在类型通配符中不存在继承这一概念!
也许你会使用方法的重载,但是在同一个泛型类中,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板,所以无法通过方法的重载,传递不同的泛型类型。
这时可以使用类型通配符?,来代表具体的类型实参!
publicstaticvoidshowBox(Box>box){
Objectwidth=box.getWidth();
System.out.println(width);
}
类型通配符的上限
在我们上面的showBox()代码中,发现box.getWidth()得到的还是Object类型,这和我们不使用类型通配符,得到的结果是一样的。这时我们可以使用类型通配符的上限。
语法:
类/接口
要求该泛型的类型,只能是实参类型,或者是实参类型的子类类型。
publicstaticvoidshowBox(Boxbox){
Numberwidth=box.getWidth();
System.out.println(width);
}
publicstaticvoidmain(String[]args){
Boxbox2=newBox<>();
box2.setWidth(200);
showBox(box2);
}
使用类型通配符的下限,无法得知该类型具体是指定的类型,还是该类型的子类类型,因此无法在List集合中执行添加该类或者该类子类的操作!
publicstaticvoidshowAnimal(Listlist){
//错误
list.add(newCat());
list.add(newMiniCat());
}
类型通配符的下限
语法
类/接口
要求该泛型的类型,只能是实参类型,或者是实参类型的父类类型。
下面通过TreeSet集合中的一个构造方法来进一步理解类型通配符的下限
publicTreeSet(Comparatorcomparator){
this(newTreeMap<>(comparator));
}
首先是一个Animal类,只有一个name属性
publicclassAnimal{
privateStringname;
publicAnimal(Stringname){
this.name=name;
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
@Override
publicStringtoString(){
return"Animal{"+
"name='"+name+'\''+
'}';
}
}
然后它的一个子类,Cat添加一个属性:age
publicclassCatextendsAnimal{
privateintage;
publicCat(Stringname,intage){
super(name);
this.age=age;
}
publicintgetAge(){
returnage;
}
publicvoidsetAge(intage){
this.age=age;
}
@Override
publicStringtoString(){
return"Cat{"+
"age="+age+
'}';
}
}
最后是Cat的子类,MiniCat,再添加一个属性level
publicclassMiniCatextendsCat{
privateintlevel;
publicMiniCat(Stringname,intage,intlevel){
super(name,age);
this.level=level;
}
publicintgetLevel(){
returnlevel;
}
publicvoidsetLevel(intlevel){
this.level=level;
}
@Override
publicStringtoString(){
return"MiniCat{"+
"level="+level+
'}';
}
}
测试,首先我们要在MyTest类通过静态内部类的方式,实现比较的接口,在构造TreeSet时,传递比较器
publicclassMyTest{
publicstaticvoidmain(String[]args){
//正常
//TreeSetanimals=newTreeSet(newComparator1());
//正常
TreeSetanimals=newTreeSet(newComparator2());
//报错
//TreeSetanimals=newTreeSet(newComparator3());
Listlist=Arrays.asList(newCat("a",12),newCat("c",9),newCat("b",20));
animals.addAll(list);
animals.forEach(System.out::println);
}
publicstaticclassComparator1implementsComparator{
@Override
publicintcompare(Animalo1,Animalo2){
returno1.getName().compareTo(o2.getName());
}
}
publicstaticclassComparator2implementsComparator{
@Override
publicintcompare(Cato1,Cato2){
returno1.getAge()-o2.getAge();
}
}
publicstaticclassComparator3implementsComparator{
@Override
publicintcompare(MiniCato1,MiniCato2){
returno1.getLevel()-o2.getLevel();
}
}
}
结论:
通过以上的比较,我们可以看出,类型通配符的下限,只能传递实参类型的或者实参类型的父类类型。
我们每次比较使用的都是Cat类型,但在Comparator1比较的是Animal中的name属性,这是因为我们在初始化Cat对象的时候,一定会先初始化Animal对象,也就是创建子类对象的时候,一定会先创建父类对象,所以才可以进行比较。
如果是使用类型通配符的上限,在创建对象时,比较的是该类的子类对象中的属性,就会造成空指针异常!也就是Comparator3无法使用的原因,所以在TreeSet中才会使用,类型通配符的下限。
类型擦除
泛型是Java1.5引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前的代码兼容。那是因为,泛型信息只存在编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除。
无限类型擦除
先定义一个泛型类:
publicclassErasure{
privateTkey;
publicTgetKey(){
returnkey;
}
publicvoidsetKey(Tkey){
this.key=key;
}
}
输出结构:
publicstaticvoidmain(String[]args){
Erasureerasure=newErasure<>();
Classcls=erasure.getClass();
Field[]fields=cls.getDeclaredFields();
for(Fieldfield:fields){
System.out.println(field.getName()+":"+field.getType().getSimpleName());//key:Object
}
}
可以发现在编译完成后的字节码文件中,T-->Object类型
有限类型擦除
还是刚才的泛型类,只不过加了泛型的上限
publicclassErasure{//...}
测试不变,输出结果:
key:Number
当我们指定了泛型的上限时,它会将我们的泛型擦除为上限类型
同样对泛型方法,也是一样的道理
//泛型方法
publicEtest(Et){
returnt;
}
Method[]methods=cls.getDeclaredMethods();
for(Methodmethod:methods){
System.out.println(method.getName()+":"+method.getReturnType().getSimpleName());
}
//输出结果
//getKey:Number
//test:List
//setKey:void
桥接方法
泛型接口
publicinterfaceInfo{
Ttest(Tvalue);
}
泛型接口的实现类
publicclassInfoImplimplementsInfo{
@Override
publicIntegertest(Integervalue){
returnvalue;
}
}
测试
publicstaticvoidmain(String[]args){
Classcls=InfoImpl.class;
Method[]methods=cls.getDeclaredMethods();
for(Methodmethod:methods){
System.out.println(method.getName()+":"+method.getReturnType().getSimpleName());
}
}
//输出结果:
//test:Integer
//test:Object
原本InfoImpl中只是实现了Info接口中的一个方法,但通过反射却拿到了两个方法。其中返回值为Object的方法就是桥接方法。
在编译完成后,类型擦除的结果是这样的:
publicinterfaceInfo{
Objecttest(Objectvalue);
}
publicclassInfoImplimplementsInfo{
publicIntegertest(Integervalue){
returnvalue;
}
//桥接方法:保持接口和类的实现关系
@Override
publicObjecttest(Objectvalue){
return(Integer)value;
}
}
泛型数组
开发中,一般常用的是泛型集合
泛型数组的创建:
可以声明带泛型的数组引用,但是不能直接创建带泛型数组对象。
可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组。
//可以创建带泛型的数组引用
ArrayList[]arrayLists1=newArrayList[3];
//无法创建带泛型的数组对象
ArrayList[]arrayLists2=newArrayList[3];
简单使用java.lang.reflect.Array的newInstance(Class,int)创建T[]数组。封装一个泛型数组
publicclassGenericArray{
privateT[]array;
publicGenericArray(Classcls,intlength){
this.array=(T[])Array.newInstance(cls,length);
}
publicvoidput(intindex,Titem){
this.array[index]=item;
}
publicTget(intindex){
returnthis.array[index];
}
publicT[]getArray(){
returnthis.array;
}
publicstaticvoidmain(String[]args){
GenericArrayga=newGenericArray<>(String.class,3);
ga.put(0,"白虎");
ga.put(1,"青龙");
ga.put(2,"朱雀");
System.out.println(Arrays.toString(ga.getArray()));
}
}
泛型和反射
反射常用的泛型类:
Class
Constructor
通过反射创建对象,带泛型和不带泛型
ClasscatClass1=Cat.class;
try{
Constructorc1=catClass1.getConstructor();
Catcat=c1.newInstance();
}catch(Exceptione){
e.printStackTrace();
}
ClasscatClass2=Cat.class;
try{
Constructorc2=catClass2.getConstructor();
Objectcat2=c2.newInstance();
}catch(Exceptione){
e.printStackTrace();
}
以上就是深入了解JAVA泛型的详细内容,更多关于JAVA泛型的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。