Java 泛型接口,泛型类和泛型通配符

 2022-09-18
原文地址:https://cloud.tencent.com/developer/article/1456981

泛型的使用位置,除了最常见的约束集合元素,还可以使用在接口,类,方法上面。最本质的原因就是为了在使用接口,类,方法的时候,可以将类型作为参数,进行类型的参数传递。这样可以使程序的编写更加的灵活,在创建对象,调用方法的时候动态的指定类型,所以泛型也可以理解为类型的参数化。

类型参数化

光看名字,又不好理解,通俗点可以这样理解。定义方法,接口的时候可以传递参数,参数通常都是指定类型的,比如

public void add(Student student);

public void add (Teacher teacher);

这里的student是Student类型的参数,teacher是Teacher 类型的参数,他们已经指定好类型了。那么类型的参数化,就是指将类型作为参数传递进方法。比如

public void add(E e);

这里的add方法并没有指定任何一个具体的类型,而是将类型也作为了参数,E是任何一个类型,e是任意类型E的实例。如果传递进来的类型参数是Student,那么这个方法就是add(Student student);,如果传递进来的类型参数是Teacher ,那么这个方法就是add (Teacher teacher);

202209182104051981.png

类型参数化

当使用泛型定义参数的时候,每一个传递进来的类型参数,就创建了一个该方法的版本,add(Student student)是一个add(E e)的版本,add (Teacher teacher)也是一个add(E e)版本。类型参数化的好处是使代码变得更加灵活,原因就在于此,因为可以通过对类型的抽象,使代码匹配各种不同有具体类型版本的需求。

泛型接口和泛型类

泛型接口的定义,public interface man<T>\{...\}。在接口名后面加上泛型类型参数T,这样就定义了一个泛型接口。

202209182104071242.png

泛型接口

在接口中定义的类型参数可以在接口中当做类型使用,任何需要类型的地方都可以使用类型参数替代。比如传递的类型是Teacher,那么run(T t)就是老师在跑路,getObject()方法返回一个老师对象,getAll(String name)方法可以根据学校名字获取所有老师。加入传递的是Student,那么上面三个方法分别是学生在跑路,获取一个学生对象,根据学校名称返回所有学生。使用泛型接口,可以在实现的时候才定义具体需要实现的类型,使接口可以进行更高级的抽象。

泛型类的定义,public class Man<T>\{...\},在类名后面加上泛型类型参数T,这样就定义了一个泛型类。

202209182104089263.png

泛型类

和泛型接口不同,类有构造器,并且构造器也可以使用泛型类型参数。在这个泛型类里面,使用了两个泛型类型参数,如果有必要可以定义更多的泛型参数。

如果java里面没有继承这个特性,那么泛型到这里就讲完了,但是,正因为java有继承这个特性,会导致很多其他的问题出现,其复杂程度会几何级的上升,后面的知识点对抽象能力和思维能力有较高的要求,请做好战斗准备。

下面从集合开始,先来思考几个前面没有思考过的问题。

1.如果集合加上了泛型,那么如果添加的元素是泛型的子类或者父类能添加进去吗?

202209182104104024.png

添加子类父类

上面例子可以看出,如果泛型类型有子类,添加泛型类型的子类是可以的,但是如果泛型类型有父类,往集合添加泛型类型的父类会出现编译错误。因为子类继承了父类的所有方法,所以如果添加的是子类,当从集合取出的元素调用泛型类型的方法也不会有什么问题。但是如果定义的是子类的泛型集合,放入的是父类元素,当要使用子类方法的时候,父类元素可能没有,那么就会发生错误,所以泛型是子类型的话,是不允许加入父类型元素的。

2.再看另一个问题,如果父类是泛型类型,如何定义子类?

202209182104121825.png

泛型类的子类

如果将一个类定义为泛型类,那么在创建该泛型类的子类的时候不能将子类直接继承该泛型类,而是需要指定父类泛型的类型。比如父类是Book<T>,子类不能直接extends Book<T>,而是需要指定T的类型,上例中使用的Book<Double>作为类型。

在java中,泛型不能继承和实现。为什么?WHY?请手动滑动到本章最上面,跟我一起念,类型参数化。问题的关键就在这里,因为泛型将类型作为一种参数,而参数是什么?在定义方法的时候,他不需要具体指定是什么数据,但是一旦你调用使用这个方法,就必须指定这个参数具体是什么。

202209182104144506.png

使用泛型

由于方法中的泛型需要在定义类的时候就指定,所以如果需要使用含有泛型的方法,必须在创建该泛型类对象的时候就需要指定泛型类型,因为使用的时候必须指定类型,不论是普通参数还是泛型参数。那为什么继承的时候也要确定泛型呢?因为继承就是在使用一个已经定义好的类,使用泛型类,就要指定类型。

3.用什么样的参数形式来接受List<Book>这种形式的参数?

现在需要为所有List抽象一个方法,不论给的参数是List<Book>List<String>,都可以接收并且打印List中的元素。是不是理所当然的想到了List<Object>?List<Object>来接收参数就行了嘛。

202209182104170767.png

泛型类型不匹配

啪啪啪,脸是不是很疼。显然这样是不可以的,错误提示参数类型不匹配,Object是所有类型的父类,但是List<Object>并不是List<Book>的父类,那应该使用什么方法达到上面的要求呢?泛型提供了一个泛型通配符用于接收所有类型的泛型类型。

泛型的通配符

202209182104184788.png

通配符

泛型的通配符可以很好的解决所有泛型类型父类的问题,使用<?>来作为类或接口的泛型参数,这样就可以抽象出泛型类的父类。比如用List<?>可以看做所有List的父类,Set<?>可以看做所有Set的父类,使用?来表示一个未定义的类型,用来接受任何类型参数。

但是如果使用通配符,在部分功能上是会受到限制的。

1.只能通过Object遍历集合。在访问通配符泛型List<?>的时候,集合里的元素只能当做Object来访问,因为在定义的时候只是一个通配符,不是具体类型,所以不能进行类型转换只能作为Object访问。

2.不能使用add方法。List提供的add(E e)方法是需要指定类型的,这里不是E吗?这是个泛型类型啊?为什么要提供类型?因为这是定义,一旦要使用add(E e)方法,必须指定具体的类型。定义通配符以后,在使用通配符的方法里是不知道类型的,所以不能使用add方法。

202209182104205009.png

不能用add方法

就算是Object类型也不能使用add方法,为什么?假设可以添加,会发生什么问题?如果我使用List<Book>作为参数,传入到printAllObject方法,运行完打印元素的语句后,会往List<Book>类型的集合里面新增一个Object类型的对象,而Object又是Book类型的父类,上面说过,泛型类型的父类型元素不能添加到该集合,所以这里就算是Objcet类型也不能添加。所以使用泛型通配符的话,这个集合的作用就是使用Object类型来遍历它。

上面第二点,如果集合使用了泛型通配符,要往集合添加Object是不允许的,因为无论最后来的是什么类型,Object都是这个类型的父类,所以不允许添加Object类型。那么如果我可以保证添加一个元素,一定是泛型类型的子类,那么是不是可以添加元素了?这个问题就涉及到泛型通配符的上下限问题了。下章继续。

本章有很多类名称相同,但是内容不同,请在不同的包下进行操作。