前言:
- 纸上得来终觉浅,绝知此事要躬行。
- 网上的此类文章可能相似的很多,但我相信这些更多的是学习者学习的痕迹。对作者本身有着特殊的含义。
- 当然感谢祖国的强大、社会服务工作者、以及千千万万的平凡无私人、也感谢尚学堂的老师们!谢谢你们这么优秀的课程。
一 泛型简介
1.1泛型基本概念
- 泛型可以帮助我们建立类型安全的集合。
- 泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。这样一来我们的程序更加灵活、功能更加强大。
- 可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型。
- 这种参数类型可以在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
- 参数化类型
- 把类型当作是参数一样传递
- <数据类型>只能是引用类型
1.2 泛型的优势
- 在不使用泛型的情况下,我们可以使用Obiect类型来实现任意的参数类型,但是在使用时需要我们强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误。但是,在编译期我们无法识别这种错误,只能在运行期发现这种错误。
- 使用泛型的优势就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
- 使用泛型主要是两个好处:
- 代码可读性更好【不用强制转换】
- 程序更加安全【只要编译时期没有警告,运行时期就不会出现ClassCastException异常】
1.3 类型擦除
- 编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
- 泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。虽然在IDEA中我们通过反编译可以看到的泛型,是IDEA只能分析出来的,实际中的class文件中不包含泛型中的类型信息。
- 涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
- 泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
二 泛型的使用
2.1定义泛型
- 泛型字符可以是任何标识符,一般规定默认采用几个标记:E、T、K、V、N、?
| 泛型标记 | 对应单词 | 说明 |
|---|
| E | Element | 在容器中使用,表示容器中的元素 |
| T | Type | 表示普通的JAVA类 |
| K | Key | 表示键,例如:Map中的键Key |
| V | Value | 表示值 |
| N | Number | 表示数值类型 |
| ? | | 表示不确定的JAVA类型 |
2.2泛型类
- 泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后添加一个或多个类型参数声明,如:、<T,K,V>
2.2.1 语法结构
public class 类名<泛型表示符号> {
}
2.2.2 栗子
public class Generic01<T> {
private T flag;
public void setFlag(T flag) {
this.flag=flag;
}
public T getFlag() {
return this.flag;
}
public static void main(String[] args) {
Generic01<String> generic = new Generic01<>();
generic.setFlag("hello");
System.out.println(generic.getFlag());
Generic01<Integer> generic01 = new Generic01<>();
generic01.setFlag(100);
System.out.println(generic01.getFlag());
}
}

2.3 泛型接口
2.3.1 泛型接口
public interface 接口名<泛型表示符号> {
}
2.3.2 栗子
interface IGeneric<T> {
T getName(T name);
}
class IGenericImpl implements IGeneric<String> {
@Override
public String getName(String name) {
return name;
}
}
public class Generic02 {
public static void main(String[] args) {
System.out.println(new IGenericImpl().getName("小明"));
IGeneric<String> iGeneric= new IGenericImpl();
System.out.println(iGeneric.getName("小美"));
}
}

2.4 泛型方法
- 泛型类中所定义的泛型,在方法中也可以使用。
- 需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
- 泛型方法是指将方法的参数类型定义成泛型,以便在调用时接收不同类型的参数。类型参数可以有多个,用逗号隔开,如:<K,V>。
- 定义时,类型参数一般放到返回值前面
- 调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型。
2.4.1 非静态方法
- 语法结构
public <泛型表示符号> void getName(泛型表示符号 name) {
}
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name) {
}
- 栗子
class MethodGeneric {
public <T> void setName(T name) {
System.out.println(name);
}
public <T> T getName(T name) {
return name;
}
}
public class Generic03 {
public static void main(String[] args) {
new MethodGeneric().setName("小华");
new MethodGeneric().setName(666);
System.out.println(new MethodGeneric().getName("love you"));
System.out.println(new MethodGeneric().getName(521));
}
}

