Java中的纸老虎之泛型

网友投稿 261 2022-12-07

Java中的纸老虎之泛型

目录一. 泛型的定义二. 为什么要用到泛型三. 泛型的写法四. 泛型的使用实例1. 求最大值2. 优化五. 通配符1. 基本写法2. 上界3. 下界六. 泛型的限制

泛型,其实算是java当中比较难的语法了,很多人一开始都对其一知半解,也很害怕阅读带泛型的源码,虽然看起来语法很难,但当你理解后会觉得很简单,其实只是一个纸老虎罢了。下面,我将会用非常简单易懂的方式带你去理解它,相信你在认真看完后会有非常大的收获,从此不会再畏惧它!

一. 泛型的定义

这里大家可以不必去看网上的有些定义,因为相对于比较学术化,只需记住泛型可以在程序设计中指定某种类型,让程序的设计更加规范化即可

二. 为什么要用到泛型

了解到了泛型是什么后,那我们来讨论讨论为什么要用泛型这个语法,这个语法到底是干什么的?别急,下面,我先给大家举一个例子:

class Stack {

public Object[] objects;

public int top;

public Stack() {

this.objects =new Object[10];

}

public void push(Object obj) {

objects[this.top++] = obj;

}

public Object get() {

return objects[this.top-1];

}

}

大家可以看看这是在干什么呢?这是我们自己写了一个栈,然后将栈里的数组类型设置成Object类型,这样的话这个栈里任意类型的数据都可以存放了(Object类是任何类的父类,不管插入什么类型的数据,都可以发生向上转型)

下面,我们来测试一下

public class Test {

public static void main(String[] args) {

Stack stack=new Stack();

stack.push(1);

stack.push(2);

stack.push("123");

String str=(String)stack.get();

}

}

可以看到,我们可以向自己写的栈里放入整形以及字符串等等任何类型的数据,但注意一下取出数据的时候要进行强制类型转换

以上这样写,可以向栈里存放任何类型的数据,比较通用,其优点也可以变成缺点,正因为太通用了,使代码的规范性降低,看起来比较凌乱,这时候,我们可以考虑使用泛型,这样可以在类中或者Java集合中存放特定的数据(使用Java集合时,一般都要用到泛型,而自定义的类型中可以使用泛型也可以不使用)

三. 泛型的写法

以自定义的类型为例,写法为在类名后面加上尖括号,里面写上一个字母(注意,此处写任何字母都可以,只起到一个标记这个类为泛型类的作用)

class Stack

而在new对象时,以栈里只能存放整形为例,前面的尖括号必须写基本数据类型对应的包装类,而后面的尖括号可以不用写,示例如下:

Stack stack = new Stack<>();

补一下Java中的基本数据类型与对应的包装类:

因此,我们前面写的自定义的栈可以写成以下形式(以存放整形为例):

class Stack {

public T[] objects;

public int top;

public Stack() {

this.objects = (T[])new Object[10];

}

public void push(T obj) {

objects[this.top++] = obj;

}

public T get() {

return objects[this.top-1];

}

}

Stack stack = new Stack<>();

stack.push(1);

stack.push(2);

int ret = stack.get();

System.out.println(ret);

特别注意此处:public Stack() { this.objects = (T[])new Object[10]; }

这里不能写成this.objects=new T[10];

原因:

1. 不能new泛型类型的数组

2. 也可理解为泛型是先检查后编译的,如果new泛型类型的数组的话,编译器检查时并不知道T是什么类型的,因此会报错。而编译的时候才会进行擦除机制fVHEapdQ,都会将其转换为Object类型

3. 正因为有这个擦除机制,这里才能进行数组整体强制类型转换(一般数组不能整体进行强制类型转换),因为泛型只是在编译的时候起作用,而实际运行时都会被擦除成Object类型,即实际运行时是没有泛型这个概念的,也即实际运行时类型都是一样的,所以T本质上是object类型的,所以此代码等价于不进行强制类型转换!!!

4.而直接指定泛型的代码(不是T) 比如:Stack和Stack都是在运行时直接把尖括号里的类型擦掉了,可以看到直接打印的结果(并没有打印出类型):

此处注意多理解理解

四. 泛型的使用实例

1. 求最大值

以上就是泛型的一个重要知识点了,但光看是不够的,还是得通过例子让大家有一个更为深入的理解,比如,如何写一个泛型类来求数组的最大值呢?

基本的框架大概是这样的:(没看懂的小可爱好好看看上面讲的内容哦)

class Algorithm> {

public T findMax(T[] array) {

T max = array[0];

for (int i = 1; i < array.length; i++) {

if(max < array[i]) {

max = array[i];

}

}

return max;

}

}

但是此代码if(max < array[i])会报错,为什么呢?因为将来给T传的值一定是一个引用类型,引用类型不能直接比较大于或者小于的,是要用Comparable或Comparator接口里的方法比较的,因为泛型在编译的时候会被擦除成Object类型,但Object类本身并没有实现Comparable或Comparator接口,所以我们要控制其不要擦除到Object类,所以要给泛型指定一个边界

