基本概念
泛型本质是**“数据类型的参数化”。我们可以把泛型理解为数据类型的一个占位符,即告诉编译器,在调用泛型时必须传入实际类型。这种参数类型可以用在类,接口和方法中。分别被称为泛型类,泛型接口,泛型方法**。
参数化类型:1、把类型当做是参数一样传递。2、数据类型只能是引用类型。
泛型的好处
- 代码可读性更好,不用强制转换类型。
- 程序更加安全,只要编译器没有警告,运行时期就不会出现ClassCastException异常
类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。泛型主要用于编译阶段,编译后生成的字节码文件不包括泛型中的类型信息,涉及类型转换仍然是普通的强制性类型转换,类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
泛型的使用
定义泛型
泛型标记 | 对应单词 | 说明 |
---|---|---|
E | Element | 在容器中使用,表示容器中的元素 |
T | Type | 表示普通的JAVA类 |
K | Key | 表示键,例如Map中的键Key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的JAVA类型 |
泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后面添加一个或多个类型参数声明,如,<T,K,V>
//泛型类
public class Generic<T> {
private T data;
public void setData(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
public class Test {
public static void main(String[] args) {
//假如没有设定占位符,则set方法中的参数 默认是Object类型
Generic<String> generic = new Generic<>();
generic.setData("admin");
String data = generic.getData();
System.out.println(data);
Generic<Integer> generic1 = new Generic<>();
generic1.setData(54);
Integer flag = generic1.getData();
System.out.println(flag);
}
}
泛型接口
泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
public interface Igeneric<T> {
T getName(T name);
}
public class IgeneicImpl implements Igeneric<String> {
@Override
public String getName(String name) {
return name;
}
}
public class Test2 {
public static void main(String[] args) {
IgeneicImpl igeneic = new IgeneicImpl();
String name = igeneic.getName("oldlu");
System.out.println(name);
Igeneric<String> igeneric = new IgeneicImpl();
String name1 = igeneric.getName("bjsxt");
System.out.println(name1);
}
}
泛型方法
非静态方法
泛型类中所定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
泛型方法是指将方法的参数类型定义成泛型,以便在调用时接受不同类型单参数。类型参数可以有多个,用逗号隔开,如:<K,V>。定义时,类型参数一般放到返回值前面。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型来。
public class MethodGeneric {
public <T> void setName(T name){
System.out.println(name);
}
public <T> T getName(T name){
return name;
}
}
public class Test3 {
public static void main(String[] args) {
MethodGeneric methodGeneric = new MethodGeneric();
methodGeneric.setName("o");
methodGeneric.setName(123);
MethodGeneric methodGeneric2 = new MethodGeneric();
String name = methodGeneric2.getName("BjSXT");
Integer name1 = methodGeneric2.getName(123);
System.out.println(name);
System.out.println(name1);
}
}
静态方法
静态方法无法访问类所定义的泛型;如果静态方法操作的引用类型不确定,必须将泛型定义在方法上
public static<T> void setFlag(T flag){
System.out.println(flag);
}
public static<T> T getFlag(T flag){
return flag;
}
泛型方法与可变参数
在泛型方法中,泛型也可以定义可变参数类型。
public <T> void method(T...args){
for(T t : args){
System.out.println(t);
}
}
public static void main(String[] args) {
MethodGeneric methodGeneric = new MethodGeneric();
String[] arr = new String[]{"a","b","c"};
Integer[] arr2 = new Integer[]{1,2,3};
methodGeneric.method(arr);
methodGeneric.method(arr2);
}
通配符和上下限定
无界通配符
?表示类型通配符,用于代替具体类型。它只能在<>中使用。可以解决当具体类型不确定的问题。
public class ShowMsg {
public void setFlag(Generic<?> generic){
System.out.println(generic.getData());
}
}
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
Generic<Integer> generic = new Generic<>();
generic.setData(20);
showMsg.setFlag(generic);
Generic<Number> generic1 = new Generic<>();
generic1.setData(50);
showMsg.setFlag(generic1);
Generic<String> generic2 = new Generic<>();
generic2.setData("hape");
showMsg.setFlag(generic2);
}
通配符的上限限定
上限限定表示通配符(<?>)的类型是T类以及T的子类或者T接口以及T接口的子接口。该方法同样适用于泛型(T)的上限限定。
public class ShowMsg {
public void setFlag(Generic<? extends Number> generic){
System.out.println(generic.getData());
}
}
public class Generic<T extends Number> {
private T data;
public void setData(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
通配符的下限限定
下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的父接口。该方法不适用泛型类
public class ShowMsg {
public void setFlag(Generic<? super Number> generic){
System.out.println(generic.getData());
}
}
总结
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
注意点
- 基本类型不能用于泛型
- 不能通过类型参数创建对象。例如T t = new T()