2.4.2 静态方法
- 静态方法中使用泛型时,注意静态方法无法访问类上定义的泛型。
- 静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
- 语法结构
public static <泛型表示符号> void getName(泛型表示符号 name) {
}
public static <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name) {
}
- 栗子
class StaticMethodGeneric {
public static <T> void setFlag(T flag) {
System.out.println(flag);
}
public static <T> T getFlag(T flag) {
return flag;
}
}
public class Generic04 {
public static void main(String[] args) {
StaticMethodGeneric.setFlag("i love sky");
StaticMethodGeneric.setFlag(2022);
System.out.println(StaticMethodGeneric.getFlag("he like her"));
System.out.println(StaticMethodGeneric.getFlag(6688));
}
}

2.4.3 泛型方法的可变参数类型
- 语法结构
public static <泛型表示符号> void showMsg(泛型表示符号... args) {
}
- 栗子
class ChangeGeneric {
public <T> void method(T...args) {
for(T t:args) {
System.out.print(t+" ");
}
}
}
public class Generic05 {
public static void main(String[] args) {
String[] strings = {"aaa", "bbb", "ccc"};
Integer[] integers = {1, 2, 3};
new ChangeGeneric().method(strings);
new ChangeGeneric().method(integers);
}
}

2.5 通配符和上下限定
2.5.1 无界通配符
- “?”表示类型通配符用于替代具体的类型。它只能在"<>"中使用可以解决当具体类型不确定的问题。
- 语法结构
public void showFlag(Generic<?> generic) {
}
- 栗子
class MyGeneric<T> {
private T flag;
public void setFlag(T flag) {
this.flag=flag;
}
public T getFlag() {
return this.flag;
}
}
class showMsg {
public void showFlag(MyGeneric<?> generic) {
System.out.println(generic.getFlag());
}
}
public class Generic06 {
public static void main(String[] args) {
showMsg showMsg = new showMsg();
MyGeneric<Integer> generic = new MyGeneric<>();
generic.setFlag(20);
showMsg.showFlag(generic);
MyGeneric<Number> generic1 = new MyGeneric<>();
generic1.setFlag(33);
showMsg.showFlag(generic1);
MyGeneric<String> generic2 = new MyGeneric<>();
generic2.setFlag("boy");
showMsg.showFlag(generic2);
}
}

2.5.2 通配符的上限限定
- 上限限定表示通配符的类型是T类以及T类的子类或者T接口以及T接口的子接口。
- 语法结构
public void showFlag(Generic<? extends Number> generic) {
}
- 栗子
class MyGeneric07<T> {
private T flag;
public void setFlag(T flag) {
this.flag=flag;
}
public T getFlag() {
return this.flag;
}
}
class showMyMsg {
public void showFlag(MyGeneric07<? extends Number> myGeneric07) {
System.out.println(myGeneric07.getFlag());
}
}
public class Generic07 {
public static void main(String[] args) {
showMyMsg showMyMsg = new showMyMsg();
MyGeneric07<Integer> generic07 = new MyGeneric07<>();
generic07.setFlag(20);
showMyMsg.showFlag(generic07);
MyGeneric07<Number> generic071 = new MyGeneric07<>();
generic071.setFlag(666.666);
showMyMsg.showFlag(generic071);
}
}

2.5.3 通配符的下限限定
- 语法结构
public void showFlag(Generic<? super Number> generic) {
}
- 栗子
class MyGeneric08<T> {
private T flag;
public T getFlag() {
return this.flag;
}
public void setFlag(T flag) {
this.flag = flag;
}
}
class showMsg08 {
public void showFlag(MyGeneric08<? super Integer> myGeneric) {
System.out.println(myGeneric.getFlag());
}
}
public class Generic08 {
public static void main(String[] args) {
showMsg08 showMsg = new showMsg08();
MyGeneric08<Integer> myGeneric = new MyGeneric08<>();
myGeneric.setFlag(66);
showMsg.showFlag(myGeneric);
MyGeneric08<Object> myGeneric1 = new MyGeneric08<>();
myGeneric1.setFlag(50.5);
showMsg.showFlag(myGeneric1);
}
}

三 总结
- ***泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。***类型参数在编译后会被替换成obiect,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错误的:
- 基本类型不能用于泛型
Test<int> t;这样写法是错误,我们可以使用对应的包装类:Testt;
- 不能通过类型参数创建对象
T elm=newT();运行时类型参数T会被替换成Object,无法创建T类型的对象,容易引起误解,所以在Java中不支持这种写法。