数据结构入门篇:了解泛型

哈喽,友友,你好呀,从这篇文章开始呢,我会陆陆续续推出关于数据结构的讲解的,是基于java语言来写的。对java的基本语法还不熟悉的友友们可以在我的主页中寻找相关博客来看,里面的每一篇博客都是我认真打磨,不断更改肝出来的,相信一定会对你有所帮助的。哈哈,接下来我们直接进入泛型的讲解吧。 

 

目录

1、什么是泛型

2、引出泛型类

3、泛型类的定义与创建

4、裸类型(Raw Type)

5、 擦除机制

6、泛型的上界

6.1 语法

6.2 泛型上界的用法

7、泛型方法


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){.......}

所以那个求最大值的泛型方法可改为如下:

运行结果:


  以上就是我要今天分享的内容啦,后续我还会继续更新的哒,常看我的博客会学到很多知识哦 

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哆啦A梦的110

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值