哈喽,友友,你好呀,从这篇文章开始呢,我会陆陆续续推出关于数据结构的讲解的,是基于java语言来写的。对java的基本语法还不熟悉的友友们可以在我的主页中寻找相关博客来看,里面的每一篇博客都是我认真打磨,不断更改肝出来的,相信一定会对你有所帮助的。哈哈,接下来我们直接进入泛型的讲解吧。
1、什么是泛型
泛型是在JDK1.5引入的新的语法。所谓泛型,通俗一点来讲:就是一个类适用于多种类型,这个类就是泛型类。从代码上讲,就是对类型实现了参数化。
可能大家看到这里还是很模糊,这说的是什么嘛,什么适用于多种类型,什么类型参数化,不要着急,接下来,我们通过例子来仔细分析分析,一点点揭开它神秘的面纱。
2、引出泛型类
通过前面的学习,我们知道一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。但这种刻板的限制对代码的束缚很大,那我们能不能定义一个类,可以用来适用于多种类型的代码呢?
比如我现在要实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值,以满足类的不同使用者对数组元素类型需求不同的需求。
按照我们以前的思路(没有用到泛型):我们知道,数组只能放指定类型的元素,如int[ ] array=new int [10];String[ ] strs=new String[10]等,那如果要放不同类型的数据,我们肯定不能定义一个int或String等一些具体类型的数组。我们知道,Object类默认为所有类的父类,那我们是不是可以定义一个Object数组?如果知道了要创建一个什么样的数组,我们就可以写以下代码来实现需求:
让我们看一下编译报错原因:
为什么会报错,原因就在这里,仔细看看,我在定义getArrayPos()方法时,因为不知道数组元素的具体类型,所以返回的是Object类型,在主函数调用的时候将一个Object类型赋值给String类型当然就会报错啦,所以我们就需要对其进行强制类型转化。所以只需要将划红色波浪线的那句改成以下就好啦:
这时再来看一下输出结果:
这就是我们想要的结果了。
但是,这样写到底好不好呢,我们来分析一下:
以上代码任何类型的数据都可以存放
1号下标对应的元素本来就是String类型,但是编译会报错,必须得强转
虽然在这种情况下,在这个类中,数组的任何数据类型都可以存放,但是,对于我们类的使用者来说,我们一般在使用时是确定要用哪种类型的,此时我们就希望它只能够持有一种数据类型,并且这种数据类型就是我们类的使用者所需要的,这样我们就不用担心强转什么的。为了满足这个需求,我们就有了泛型。所以泛型的主要目的就是:指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,我们就要把类型作为参数来传递。即需要什么类型,就传入什么类型。
在用泛型解决以上的那个数组问题时,我先讲一下泛型的语法规则。
class 泛型类名<类型形参列表>{
** //这里可以使用类型参数**
}
class ClassName<T1,T2,T3...,Tn>{
......}
class 泛型类名<类型形参列表> extends 继承泛型类<类型形参列表>{
** //这里可以使用部分类型参数**
}
class ClassName<T1,T2,T3...,Tn> extends ParentClass<T1>{
......}
我们现在再来对之前那个数组问题进行改写:
现在这个代码用到了泛型类,我们在创建对象时指定了要传一个Integer进去,因此Myarray类里面的T就被指定为了Integer,当在主方法中想调用set方法放入一个String类时,编译器会在存放元素时帮我们进行类型检查,此时当然就会报错了,因为只能放整型。如果想放String类型的数据,就直接再重新new一个对象即可,new的时候将泛型类的类型形参指定为String类型即可。如以下(以下为那个数组问题的完整正确代码):
class MyArray<T>/*类名后的<T>表示占位符,表示当前类是一个泛型类*/{ public T[] array=(T[]) new Object[10];//1 public T getArrayPos(int pos) { return array[pos]; } public void setArrayPos(int pos,T val) { this.array[pos] = val; } } public class Test { public static void main(String[] args) { ** MyArray<Integer> myArray1=new MyArray<>();**//类型后面加入<Integer>指定当前类型 myArray1.setArrayPos(0,22); myArray1.setArrayPos(1,44); int a=myArray1.getArrayPos(0);//不需要进行强制类型转换 System.out.println(a); ** MyArray<String> myArray2=new MyArray<>();** myArray2.setArrayPos(0,"hello"); myArray2.setArrayPos(1,"world"); String str= myArray2.getArrayPos(0); System.out.println(str); } }
运行结果:
注释1的地方要注意一下:不能new泛型类型的数组。以下写法是错误的:
T[ ] t=new T[5];//错
3、泛型类的定义与创建
泛型类类名<指定类型实参> 变量名;//定义一个泛型类引用
new 泛型类类名<指定类型实参>();//创建一个泛型类对象
如果将两者写在一起,则可以写成:
泛型类类名<指定类型实参> 变量名=new 泛型类类名<指定类型实参>();
也可以省略后面的指定类型实参,因为编译器可以根据上下文推导出类型实参,因此我们可以简写为:
泛型类类名<指定类型实参> 变量名=new 泛型类类名<>();
**注意:泛型只能接受类,所有的基本数据类型必须使用包装类 **
4、裸类型(Raw Type)
裸类型是一个泛型类,但是没有带类型形参。比如我们最开始写的那个MyArray就是一个裸类型,不用往上翻了,给你截下来了,嘿嘿~
注意:我们自己不要去使用裸类型,这是为了兼容老版本的API保留的机制。
5、 擦除机制
泛型在编译的过程中,会将所有的T替换为Object,我们称之为擦除机制。java的泛型机制都是在编译级别完成的。编译生成的字节码文件在运行时并不包含泛型的类型信息。
关于泛型的擦除机制的详细介绍,友友可以查看一下这篇大佬的文章:Java泛型擦除机制之答疑解惑 - 知乎 (zhihu.com)
那现在估计又有人有问题了,之前说不能new泛型类型的数组,即T[ ] t=new T[5]这种写法是错的,但是为什么会错呢,编译之后T替换为Object,不就成了Object[ ] t=new Object[5]吗?
为什么会错呢,大家可以翻到这篇文章的最上面的那段代码,如果这样写的话,使用该类的时候把Object数组传给一个有具体类型的数组,编译器肯定会觉得不安全然后报错,要解决的话又要发生强转啥的,那就又麻烦了。
6、泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
6.1 语法
class 泛型类名称<类型形参 extends 类型边界>{
** ......**
}
如:
public class MyArray<E extends Number>{** ......
}**
此时只能接受Number或其子类作为E的类型实参
MyArray<Integer> M1;//正确,因为Interge是Number的子类
MyArray<String> M2;//错误,String不是Number的子类
如果没有指定类型边界,可默认可视为E extends Object。
6.2 泛型上界的用法
例:写一个泛型类,类中有一个方法,可以用来求学生年龄的最大值
先来分析一下为什么这里会报错呢?原因很简单, max在这里不是基本类型,是一个引用类型,引用类型肯定不能用>、<来比嘛,引用类型的比较,一般用比较器或者实现Comparable接口。
在这里,我就采用实现Comparable接口来比较好了。
为什么在这里不能用呢?原因很简单,之前我们了解到在泛型编译时会被擦除为Object类型,而Object类并没有实现Comparable接口 ,因此不能用comparaTo方法。那有没有什么办法让泛型类型不被擦除我们的老祖宗Object呢,因为现在我们只希望擦除成的那个类型能使用comparaTo方法。答案当然有,以下的写法就是正确的:
在这里Comparable就是泛型的上界,这样写就保证了泛型类型擦除时最多只能被擦除为其上界,无法擦除为Object类型。我们来测试一下这段正确的代码:
运行结果:
7、泛型方法
以上面那个泛型类中求最大值的方法为例:假设我们现在要将findMax()这个方法改成静态方法,以方便以后不用new对象而直接用。那有人就说了,这不很简单吗,加ststic不就行了,比如这样:
图中我们可以看出,人家编译器都给你提示报错了,这样写肯定是不对的。那为什么不对呢,我们来分析一下:
首先我们都知道只要是静态的方法或变量,都不依赖于对象,不需要我们去new一个对象,然后通过对象来调用,静态的方法或成员变量只需要通过类名去调用访问即可。所以一旦不需要new对象了,那我们就没有机会给泛型赋具体类型了,那编译器怎么知道静态方法里面的T是什么类型呢,所以编译器就给报错了。那怎么改呢,以下是关于泛型方法的语法规定:
方法限定符<类型形参列表> 返回类型 方法名(形参列表){.......}
public static<T extends Comparable<T>> T findMax(T[ ] array){.......}
所以那个求最大值的泛型方法可改为如下:
运行结果:
以上就是我要今天分享的内容啦,后续我还会继续更新的哒,常看我的博客会学到很多知识哦** **
版权归原作者 哆啦A梦的110 所有, 如有侵权,请联系我们删除。