泛型是在jdk1.5之后被引入的,技术革新一定是由于前面的技术的缺陷造成的,因此需要了解泛型出现的原因,才能更好的理解和使用泛型。
一:多态性复习
在之前已经写了一篇关于java多态性的理解总结,链接如下:
JAVA多态性
为节约篇幅,接下来就接上一篇继续进行。
class Ball{
public void play(){
System.out.println("在玩Ball");
}
}
class BasketBall extends Ball{
public void rule(){
System.out.println("篮球规则为:....... ");
}
public void play(){
System.out.println("在玩篮球");
}
}
class FootBall extends Ball{
public void play(){
System.out.println("在玩足球");
}
}
public class DemoTest{
public static void main(String args[]){
Ball ball = new BasketBall();//向上类型转换
playRule(ball);
}
public static void playRule(Ball ball){
BasketBall bb = (BasketBall)ball; //向下类型转换(强制转换)
bb.rule();
}
}
拿了一段代码,上面定义static静态方法时参数的类型为Ball,是父类,实际上传输的是BasketBall这个子类对象,实现了参数统一,具体的作用优点已经在上面的链接文章中给出了。
二:Object的超然地位
在jdk1.9之后,java引入了自动装箱和自动拆箱机制,这就给基本数据类型转换成相应的引用数据类型提供了便利。而Object类是又是所有类的父类,他可以接收所有的引用类型。即可以实现将所有的引用类型实例对象转换成Object的对象(向上类型转换)。也就可以实现参数的统一,比上面的还要彻底 !
Object ball = new BasketBall();//向上类型转换
三:由Object引发的问题
成也萧何败也萧何,由于Object的描述范围的广泛,也会造成一些安全隐患
class Ball{
//...
}
class BasketBall extends Ball{
public void rule(){
System.out.println("篮球规则为:....... ");
}
}
class FootBall extends Ball{
public void rule(){
System.out.println("足球规则为:....... ");
}
}
public class MulChangeUpTest{
public static void main(String args[]){
Ball ball = new BasketBall();
playRule(ball);
}
public static void playRule(Object obj){
FootBall b = (FootBall)obj;
b.rule();
}
}
运行结果:编译成功,运行报错
让人头疼的是:这个问题他是编译不报错的,而是运行时报错的,这就会造成极大的安全隐患。
有人说为什么不去使用instanceof
去判断一下,-_- 可千万不要擡杠,我们这是在提出问题,准备去解决问题。但是instanceof
只是一个防治措施,是为了防止出现崩溃,并不是解决问题的根本措施,治标不治本。
四:泛型出场
我们在了解到了由于Object作为参数带来的缺点之后,必须要进行解决。我特别喜欢老师说的一句话,转型会带来问题那么就不去转型。使用别的途径去解决问题。
class Ball<T>{
// 球的类型可能使用String,或者Integer类型代表
private T ballType;
public void setBallType(T ballType){
this.ballType = ballType;
}
public T getBallType(){
return this.ballType;
}
}
public class DemoTest{
public static void main(String args[]){
Ball<Integer> ball = new Ball<Integer>();
ball.setBallType(10);
int type = ball.getBallType();
System.out.println(b.getBallType());
}
}
当前类型被死死的限制为了Integer,就不会造成这种安全隐患了。
此时编译和运行都正确。使用泛型<T>
来表示Type类型,这个Ball类内部的所有T代表的类型,是在运行时动态决定的。
如果不按照泛型定义的类型进行赋值:
public class DemoTest{
public static void main(String args[]){
Ball<Integer> ball = new Ball<Integer>();
ball.setBallType("篮球");
}
}
报错信息:
如此,使用了泛型后在编译期间就会报错,就将这种安全隐患解决了。
五:参数统一下的泛型:泛型通配符
先看一段代码
class Ball<T>{
// 球的类型可能使用String,或者Integer类型代表
private T ballType;
public void setBallType(T ballType){
this.ballType = ballType;
}
public T getBallType(){
return this.ballType;
}
}
public class DemoTest{
public static void main(String args[]){
Ball<Integer> ball = new Ball<Integer>();
ball.setBallType(10);
playRule(ball);
}
public static void playRule(Ball<String> b){
System.out.println(b.getBallType());
}
}
先想一想,我们使用Object的最大优点是什么? 就是实现了参数类型的统一。但是使用了上面的泛型来定义playRule的参数类型时,将传入的参数类型限制在了String类型。Integer类型的就会报错,这与我们改善Object的初衷是相悖的。
此时就需要一个泛型用于描述一切的类型。即泛型通配符
<?>
public static void playRule(Ball<?> b){
System.out.println(b.getBallType());
}
六:总结
技术改进的道理如果明白,那么在使用时自然就更会印象深刻,接受更快。在理解他的设计思想和改进原因下,更会事半功倍。