1.了解泛型
了解泛型以及应用场景中不使用泛型出现的问题(通过定义类属性为Object)
泛型是指再创建一个类时不指定类中属性的数据类型,而是在创建类对象时再指定相应的数据类型。
例如我们需要创建一个xy坐标轴,由于不确定要传入什么数据类型的数据,创建两个Object属性接收数据,此时xy轴可以为任意类型。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Point {
//x轴
private Object x;
//y轴
private Object y;
}
以传入 String 和 Integer 为例:
//1.传入两个 String 类型
Point point1 = new Point("x轴坐标", "y轴坐标");
String x1 = (String) point1.getX(); //正常获取
//2.传入两个 Integer 类型
Point point2 = new Point(12, 13);
Integer x2 = (Integer) point2.getX();
//3.传入一个 Integer 类型,一个 String 类型
Point point3 = new Point(12, "y轴坐标");
Integer x3 = (Integer) point3.getX();
String y3 = (String) point3.getY();
虽然在需要使用此类时可以传入任意数据类型,此时问题就出现了,但当需要取出数据时需要进行类型强制转换,这大大影响了代码的质量和实际中的代码可观性,并且造成逻辑混乱。
因此我们需要使用泛型来定义需要的数据类型。
2.使用泛型
2.1定义泛型类
定义格式:
public class 类名<泛型标识,泛型标识...>{
public 泛型标志 属性名;public 泛型标志 属性名;
}
类中需要多少个参数在<>中写几个泛型。泛型标识符可以是任意字符,习惯使用 T 。
以我们此时需要两个为例,x 和 y。我们需要的 x y 数据类型相同,故因此只写一个泛型参数。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PointT<T> {
//T为泛型标识符
private T x;
private T y;
}
当然也可以定义不同的数据类型参数 T , E ... 。比如:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PointTE<T,E> {
//T , E 为泛型标识符
private T x;
private E y;
}
2.2实际应用
分别创建两个对象,一个泛型为String 一个为Integer 。
//此时泛型为 String
PointT<String> stringPointT = new PointT<>("x轴坐标1", "y轴坐标2");
System.out.println(stringPointT);
//输出结果 PointT(x=x轴坐标1, y=y轴坐标2)
//此时泛型为 Integer
PointT<Integer> integerPointT = new PointT<>(12, 13);
System.out.println(integerPointT);
//输出结果 PointT(x=12, y=13)
这是我们就不需要再将需要的数据进行强制类型转换了,直接将想要的数据类型填充在<>内,自动输出相应类型的数据。
如果定义为两个不同类型的泛型:
//两个不同类型的泛型 String , Integer
PointTE<String, Integer> stringIntegerPointTE = new PointTE<>("x轴坐标1",15);
System.out.println(stringIntegerPointTE);
//输出结果 PointTE(x=x轴坐标1, y=15)
3.通配符 ?
一般我们需要调用一个需要接收泛型类参数的方法时,只能根据方法形参传入对应的类和泛型相同的实参。定义一个泛型类 Info<T>,一个方法fun。
/**
* 定义泛型类
*/
@Data
class Info<T> {
private T var;
public void show() {
System.out.println("var===" + var);
}
}
//不推荐的方法
public static void fun(Info<String> info) {
info.show();
}
//调用方法
Info<Integer> info = new Info<>();
info.setVar(14);
//fun(info); //泛型要求为 String 因此无法调用
Info<String> info1 = new Info<>();
info1.setVar("通配符是?");
fun(info1); //泛型方法只接收相同类 相同泛型
每当此类需要一个不同的数据类型参数就要重新写一个对应的方法,这样无疑是提高了这个方法的局限性,因此可以用通配符 ? 来提高代码的复用性。定义方法fun1,使用 ? 代替 T 。
//使用通配符的方法
//能够让泛型接受任意泛型类型 必须使用通配符 ? 是泛型的通配符
public static void fun1(Info<?> info) {
info.show();
}
//泛型为 Integer
Info<Integer> info = new Info<>();
info.setVar(14);
//泛型为 String
Info<String> info1 = new Info<>();
info1.setVar("通配符是?");
//此时使用通配符可以接受任意泛型的类
fun1(info);
fun1(info1);
4.受限泛型
我们在使用泛型传递操作中也可以为泛型的类型设定一个范围 ,
分为: 泛型上限 <? extends 类> 和 泛型下限 <? super 类>
定义格式:声明对象: 类名称< ? extends / super 类> 对象名称;
定义类: [访问权限] 类名称<泛型标识 extends / super 类>{ }
方法: [访问权限] 返回值类型 方法名( 类名称<泛型标识 extends / super 类> 形参名 ){ }
4.1 泛型上限 <? extends 类>
我们以类 Number 作为受限上限为例。
分别创建泛型为 Integer 、Object 、String 的对象解实说明。
//泛型上限
Info<Integer> info2 = new Info<>();
fun2(info2); // Integer 是 Number 子类 满足泛型上限
Info<Object> info21 = new Info<>();
Info<String> info22 = new Info<>();
//fun2(info21); // Object 为 Number 父类 超出泛型上限 泛型需要其为子类或Number
//fun2(info22); // String 不为 Number 类 不满足泛型上限 泛型需要其为子类或Number
//泛型上限 <? extends 类>
public static void fun2(Info<? extends Number> info) {
info.show();
}
4.2 泛型下限 <? super 类>
我们以类 Number 作为受限下限为例。
分别创建泛型为 Integer 、Object 、String 的对象解实说明。
//泛型下限
Info<Integer> info3 = new Info<>();
//fun3(info3); // Integer 为 Number 子类 不到泛型下限 泛型需要为其父类或Number
Info<Object> info31 = new Info<>();
Info<String> info32 = new Info<>();
fun3(info31); // Object 为 Number 父类 满足泛型下限
//fun3(info32); // String 不为 Number 类 不满足泛型下限
//泛型下限 <? super 类>
public static void fun3(Info<? super Number> info) {
info.show();
}
总结:泛型上限为继承类的本身或其子类,泛型下限为super类本身或其父类。
5.泛型接口 (以 USB<T> 为例)
语法:public interface 接口名<泛型标识 , ...>{ }
创建一个接口,可以将接口设置为泛型。实现泛型接口有两种方式:
1. 类实现泛型接口时为其指定泛型类型 。
2. 类实现接口时同时也设置类为泛型类 泛型标识符要一致 。
/**
* 4.泛型接口 USB<T>
*/
interface USB<T> {
public abstract void use(T s);
}
//4.1 类实现泛型接口时为其指定泛型类型 Mouse
class Mouse implements USB<String> {
@Override
public void use(String s) {
System.out.println("s===" + s);
}
}
//4.2 类实现接口时同时也设置类为泛型类 泛型标识符要一致 Upan<T>
class Upan<T> implements USB<T> {
@Override
public void use(T s) {
System.out.println("s===" + s);
}
}
调用类方法:
//4.1 类实现泛型接口时为其指定泛型类型
Mouse mouse = new Mouse();
mouse.use("鼠标"); // s===鼠标
//4.2 类实现接口时同时也设置类为泛型类 泛型标识符要一致
Upan<String> stringUpan = new Upan<String>();
stringUpan.use("U盘"); // s===U盘
总结:两种实现方法都各有用处,根据实际应用场景进行选择。
6. 泛型方法
以上都是将整个类进行泛型化,而泛型方法则是与其所在类是否是泛型没有任何关系,所在类可以是泛型也可以不是。
我们也可以为方法的形参和返回值类型同时设置泛型,以方便开发中的需求。
语法:[访问权限] <泛型标识> 泛型标识 方法名称(泛型标识 参数名称){ }
定义泛型方法:
/**
* 5.泛型方法 fun4
*/
public <T> T fun4(T a) {
System.out.println("---------------"+a);
return a;
}
调用方法:
PointTest pointText = new PointTest();
Integer i = pointText.fun4(15);
String s = pointText.fun4("15 String");
//输出结果:
//---------------15
//---------------15 String
总结:泛型方法的灵活性与实用性更为广,能够在开发中提供很大的操作性。