什么是泛型
官方解释是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
通俗来讲,在Java中,泛型本质上就是java基础数据类型、数组、对象等数据类型的==类型占位符==,常见泛型占位符参数名称有如下(以下几种方式只是约定俗成的几种定义参数的占位符,如果你指定xxx<T> 中的T占位符表示基本数据类型也无错):
E: Element (在集合中使用,因为集合中存放的是元素) eg:ArrayList<E>、HashSet<E>...
T:Type(Java 类)eg:Class<T>
K: Key(键)eg:Map<K,V>
V: Value(值)eg:Map<K,V>
N: Number(数值类型)
?: 表示不确定的java类型
在工作中,一般使用泛型是在定义通用类、通用接口、通用方法的时候使用,如果你喜欢查阅一些开源框架的源码,泛型这种定义数据类型方式,最好是了解清楚比较好。
泛型类
把泛型定义在类上。
定义格式:public class 类名<泛型占位符1,泛型占位符2,...>{...} //占位符可以有多个,以逗号分隔
eg:
@Data
public class Person<T,N>{ //占位符名称一般为大写英文字母,可以任意,但一般我们开发都有开发规范,最好还是按照上面常见占位符所表示的类型设计
private T name;
private N age:
}
public static void main(String[] args) {
Person<String,Integer> p = new Person<>();//创建对象的时候指定参数类型后,在相关类中的参数类型就关联上指定类型了
p.setName("hw");//在此,Person类中泛型 T 指定的参数类型就是String
p.setAge(18); // 泛型 N 的参数类型就是Integer
System.out.println(名字:+ p.getName());
System.out.println(年龄:+ p.getAge());
}
输出:
名字:hw
年龄:18
泛型方法
简单来说就是将泛型定义在方法上。
泛型方法分为普通泛型方法和静态泛型方法,不同的方法使用泛型有一些区别。
普通方法可以将泛型指定在类上,方法引用类泛型。
eg:
public class person<T> {
public void test(T info){
System.out.println( “内容是:” + info);
}
//方法可以直接引用类的泛型,也可以单独定义
public <W> void test2(W info){
System.out.println( “内容是:” + info);
}
//但是static静态方法无法直接引用类泛型,需要重新定义泛型
public static <E> E test2(E info){
System.out.println( “内容是:” + info);
return info;
}
}
泛型接口
简单来说就是将泛型定义在接口上。
eg:
public interface Information<T> {
void test(T info);
}
1. 泛型接口的实现类可以指定泛型接口的具体泛型类型
public class InformationImpl implements Information<String>{//在此指定的具体泛型类型是String类型
@Override
public void test(String info){
System.out.println(info);
}
}
2.泛型接口的实现类如果没有指定具体的泛型类型,必须要在这个实现类中声明一个泛型类型占位符给接口用
public class InformationImpl<E> implements Information<E>{//在此指定的具体泛型类型是String类型
@Override
public void test(E info){
System.out.println(info);
}
}
擦除模式
实质就是在代码运行期间将所有的泛型全部都去掉。
Java中的泛型只存在于编码编译阶段,是为了兼容jdk老版本的编码。
eg:
定义一个泛型类Person<T>
Person<String> p1 = new Person<String>();
Person<Integer> p2 = new Person<Integer>();
System.out.printIn(p1.getClass() == p2.getClass());
输出:
true
通配符
由于Java中继承关系,在泛型中不做任何声明修饰的情况下是不被认可的,所以需要使用通配符进行处理,用通配符在泛型中将原父子类继承关系重新绑定。
通配符一般用?
来表示,可以理解为?
在泛型中是所有类型的父类型
Java中的继承关系并不能作用于泛型,也就是说Java中的父子类关系,在同一个泛型类中并不被承认。
public class Person<T> {
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public void info(Person<T> per){
this.setName(per.getName());
}
}
public static void main(String[] args) {
Person<Number> p1 = new Person<>();
Person<Integer> p2 = new Person<>();
p2.setName(111);
p1.info(p2);//Number和Integer类型在Java中是父子类的继承关系,但是在此处会报错,泛型并不承认Java中的继承关系
* //此时将Person类的info方法中的输入参数的泛型类型改为 `?` 就不会报错了
* public void info(Person<?> per){
this.setName(per.getName());
}
}
通配符的上边界、下边界
**在上面的例子中,直接将?
作为通配符是有问题的,因为‘?’在泛型中是所有类型的父类,因此就算p1是Number类型,但是将p2设置为string类型仍然可以将p2赋值给p1,这样的话各种类型之间没有声明继承关系,使类型之间关系混乱。
这种情况下,我们可以使用通配符的上、下边界来限定泛型各类型之间的继承关系
通配符的下边界使用super关键字来表示
通配符的上边界使用extends关键字来表示
eg:
/*
* ? extends T 表示可以传入类型 T 和 类型 T ==子==类的类型
*/
public void info(Person<? extends T> per){
this.setName(per.getName());
}
/*
* ? super T 表示泛型可以传入类型 T 和 类型 T ==父类==的类型
*/
public void info(Person<? super T> per){
this.setName(per.getName());
}
/*
* 在什么时候使用上边界 什么时候使用下边界
* 上边界 在读取 T 这个类型,但不写入数据的时候
* 下边界 在需要写入数据,但不需要读取的时候
*/