什么是泛型
泛型就是参数化类型
,也就是把所操作的类型看做为一个参数。
为什么需要泛型
-
在有泛型之前,我们可以创建一个存储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 类名<泛型标识> { }
- 泛型类例子:
/**
* @Author GJY
* @Date 2021/7/18 22:22
* @Version 1.0
* T 当我们创建GenericClass实例对象需要指定的具体数据类型
*/
public class GenericClass<T> {
//name这个成员变量的类型为T,T的类型由我们实例化对象时指定
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public static void main(String[] args) {
GenericClass<String> demo1 = new GenericClass<>();
demo1.setName("不喝奶茶的Programmer");
System.out.println(demo1.getName());
GenericClass<Integer> demo2 = new GenericClass<>();
demo2.setName(212);
System.out.println(demo2.getName());
}
}
泛型类派生子类
-
如果继承于父类(泛型类)的子类是泛型类,那么子类和父类的泛型类型需一致。
class children<T> extends GenericParent<T> { }
-
如果继承于父类(泛型类)的子类为非泛型类,那么在子类在声明继承父类时必须明确指定父类的泛型类型。
class children extends GenenricParent<String> { }
泛型方法
泛型类是我们在实例化类时指明泛型的具体类型,而泛型方法就是我们在调用方法时指明泛型的具体类型,泛型方法能使方法独立于类而产生变化。
泛型方法的定义
[访问修饰符] <T,E...> 返回值类型 方法名称 (形参列表) { }
注意
- 只有在方法的访问修饰符后面加了<T,E..>泛型标识才能标识该方法为一个泛型方法,比如上面泛型类中的
public T getName() { return name; }
并不是泛型方法。
泛型方法例子:
public class GenericClass<T> {
//使用泛型方法后,该方法就可以接收任何数据类型的参数
//<T> 泛型标识,具体类型由调用该方法时具体指定
public static <T> void genericMethod(T birthday){
System.out.println(birthday);
}
//采用多个泛型类型
public static <T,V,E> void genericMethod2(T t,V v,E e){
System.out.println(t+"-"+v+"-"+e);
}
//泛型可变参数的定义
public static <T> void GenericVariadic(T...t){
for (int i = 0; i < t.length; i++) {
System.out.println(t[i]);
}
}
public static void main(String[] args) {
GenericClass.genericMethod(new Date());
GenericClass.genericMethod("1999-12-31");
//调用多个泛型类型的泛型方法
GenericClass.genericMethod2("不喝奶茶的Programmer",212,110);
//泛型可变参数
GenericClass.GenericVariadic(1,2,3,4,5,"4");
}
}
泛型接口
泛型接口的定义
interface 接口名称 <泛型标识,泛型标识....> {
泛型标识 方法名称();
}
-
对应的实现
泛型接口
的实现类
的定义:class 实现类名称 [<泛型标识>] implements 接口名称 <泛型标识> { }
注意
-
如果实现泛型接口的实现类不是泛型类,则在声明实现泛型接口时必须明确指定泛型接口的泛型类型。
class interfaceImpl implements GenericInterface<String> { }
-
如果实现泛型接口的实现类是泛型类,则实现类的泛型类型必须包含有接口泛型类的泛型类型。
class interfaceImpl<T,其他泛型标识...> implements GenericInterface<T> { }
泛型接口例子:
//泛型接口
public interface GenericInterface<T> {
T method(T e);
}
- 实现类为非泛型类
/**
* @Author GJY
* @Date 2021/7/18 22:46
* @Version 1.0
* 实现类不是泛型类,在声明实现接口时则必须明确指定接口的泛型类型
*/
public class GenericInterfaceImpl implements GenericInterface<String> {
@Override
public String method(String e) {
return e;
}
public static void main(String[] args) {
GenericInterfaceImpl gis = new GenericInterfaceImpl();
gis.method("不喝奶茶的Programmer");
}
}
- 实现类为泛型类
/**
* @Author GJY
* @Date 2021/7/18 22:46
* @Version 1.0
* 实现类为泛型类,则实现类的泛型类型必须包含有接口泛型类的泛型类型
*/
public class GenericInterfaceImpl<T,V> implements GenericInterface<T> {
T name;
V id;
public GenericInterfaceImpl(T name, V id) {
this.name = name;
this.id = id;
}
@Override
public T method(T e) {
return e;
}
public static void main(String[] args) {
GenericInterfaceImpl<String, Integer> gis =
new GenericInterfaceImpl<>("不喝奶茶的Programmer",212);
gis.method("不喝奶茶的Programmer");
}
}
泛型通配符
泛型通配符一般使用“ ?”
替代具体的类型实参。
问题引入
我们都知道Interger是Number的子类,那么在Generic<Number>
作为形参的方法中,能否使用Generic<Ingeter>
的实例传入呢? 我们测试一下:
- 首先我们先定义一个Generic泛型类
public class Generic<T> {
T element;
public T getElement() {
return element;
}
public void setElement(T element) {
this.element = element;
}
@Override
public String toString() {
return element +"";
}
}
- 声明一个传入形参为
Generic<Number>
的方法,并测试当调用该方法时传入Generic <Intege>
类型实参的情况
public class GenericTest {
public static void methodTest(Generic<Number> gn){
System.out.println(gn);
}
public static void main(String[] args) {
Generic<Integer> gi = new Generic<>();
gi.setElement(212);
GenericTest.methodTest(gi);//报错
}
很显然,当我们在形参声明为Generic<Number>
类型的方法中,在调用该方法时传入一个Generic<Integer>类型的实参
时,会编译不通过,即使Integer是Number的子类。
无限通配符
- 这时,无限通配符
“ ?”
的用武之地就来了,使用Generic<?>
来替换掉方法定义时形参中的Generic<Number>
,就可以在调用该方法时传入任意类型的数据。修改后正常执行,如下:
public class GenericTest {
public static void methodTest(Generic<?> gn){
System.out.println(gn);
}
public static void main(String[] args) {
Generic<Integer> gi = new Generic<>();
Generic<String> gs = new Generic<>();
gi.setElement(212);
gs.setElement("不喝奶茶的Programmer");
GenericTest.methodTest(gi);
GenericTest.methodTest(gs);
}
}
上限通配符<? extends T>
上限通配符的定义
类/接口< ? extends 实参类型>
要求该泛型的类型只能是实参类型或者实参类型的子类
- 有了上限通配符,我们就使用其来解决上述问题,特别适用于父子类继承关系(Integer的父类是Number),因此,我们可以将上限通配符中的实参类型设置为Number类型,那么该泛型类型(即 ?)只要是Number的本身或者Number的子类就都可以。使用如下:
public class GenericTest {
public static void methodTest(Generic<? extends Number> gn){
System.out.println(gn);
}
public static void main(String[] args) {
//泛型类型为Integer,为Number的子类
Generic<Integer> gi = new Generic<>();
gi.setElement(212);
GenericTest.methodTest(gi);
}
}
下限通配符<? super T>
下限通配符要求该泛型的类型只能是实参类型或者实参类型的父类类型
下限通配符的定义
类/接口< ? super 实参类型>
- 示例如下:
public class GenericTest {
//该方法只能接收泛型类型为Number或Number的父类的实参
public static void methodTest(Generic<? super Number> gn){
System.out.println(gn);
}
public static void main(String[] args) {
Generic<String> gs = new Generic<>();
Generic<Object> gi = new Generic<>();
Generic<Number> gn = new Generic<>();
gi.setElement(212);
GenericTest.methodTest(gs);//报错
GenericTest.methodTest(gi);//Object是Number的父类,编译通过
GenericTest.methodTest(gn);//实参为是Number类型,编译通过
}
}
泛型擦除
泛型是JDK1.5引进的,在这之前是没有泛型的,但是,泛型代码能够很好地和之前的代码兼容,那么因为,泛型的信息只存在于代码的编译器,在进入JVM之前,与泛型相关的信息会被擦除,这就是泛型擦除。
初见端倪
运行以下代码,我们可以发现,我们定义的存放String和Integer类型的两个ArrayList集合intList和StrList是同一个ArrayList,那是因为在运行时我们传入的类型变量String和Integer都被擦除了。
public static void main(String[] args) {
ArrayList<Integer> intList = new ArrayList<>();
ArrayList<String> StrList = new ArrayList<>();
System.out.println(intList.getClass().getSimpleName());//ArrayList
System.out.println(StrList.getClass().getSimpleName());//ArrayList
System.out.println(intList.getClass() == StrList.getClass());//true
}
无限制擦除
无限制擦除,就是无论你给我的泛型类型是什么,Java虚拟机会都将你的类型擦除掉变为Object类型
- 首先,还是我们自定义的一个泛型类Generic,当我们实例化一个类型为String的Generic时,编译后它的成员变量还会String类型吗,接下来我们通过反射来一探究竟。
public class Generic<T> {
T element;
public T getElement() {
return element;
}
public void setElement(T element) {
this.element = element;
}
@Override
public String toString() {
return element +"";
}
public static void main(String[] args) {
Generic<String> gs = new Generic<>();
//通过反射获取Generic类的Class对象
Class<? extends Generic> cs = gs.getClass();
//获取Generic类的所有成员变量
for (Field declaredField : cs.getDeclaredFields()) {
//输出:element:class java.lang.Object
System.out.println(declaredField.getName()+":"+declaredField.getType());
}
}
}
- 通过运行结果我们惊奇的发现成员变量element的类型被擦除变为了Object类型,因此无论我们定义的泛型类的数据类型是什么,Java虚拟机会都将其擦除为Object类型。
有限制擦除
编译后都将其擦除变为上限类型
public class Generic<T extends Number> {
T element;
public T getElement() {
return element;
}
public void setElement(T element) {
this.element = element;
}
@Override
public String toString() {
return element +"";
}
public static void main(String[] args) {
Generic<Integer> gs = new Generic<>();
//通过反射获取Generic类的Class对象
Class<? extends Generic> cs = gs.getClass();
//获取Generic类的所有成员变量
for (Field declaredField : cs.getDeclaredFields()) {
//输出:element:class java.lang.Number
System.out.println(declaredField.getName()+":"+declaredField.getType());
}
}
}
擦除方法中类型定义的参数
同样的泛型擦除也可以作用域泛型方法中
public class Generic<T extends Number> {
T element;
//定义一个泛型方法,泛型上限为Number
public <T extends Number> T method(T value){
return value;
}
public T getElement() {
return element;
}
public void setElement(T element) {
this.element = element;
}
@Override
public String toString() {
return element +"";
}
public static void main(String[] args) {
Generic<Integer> gs = new Generic<>();
//通过反射获取Generic类的Class对象
Class<? extends Generic> cs = gs.getClass();
//获取Generic类的所有成员方法
for (Method declaredMethod : cs.getDeclaredMethods()) {
//if(declaredMethod.getName()=="method")
//打印方法的名称和返回值类型
System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType());
}
}
}
- 在上面的例子中,我们定义了一个
Integer类型的Generic类
,经过编译后成员方法method()以及getElement()的返回值类型被擦除变为泛型的上限Number
.
桥接方法
首先我们定义一个Info泛型接口包含method()方法,然后InfoImpl实现Info接口并指定泛型接口的泛型类型为Integer并重写接口中的method()方法,通过反射查看编译后InfoImpl实现类中的方法有哪些及其返回值类型。
public interface Info<T> {
T method(T t);
}
public class InfoImpl implements Info<Integer> {
@Override
public Integer method(Integer i) {
return i;
}
public static void main(String[] args) {
//获取泛型接口Info的实现类InfoImpl的字节码
Class<InfoImpl> infoClass = InfoImpl.class;
//获取所有方法
Method[] mes = infoClass.getDeclaredMethods();
for (Method method : mes) {
//输出实现类中的方法名称和返回值类型
if(method.getName()!="main")
System.out.println(method.getName() + ":" + method.getReturnType());
}
}
}
程序输出:
method:class java.lang.Integer
method:class java.lang.Object
运行以上main方法后,我们可以发现InfoImpl实现类中出现了两个名称一样但返回值类型和参数列表的类型都不同
的method方法。这是为什么呢,明明在实现类中只有重写了一个method方法?其实其中返回值类型和参数类型为Object的method()
是Java虚拟机会帮我们添加的,该方法就是一个桥接方法。因为我们定义的Integer类型的泛型接口Info编译后会被擦除变为Object类型,而我们的实现类InfoImpl为了保证我们对接口实现的规范和约束,所以Java 虚拟机会帮我们在实现类中生成一个返回值类型和参数类型为Object的method()方法,从而保持接口和类的实现关系。
泛型与数组
创建一个泛型数组有以下规范:
- 可以声明带泛型的数组引用,如
ArrayList<String> [] listArray;
,但是不能直接创建带泛型的数组对象,如ArrayList<String> [] listArray = new ArrayList<String>[2];
因此,我们可以通过以下方式创建泛型数组:
//创建一个泛型数组
ArrayList<Integer> [] arrayLists = new ArrayList[5];
//创建一个存入泛型数组的泛型集合ArrayList
ArrayList<Integer> list = new ArrayList<>();
list.add(212);
//将泛型集合存入到泛型数组中
arrayLists[0]=list;
//获取
Integer i = arrayLists[0].get(0);
System.out.println(i);//212
以上就是对Java中泛型知识点的总结,掌握好泛型的使用,可以提高我们代码的重用率也通过消除了强制的类型转换可以提高我们程序的安全性。
最后
最近我整理了整套《JAVA核心知识点总结》,说实话 ,作为一名Java程序员,不论你需不需要面试都应该好好看下这份资料。拿到手总是不亏的~我的不少粉丝也因此拿到腾讯字节快手等公司的Offer
进【Java进阶之路群】,找管理员获取哦-!