什么是泛型
Java泛型设计原则: 只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.
泛型: 把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
一些常用的泛型类型变量:
E:元素(Element)
K:关键字(Key)
T:类型(Type)
V:值(Value)
参数化类型: 在不创建新类型的情况下,通过泛型指定的不同类型(类型形参),调用时传入具体的类型(类型实参)。(数据类型只能为引用类型)
为什么要用泛型
在泛型没有诞生之前,Java用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全
ArrayList list = new ArrayList();
list.add("Java");
list.add(123);
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println(str);
}
能通过编译,但是运行时会报ClassCastExecption
泛型优点:
安全: 不用担心程序运行过程中出现类型转换的错误。
避免了类型转换: 如果是非泛型,获取到的元素是 Object 类型的,需要强制类型转换。
可读性高: 编码阶段就明确的知道集合中元素的类型。
泛型基础
在使用泛型过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类
public class ObjectClass<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
ObjectClass<String> objectClass = new ObjectClass<>();
ObjectClass.setData("Generic Class");
System.out.println(ObjectClass.getData());
}
}
泛型接口
public interface InterDemo<T> {
T getData();
}
子类明确泛型类的类型参数变量
public class InterDemoImpl implements InterDemo<String> {
@Override
public String getData() {
return "子类明确泛型类的类型参数变量";
}
public static void main(String[] args) {
InterDemoImpl interDemoImpl = new InterDemoImpl ();
System.out.println(InterDemoImpl .getData());
}
}
子类不明确泛型类的类型参数变量
public class InterDemoImpl <T> implements InterDemo<T> {
private T data;
private void setData(T data) {
this.data = data;
}
@Override
public T getData() {
return data;
}
public static void main(String[] args) {
InterDemoImpl <String> interDemoImpl = new InterDemoImpl <>();
InterDemoImpl .setData("子类不明确泛型类的类型参数变量");
System.out.println(InterDemoImpl .getData());
}
}
泛型方法
在方法中使用泛型表示,对参数类型不作强制要求。
public class ToolDemo{
public <T> void method(T t){
System.out.println(t);
}
}
public class TestDemo{
public static void main(String[] args){
ToolDemo tool = new ToolDemo();
tool.method("我是字符串");
tool.method(123);
tool.method('A');
}
}
<?>通配符
<?>用于承接Object,及所有对象型。不知道使用什么类型接收数据时,使用?通配符。
此时只能接收数据,不能往该集合中存储数据。
常规写法
public <T> void method(List<T> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
通配符
public void method(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
通配符界限
泛型不是协变的
在 Java 语言中,数组是协变的,也就是说,如果 Integer 扩展了 Number,那么不仅 Integer 是 Number,而且 Integer[] 也是 Number[],在要求 Number[] 的地方完全可以传递或者赋予 Integer[]。(更正式地说,如果 Number是 Integer 的超类型,那么 Number[] 也是 Integer[]的超类型)。但是在泛型类型中 List< Number> 不是 List< Integer> 的超类型,也就是说在需要 List< Number> 的地方不能传递 List< Integer>。
上限通配符
<? extends T>:是 “上限通配符”,泛型只能是T的子类/本身
只接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte】
设置的上限为Number,添加String类型,超过了上限,就会报错。
设置的上限为Banana类,Banana的父类Fruit、Foot超过了上限,所以报错。
下限通配符
<? super T>:是 “下限通配符”,泛型只能是T的子类/本身
TreeSet集合中,采用的就是下限通配符
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
设置的下限为Banana类,Banana的子类BigBanana达不到下限,所以报错。
PECS法则:
HashMap< T extends String>;
HashMap< ? extends String>;
HashMap< T super String>;
HashMap< ? super String>;
从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;
从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;
如果既要存又要取,那么就不要使用任何通配符。
即
参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法;
参数之间没有依赖关系,就使用通配符,通配符会灵活一些。
泛型擦除
在泛型类被类型擦除的时候,如果类型参数部分没有指定上限,如 会被转译成普通的 Object 类型,如果指定了上限,则类型参数被替换成类型上限。
无限制类型擦除
有限制类型擦除
ArrayList 与 ArrayList 编译后都被擦除了,变成了原生类型 ArrayList。
感谢您的阅读。关注公众号(微微的灯光),让我们一起学习,一起交流,一起进步。