具体写法如下:

class Algorithm> {

public T findMax(T[] array) {

T max = array[0];

for (int i = 1; i < fVHEapdQarray.length; i++) {

//max < array[i]

if(max.compareTo(array[i]) < 0) {

max = array[i];

}

}

return max;

}

}

class Algorithm>

注意,extends叫做上界,此代码代表的意思为T这个泛型类会擦除到实现了Comparable接口的地方,换句话说,这个T类型一定是实现了Comparable接口的

同理:这个代码public class MyArrayList { ... }代表E为Number的子类或Number本身

下面让我们来用一下:

Algorithm algorithm1 = new Algorithm<>();

Integer[] integers = {1,2,13,4,5};

Integer ret = algorithm1.findMax(integers);

System.out.println(ret);

运行结果如下:

成功了!

2. 优化

经过上面的努力,我们已经写出了一个泛型类来求一个数组的最大值了,但是,上面的例子是一个整形数组,那么我们能不能在数组里存放别的类型去比较呢?答案是可以的,但是我们还得去new一个对象,例如:Algorithm algorithm2 = new Algorithm<>();这样很麻烦。但是我们可以将求最大值的方法设置成静态的class Algorithm2 ,因为是静态的方法,不需要new对象,所以就没有在new对象时指定泛型的过程了,所以没必要给方法后加尖括号,但是去掉之后,代码又会被错:

我们可以这样修改:

class Algorithm2 {

public static> T findMax(T[] ahttp://rray) {

T max = array[0];

for (int i = 1; i < array.length; i++) {

if(max.compareTo(array[i]) < 0) {

max = array[i];

}

}

return max;

}

}

此方法public static> T findMax(T[] array){}叫做泛型方法

下面继续带大家来用一下:

public static void main(String[] args) {

Integer[] integers = {1,2,13,4,5};

//会根据形参的类型推导出整个泛型的类型参数

Integer ret = Algorithm2.findMax(integers);

System.out.println(ret);

Integer ret2 = Algorithm2.findMax(integers);

System.out.println(ret2);

}

注意,ret1写法和ret2写法是一样的,都可以

打印结果如下:

五. 通配符

1. 基本写法

通配符也是泛型的一种,下面我们来写一个泛型方法来打印集合中的元素

class Test {

public static void print(ArrayList list) {

for (T t : list) {

System.out.println(t);

}

}

这个写法很简单,上文都讲过了,那么让我们来试着用一下吧:

public static void main(String[] args) {

ArrayList list = new ArrfVHEapdQayList<>();

list.add(1);

list.add(2);

list.add(3);

Test.print(list);

}

打印的结果如下:

除了以上这种写法,我们还可以将其改成通配符的写法,先给大家上代码:

//?代表通配符 擦除机制 Object

public static void print2(ArrayList> list) {

for (Object t : lhttp://ist) {

System.out.println(t);

}

}

}

此处for (Object t : list)必须这样写,因为通配符也是有擦除机制的,会在编译器编程Object类型。

2. 上界

语法: extends 上界>

示例:

public static void printAll(MyArrayList extends Number> list) {

...

}

代表可以传入类型实参是 Number 子类的任意类型的 MyArrayList

所以以下调用都是正确的:

printAll(new MyArrayList());

printAll(new MyArrayList());

printAll(new MyArrayList());

以下调用都是错误的:

printAll(new MyArrayList());

printAll(new MyArrayList());

3. 下界

下界和上界的用法很类似

语法: super 下界>

示例:

public static void printAll(MyArrayList super Integer> list) {

...

}

代表可以传入类型实参是 Integer 父类的任意类型的 MyArrayList

所以以下调用是正确的:

printAll(new MyArrayList());

printAll(new MyArrayList());

printAll(new MyArrayList());

以下调用是错误的:

printAll(new MyArrayList());

printAll(new MyArrayList());

六. 泛型的限制

学习完后,我们应该注意泛型使用过程中以下一些限制:

泛型类型参数不支持基本数据类型

无法实例化泛型类型的对象

无法使用泛型类型声明静态的属性

无法使用 instanceof 判断带类型参数的泛型类型(因为被擦除机制擦除了)

无法创建泛型类数组

无法 create、catch、throw 一个泛型类异常(异常不支持泛型)

泛型类型不是形参一部分,无法重载

好啦,本次泛型知识点的分享就先告一段落了,整理不易,但如果能帮到大家很开心了。也希望大家多理解理解,不论是刚开始学习还是复习,都值得仔细揣摩哦!一起加油吧!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringBoot模拟员工数据库并实现增删改查操作
下一篇:SpringBoot整合Swagger2的完整过程记录
相关文章

 发表评论

暂时没有评论,来抢沙发吧~