什么是泛型
泛型就是
参数化类型,也就是把所操作的类型看做为一个参数。
为什么需要泛型
-
在有泛型之前,我们可以创建一个存储Object类型的集合,该集合可以存储任何数据类型对象(8种基本类型除外),在没有泛型的支持下,我们需要明确集合中存储的每一个元素的数据类型,否则,在程序中很容易出现ClassCastException类转换异常。
-
有了泛型之后,我们在创建一个集合的时候可以指定其存储的数据类型。如当我们创建一个只能存储String类型的ArrayList集合时,当我们往集合中添加Integer类型的数据时,就会编译不通过,保证了集合类型的安全,同时在取出集合数据时消除了类型的强制转换。
T、E、K、V的含义
本质上这些都是表示通配符,没什么区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。 通常情况下,T,E,K,V,?是这样约定的:
- ?表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值对中的Key Value
- E (element) 代表元素
泛型类
类是面向对象中最基础的元素,当我们在设计一个类时,不仅需要考虑类的属性和方法,还需要考虑类的
可扩展性,泛型就给我们提高类的可扩展提供了实现。通过泛型我们可以完成对一组类的操作对外开放相同的接口,通过传入不同的数据类型,可以对相应类型的数据进行操作。
泛型类的定义
[访问修饰符] class 类名<泛型标识> { }
- 泛型类例子:
泛型类派生子类
-
如果继承于父类(泛型类)的 子类是泛型类,那么 子类和父类的泛型类型需一致。
class childrenextends GenericParent { }
-
如果继承于父类(泛型类)的 子类为非泛型类,那么在子类在声明继承父类时 必须明确指定父类的泛型类型。
class children extends GenenricParent{ }
泛型方法
泛型类是我们在实例化类时指明泛型的具体类型,而泛型方法就是我们在调用方法时指明泛型的具体类型, 泛型方法能使方法独立于类而产生变化。
泛型方法的定义
[访问修饰符]
注意
-
只有在方法的访问修饰符后面加了
泛型标识才能标识该方法为一个泛型方法,比如上面泛型类中的 public T getName() { return name; }并不是泛型方法。
泛型方法例子:
泛型接口
泛型接口的定义
-
对应的实现
泛型接口的实现类的定义:class 实现类名称 [<泛型标识>] implements 接口名称 <泛型标识> { }
注意
-
如果实现泛型接口的 实现类不是泛型类,则在声明实现泛型接口时 必须明确指定泛型接口的泛型类型。
class interfaceImpl implements GenericInterface{ } -
如果实现泛型接口的
实现类是泛型类,则
实现类的泛型类型必须包含有接口泛型类的泛型类型。
-
class interfaceImplimplements GenericInterface { }
泛型接口例子:
- 实现类为非泛型类
- 实现类为泛型类
泛型通配符
泛型通配符一般使用
“ ?”替代具体的
类型实参。
问题引入
我们都知道Interger是Number的子类,那么在
Generic作为形参的方法中,能否使用
Generic的实例传入呢? 我们测试一下:
- 首先我们先定义一个Generic泛型类
-
声明一个传入形参为
Generic的方法,并测试当调用该方法时传入Generic类型实参的情况
很显然,当我们在形参声明为
Generic类型的方法中,在调用该方法时传入一个
Generic时,会编译不通过,即使Integer是Number的子类。
无限通配符
-
这时,无限通配符
“ ?”的用武之地就来了,使用Generic>来替换掉方法定义时形参中的Generic,就可以在调用该方法时传入任意类型的数据。修改后正常执行,如下:
上限通配符 extends T>
上限通配符的定义
类/接口< ? extends 实参类型>
要求该泛型的类型只能是 实参类型或者 实参类型的子类
- 有了上限通配符,我们就使用其来解决上述问题,特别适用于父子类继承关系(Integer的父类是Number),因此,我们可以将上限通配符中的实参类型设置为Number类型,那么该泛型类型(即 ?)只要是Number的本身或者Number的子类就都可以。使用如下:
下限通配符 super T>
下限通配符要求该泛型的类型只能是 实参类型或者 实参类型的父类类型
下限通配符的定义
类/接口< ? super 实参类型>
- 示例如下:
泛型擦除
泛型是JDK1.5引进的,在这之前是没有泛型的,但是,泛型代码能够很好地和之前的代码兼容,那么因为,泛型的信息只存在于代码的编译器,在进入JVM之前,与泛型相关的信息会被擦除,这就是泛型擦除。
初见端倪
运行以下代码,我们可以发现,我们定义的存放String和Integer类型的两个ArrayList集合intList和StrList是同一个ArrayList,那是因为在运行时我们传入的类型变量String和Integer都被擦除了。
无限制擦除
无限制擦除,就是无论你给我的泛型类型是什么,Java虚拟机会都将你的类型擦除掉变为Object类型
- 首先,还是我们自定义的一个泛型类Generic,当我们实例化一个类型为String的Generic时,编译后它的成员变量还会String类型吗,接下来我们通过反射来一探究竟。
- 通过运行结果我们惊奇的发现成员变量element的类型被擦除变为了Object类型,因此无论我们定义的泛型类的数据类型是什么,Java虚拟机会都将其擦除为Object类型。
有限制擦除
编译后都将其擦除变为上限类型
擦除方法中类型定义的参数
同样的泛型擦除也可以作用域泛型方法中
-
在上面的例子中,我们定义了一个
Integer类型的Generic类,经过编译后成员方法method()以及getElement()的返回值类型被擦除变为泛型的上限Number.
桥接方法
首先我们定义一个Info泛型接口包含method()方法,然后InfoImpl实现Info接口并指定泛型接口的泛型类型为Integer并重写接口中的method()方法,通过反射查看编译后InfoImpl实现类中的方法有哪些及其返回值类型。
运行以上main方法后,我们可以发现InfoImpl实现类中出现了两个
名称一样但返回值类型和参数列表的类型都不同的method方法。这是为什么呢,明明在实现类中只有重写了一个method方法?其实其中
返回值类型和参数类型为Object的method()是Java虚拟机会帮我们添加的,该方法就是一个桥接方法。因为我们定义的Integer类型的泛型接口Info编译后会被擦除变为Object类型,而我们的实现类InfoImpl为了保证我们对接口实现的规范和约束,所以Java 虚拟机会帮我们在实现类中生成一个返回值类型和参数类型为Object的method()方法,从而
保持接口和类的实现关系。
泛型与数组
创建一个泛型数组有以下规范:
-
可以声明带泛型的数组引用,如
ArrayList,但是不能直接创建带泛型的数组对象,如[] listArray; ArrayList[] listArray = new ArrayList [2];
因此,我们可以通过以下方式创建泛型数组:
以上就是对Java中泛型知识点的总结,掌握好泛型的使用,可以提高我们代码的重用率也通过消除了强制的类型转换可以提高我们程序的安全性。
最后
最近我整理了整套 《JAVA核心知识点总结》,说实话 ,作为一名Java程序员,不论你需不需要面试都应该好好看下这份资料。拿到手总是不亏的~我的不少粉丝也因此拿到腾讯字节快手等公司的Offer
进 【 Java进阶之路群】,找管理员获取哦-!