泛型简介
概念
- 泛型的本质就是数据类型的参数化,处理的数据类型不是固定的,而是可以作为参数传入。
- 我们可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编
译器,在调用泛型时必须传入实际类型。 - 这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
好处
- 可以让编译器帮助我们识别强制类型转换时可能引起的错误,自动转换增加安全性和可读性
类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码 class 文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。 类型参数在编译后会被替换成 Object,运行时虚拟机并不知道泛型。
泛型使用
定义泛型
泛型类
- 泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。
多个标识符用逗号隔开
public class 类名<泛型标识符,泛型标识符>{
}
- 定义一个泛型类
package generic;
public class Generic <T>{
private T flag;
public void setFlag(T flag) {
this.flag = flag;
}
public T getFlag() {
return flag;
}
}
- 测试泛型类
package generic;
public class testGeneric {
public static void main(String[] args) {
// TODO Auto-generated method stub
Generic<String> generic = new Generic<>();
generic.setFlag("admin");
String flag = generic.getFlag();
System.out.println(flag);
Generic<Integer> generic2 = new Generic<>();
generic2.setFlag(123);
Integer flag1 = generic2.getFlag();
System.out.println(flag1);
System.out.println(flag1.TYPE);
}
}
admin
123
int
泛型接口
- 泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
多个标识符用逗号隔开
public interface 接口名<泛型标识符,泛型标识符>{
}
- 定义一个泛型接口
public interface Igeneric<T> {
T getName(T name);
}
- 定义一个泛型接口实现类
public class IgenericImpl implements Igeneric<String>{
@Override
public String getName(String name) {
// TODO Auto-generated method stub
return name;
}
}
- 测试类
- 通过接口实现类创建对象时,按照实现类的参数类型确定
- 直接通过接口修饰时需要确定参数类型,不指定之前时Object类型
package generic;
import Study.Salary;
public class testGeneric {
public static void main(String[] args) {
// TODO Auto-generated method stub
IgenericImpl igenericImpl = new IgenericImpl();
String name = igenericImpl.getName("ssc");
System.out.println(name);
}
}
泛型方法
- 泛型方法是指将方法的参数类型定义成泛型,以便在调用时接收不同类型的参数。类型参数可以有多个,用逗号隔开,如:<K,V>。定义时,类型参数一般放到返回值前面。
- 调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型来。
非静态方法
只要定义了泛型方法,方法的参数和内部都可以使用该泛型
public <泛型表示符号> void getName(泛型表示符号 name){
}
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
package generic;
public class MethodGeneric {
public static void main(String[] args) {
// TODO Auto-generated method stub
MethodGeneric methodGeneric = new MethodGeneric();
//不需要写出参数类型
methodGeneric.setName("ssc");
methodGeneric.setName(123);
System.out.println(methodGeneric.getName(456));
System.out.println(methodGeneric.getName("ssc1"));
}
//定义了一个泛型方法
public <T> void setName(T name) {
System.out.println(name);
}
public <T> T getName(T name){
return name;
}
}
ssc
123
456
ssc1
静态方法
- 静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static <泛型表示符号> void setName(泛型表示符号 name){
}
public static <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
//静态方法中测试泛型
//有返回值
public static <T> void getFlag(T flag) {
System.out.println(flag);
}
//无返回值
public static <T> T setFlag1(T flag) {
return flag;
}
package generic;
import Study.Salary;
public class testGeneric {
public static void main(String[] args) {
// TODO Auto-generated method stub
//静态方法测试
Igeneric<String> igenericImpl2 = new IgenericImpl();
String name1 = igenericImpl2.getName("sdasdad");
System.out.println(name1);
MethodGeneric.getFlag(132);
Integer flag12 = MethodGeneric.setFlag1(234);
System.out.println(flag12);
}
}
132
123456
泛型方法和可变参数
public <泛型表示符号> void showMsg(泛型表示符号... agrs){
}
//泛型方法可变参数
public <T> void method(T...args) {
for(T t:args) {
System.out.println(t);
}
}
public class testGeneric {
public static void main(String[] args) {
// TODO Auto-generated method stub
//可变参数类型遍历数组
MethodGeneric methodGeneric2 = new MethodGeneric();
String[] arr = new String[] {"a","b","c"};
Integer[] a = new Integer[] {1,2,3};
methodGeneric2.method(arr);
methodGeneric2.method(a);
}
}
a
b
c
1
2
3
通配符的使用
- “?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用。可以解决当具体类型不确定的问题。
public void showFlag(Generic<?> generic){
}
package generic;
public class Wildcard {
public static void main(String[] args) {
ShowMsg uMsg = new ShowMsg();
Generic<Integer> generic = new Generic<>();
generic.setFlag(20);
uMsg.showFlag(generic);
Generic<Number> generic1 = new Generic<>();
generic1.setFlag(30);
uMsg.showFlag(generic1);
}
}
class ShowMsg{
//?通配符
public void showFlag(Generic<?> generic) {
System.out.println(generic.getFlag());
}
// public void showFlag(Generic<Integer> generic) {
// System.out.println(generic.getFlag());
// }
}
20
30
通配符的上限限定
- 上限限定表示通配符的类型是 T 类以及 T 类的子类或者 T 接口以及 T 接口的子接口。该方式同样适用于与泛型的上限限定
public void showFlag(Generic<? extends Number> generic){
}
public class Wildcard {
public static void main(String[] args) {
ShowMsg uMsg = new ShowMsg();
//被限定在Number的子类
// Generic<String> generic2 = new Generic<>();
// generic2.setFlag(40);
// uMsg.showFlag(generic2);
}
}
class ShowMsg{
//?通配符
public void showFlag(Generic<? extends Number> generic) {
System.out.println(generic.getFlag());
}
}
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
通配符的下限限定
- 下限限定表示通配符的类型是 T 类以及 T 类的父类或者 T 接口以及 T 接口的父接口。
注意:该方法不适用泛型类。
public void showFlag(Generic<? super Number> generic){
}
public class Wildcard {
public static void main(String[] args) {
ShowMsg uMsg = new ShowMsg();
//被限定在Integer的父类
// Generic<String> generic2 = new Generic<>();
// generic2.setFlag(40);
// uMsg.showFlag1(generic2);
}
class ShowMsg{
//?通配符下限
public void showFlag1(Generic<? super Integer> generic) {
System.out.println(generic.getFlag());
}
}
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
总结
- 泛型最大的作用就是在编译时类型安全的问题,仅在编译阶段有效,结束后会自动转为Object类
- 使用泛型时,如下几种情况是错误的:
- 不能创建基本类型
Test t; 这样写法是错误,我们可以使用对应的包装类;Test t ; - 不能通过类型参数创建对象
T elm = new T(); 运行时类型参数 T 会被替换成 Object,无法创建 T 类型的对 象,容易引起误解,所以在 Java 中不支持这种写法
- 不能创建基本类型