Java泛型
Java泛型是我们平常开发中经常遇到的,尤其是在一些框架中。使用泛型,我们可以写出通用的代码,来适配各种环境,例如可以通过泛型编码一个方法或类,使其可以接受不同类型的参数,而不需要编写各种重载方法等。
泛型的使用
泛型有两种使用场景,一种是泛型方法,一种是泛型类,在使用的时候通过<T>类进行声明,其中T可以换成任意字母或单词,大小写都可以,按照习惯都会将其声明为大写字母。
1 | |
如上是泛型方法的定义,即在方法的返回值前面通过<T>来声明一个泛型类型,然后在方法的参数列表和方法体中,都可以将T当成一个具体的类来进行使用。
1 | |
由于test方法的参数是一个泛型类型T,并未指定具体类型,所以在调用时可以传入任何类型的参数而不会报错。如果要使用多个泛型参数,则使用逗号隔开,如上面的test1方法,就声明了两个泛型类型K和V。
泛型类也是一样的,泛型类是在声明类时设置泛型类型,此时该类型可以在类中的任何地方使用。
1 | |
相比于泛型方法,泛型类将泛型提取到了类的声明上去,跟在类名的后面,因此它的使用范围更大,可以在类中的任何地方使用泛型类型。而为了保证类中所有的方法使用的泛型是相同的,就需要在实例化类时就指明泛型的类型,这样在这个实例化的对象中才能保证统一。
1 | |
泛型上下限
在使用泛型类时,还有一种方法是通过通配符?来指明这个类的泛型是不可知的,这样我们就无法直接使用泛型,通过这种方式可以保证数据只能流出而无法流入。
1 | |
例如上面这个就是一个普通的泛型类,有一个属性为泛型类型T,两个方法一个是set方法一个get方法。正常我们使用就是通过具体类型实例化一个对象,然后就可以给他设置参数和获取参数了。
1 | |
当通过?来使用泛型时,意思就是不指定泛型的具体类型,因此无法调用set方法,因为这个方法的参数要求是泛型类型,而我们不知道是啥,因此无法传参。但是我们却可以访问get方法,只需要通过Object来进行引用即可。
那么通过?来使用泛型的应用场景呢?我们看下上面的实例就知道,这种使用方式限定了泛型类型的数据无法流入而只能流出,这就是使用场景。即我有一个demo实例,我想给你使用,但是我不想让你修改。
1 | |
如上,我在main中有一个泛型类型为String的实例化对象demo,然后给他设置了参数。然后想给别人使用,但是不想给别人修改,就可以在useDemo的参数列表中以Demo<?>的类型来声明。这样,在useDemo方法中就只能获取而不能设置了。
那么问题又来了,使用?之后就只能获取而不能设置了,但是获取的类型只能认为是Object类型,这样即使拿到数据作用也不大。而为了避免这个问题,就又将通配符?进一步细化,给它一个上限和下限,分别控制数据的流入和流出。
1 | |
在上面的代码中,泛型类型变成了<? extends String>,也就是说,泛型的这个类型虽然仍是不可知的,但是却可以知道它是String或者是String的子类,因此我们get方法获取到的泛型,可以直接通过String进行引用。这也就是上限,即限定了参数的Demo的泛型类型必须是String以及String的子类。
而有上限就必然有一个对应的下限,下限通过super关键字声明。
1 | |
方法的参数泛型变成了<? super String>,这句话的意思就是虽然泛型类型仍然是不可知的,但却可以知道它是String或者String的父类,因此可以继续往里面设置数据。
1 | |
我们来加深下理解,上限<? extends String>的意思就是这个泛型类型确定了一个上界,它只能是这个上界的子类型,因此我们可以直接可以通过Stirng来获取,但无法设置,因为我们不知道它的具体类型是什么。
而下限<? super A>的意思是泛型类型确定了下界,它只能是这个下界的父类型,因此我们不能通过String来引用获取到的数据,而只能通过Object来引用。但是我们却可以给他设置数据,只能设置String或者它的父类。
注意:extends关键字不仅可以在通配符?后使用,也可以在任何泛型类和泛型方法中使用,但是super仅能在通配符?后使用。
1 | |
泛型继承
泛型仍然是支持继承和覆写的,对于泛型方法而言,继承后覆写仍是泛型方法,而泛型类却可以指定类型。
1 | |
如上面代码,在父类Parent中有一个泛型方法test,那么在子类Child中,如果选择覆写的话,它仍然只能是一个泛型方法,而不能改成具体类型。但是泛型类却不一样,他可以指定类型。
1 | |
现在Parent是一个泛型类了,那么在子类Child中,继承自Parent是可以选择给它指定一个类型的,如果指定了类型,这里指定了String类型,当然也可以选择不指定,仍使用泛型类型。但是当指定了类型后,如果要重写它的方法时,必须要将泛型类型替换成指定的类型String。
替换后,如果想用父类引用子类,则必须提供相同的泛型类型。
1 | |
泛型擦除
泛型是在JDK5中引入的,它并没有大量的修改字节码,而是通过编译期间进行适配,在编译期间将泛型类型移除,从而实现直接复用原来的字节码逻辑,减少了大量的修改工作。也就是说,泛型的类型检查实际上是在编译期间完成的,而非运行期间进行检查。
如下面的代码,是个泛型类,那么在编译完成之后,类中的所有的T都会被替换成Object,也就是如下所示:
1 | |
那么对于继承后指定类型的类,并且重写了带泛型的方法,则会额外生成一个指定类型的方法:
1 | |
总结
泛型在java中应用还是比较多的,尤其是各种集合结构中。泛型的使用中,主要需要注意的就是它的上下限,主要控制的就是数据的暴露问题。其次就是泛型擦除问题,虽然用不到,但是面试它就总是问这个,贼烦。
