目录
一、泛型的定义
- 泛型的实质是将数据的类型参数化,也就是说类中操作的数据类型可以指定为一个参数(在定义类,接口,方法时为其增加数据类型的参数)
- 泛型技术的核心目标是提高数据的安全性,减少强制类型转换,增强代码的可复用性(接下来会通过案例讲解)
- 泛型的声明使用尖括号加一个字母,,其中的大写字母可以是任何字母,表示要传入的参数类型
定义语法
// 泛型类的定义
[修饰符] class 类名<T> // T必须是继承Object类的子类,如String,Integer,Double...
// 泛型接口的定义
[public] interface 接口名<T>
// 泛型方法的定义
[public] [static] <T> 返回值类型 方法名(T参数)
代码示范
// 泛型类
// T必须是继承Object父类的子类,如String,Integer,Double...
public class Test<T>{
//该属性字段的使用T类型填充(数据类型未知)
//使用泛型表示,运行时确定该字段的数据类型
private T data;
}
// 泛型接口
public interface ITest<T>{}
// 静态泛型方法
public static <T> void getName(T a) {
System.out.println(a.toString());
}
// 实例泛型方法
public <T> void getId(T a) {
System.out.println(a.toString());
}
// 泛型类对象实例化
public static void main(String[] args) {
// 泛型类在创建实例时,通过尖括号,传入一个String类型,类的内部会根据传入的这个数据类型做相应处理
Test<String> obj = new Test<String>();
// 也可以不对尖括号<>传参数的数据类型
Test obj2 = new Test();
}
1.1、泛型保证数据的安全
Java中常用的List集合就是一种泛型类
List<String> list = new ArrayList<>();
list.add("Hello"); // 编译通过
list.add(123); // 编译错误:不能添加 Integer 到 String 列表(编译时会检查数据类型是否匹配)
1.2、消除强制类型转换
// 1.如果不使用<>泛型,那么在获取元素时就要强制类型转换,这可能引发ClassCastException异常
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // 需要强制转换
// 2.使用<>泛型后,在获取元素时,无需转换数据类型(在类的内部就做好了数据类型的匹配处理)
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // 自动推断类型,无需转换
二、深入了解泛型
2.1、泛型类的使用
- 注意代码案例中的T,T的传参不能是基本数据类型,必须是引用数据类型,如Integer,Double,String...
// 创建一个Box盒模型泛型类
public class Box<T> {
// 设置数据类型为泛型
private T value;
// 设置泛型传参
public void setValue(T value) {
this.value = value;
}
// 返回泛型参数
public T getValue() {
return value;
}
}
// Test.java
public class Test {
public static void main(String[] args) {
// 普通类的构造
Box box = new Box();
// 泛型类的构造
// 创建泛型类对象实例, <Integer> 表示传入Integer类型
// 类内部相相关的属性参数会自动推导为Integer
Box<Integer> intBox = new Box<>();
intBox.setValue(100);
int num = intBox.getValue();
Box<String> strBox = new Box<>();
strBox.setValue("Hello");
String text = strBox.getValue();
}
}
2.2、泛型函数
public class FanMethods {
//静态泛型函数
public static <T> void staticMethod(T a){
System.out.println("静态泛型函数"+a.toString());
}
//实例泛型函数
public <T> void OtherMethod(T a){
System.out.println("对象实例泛型函数"+a.toString());
}
}
// Test.java
public class Test{
public static void main(String[] args) {
// 静态泛型方法调用
FanMethods.staticMethod("adfdsa");//使用方法一,此种方式根据传参参数类型隐式传递
FanMethods.<String>staticMethod("adfdsa");//使用方法二,这种编写方式是为了代码更加清晰,表明调用了泛型函数
// 实例泛型函数调用
// 需要先创建实例对象才能调用
FanMethods fanMethod = new FanMethods();
fanMethod.OtherMethod(new Integer(123));//使用方法一
fanMethod.<Integer>OtherMethod(new Integer(123));//使用方法二
}
}
2.3、泛型接口
方式一:非泛型类实现泛型接口
// 在接口上定义泛型
interface Info<T>{
// 定义抽象方法,抽象方法的返回值就是泛型类型
T getVar() ;
void setVar(T x);
}
// 定义泛型接口的实现类
// 注意这段代码Info<String>,在实现接口方法时,会自动将方法的泛类型填充为String
class InfoImpl implements Info<String>{
// 定义属性
private String var ;
// 通过构造方法设置属性内容
public InfoImpl(String var){
this.setVar(var) ;
}
// 重写接口方法
// 因为显式定义了Info<String>,所以这里会自动设置String数据类型
@Override
public void setVar(String var){
this.var = var ;
}
@Override
// 因为显式定义了Info<String>,所以这里会自动设置String数据类型
public String getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
方式二:泛型类实现泛型接口
使用泛型类来实现泛型接口就是为了让用户定义接口所需要的数据类型,而非方式一那样直接写死
interface Info<T>{
public T getVar() ;
public void setVar(T var);
}
// 定义泛型接口实现类
// 在构造实现类时, 定义InfoImpl<T>泛型类来实现泛型接口
class InfoImpl<T> implements Info<T>{
// 定义属性泛型
private T var;
// 通过构造方法设置属性内容
public InfoImpl(T var){
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public static void main(String arsg[]){
InfoImpl<String> i = new InfoImpl<String>("harvic");
InfoImpl<Integer> i2 = new InfoImpl<Integer>(222);
System.out.println(i.getVar()) ;
System.out.println(i2.getVar()) ;
}
};
2.4、多泛型变量
可以一次定义多个泛型变量
通常来说,类型参数不限于任何字母,但其一般都有对应语义
- E — Element,常用在java Collection里,如:List,Iterator,Set
- K,V — Key,Value,类似Map的键值对key-Value
- N — Number,数字
- T — Type,类型,如String,Integer等等
class InfoImpl<T,K,U> implements Info<U>{ // 定义泛型接口的子类
private U var ;
private T x;
private K y;
public InfoImpl(U var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(U var){
this.var = var ;
}
public U getVar(){
return this.var ;
}
}
2.5、泛型类的边界约束
在定义泛型类时,Java默认可以使用任何数据类型来实例化泛型对象,但Java语言也可以在使用泛型类创建对象时,对传递的类型参数做出类型限制。
T extends Type 上界约束
// class className <T extends anyClass>
// anyClass可以是一个类,也可以是一个接口。
// 在实例化泛型类对象时,T传参类型的类只能是anyClass类或继承了anyClass类的子类。
// 如果anyClass是接口,则传入的类型参数只能是实现了anyClass这个接口的类
// 代码案例
public class Box<T extends Number> {
private T value;
// 只能存储 Number 或其子类(如 Integer、Double)
}
T super anyClass 下界约束
// class className <T super anyClass>
// 必须是anyClass类或其父类,如果anyClass是接口,则必须是anyClass接口或者它的父接口
// 代码案例
public class Box<T super Integer> {
private T value; //元素类型只能是Integer或其父类(如Number、Object等)。
}
多重边界约束
public class Example<T extends Number & Comparable<T>> {
// T 必须是 Number 的子类且实现了 Comparable 接口
}
2.6、泛型实例的通配符
无界通配符?
? 表示任意类型,适用于只读操作
List<?> list = new ArrayList<String>();
String s = list.get(0); // 允许读取,但类型为 Object
list.add("Hello"); // 编译错误:禁止写入
上界通配符<? extends T>
表示类型必须是 T 或其子类,适用于只读场景
List<? extends Number> numbers = new ArrayList<Integer>();
Number n = numbers.get(0); // 允许读取 Number 类型
numbers.add(10); // 编译错误:禁止写入
下界通配符<? super T>
表示类型必须是 T 或其父类,适用于写入场景
List<? super Integer> list = new ArrayList<Number>();
list.add(10); // 允许写入 Integer
Integer i = (Integer) list.get(0); // 需要强制转换
2.7、Class 传递类的Class对象
先简单介绍下Class对象
- Class对象:每个类都有一个Class对象,Class对象是反射机制的核心,是Java运行时为每个类或者接口生成的一个特殊对象。
- JVM在加载类时都会在内存中为其创建对应的Class对象(单例对象)
- Class对象包含了一个类的所有结构信息
- 类名
- 父类
- 实现的接口
- 构造方法
- 成员变量
- 方法
- 注解等元数据
现在回到泛型对Class对象的应用:在实际业务场景中,我们经常会遇到解析JSON字符串的需求,代码如下
public static List<SuccessModel> parseArray(String response){
// JSON.parseArray(String jsonStr,Class classObj) 属于FastJSON框架的API,参数1传递json字符串,参数2传递一个class对象
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
public static List<SuccessModelB> parseArray2(String response){
// JSON.parseArray(String jsonStr,Class classObj) 属于FastJSON框架的API,参数1传递json字符串,参数2传递一个class对象
List<SuccessModelB> modelList = JSON.parseArray(response, SuccessModelB.class);
return modelList;
}
上面代码在书写格式上没有任何问题,但假如现在有一个新的类型SuccessModelB需要做解析,难道再编写一个代码几乎一致的函数吗?
这时候可以通过泛型封装成一个公共函数,解决代码复用问题
public class JsonUtils{
// Class<T> object传递类的class对象
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> resultList = JSON.parseArray(response, object);
return resultList;
}
}
// 代码使用案例
public class Test{
public static void main(String[] args) {
String jsonStr = "[{name: 'xxx', age: 20}, {name: 'yyy', age: 22}, {name: 'zzz', age: 30}]"
List<Person> person = JsonUtils.parseArray(jsonStr,Person.class);
List<PersonB> person = JsonUtils.parseArray(jsonStr,PersonB.class);
}
}
三、泛型实际应用场景(对应业务场景)
3.1、Http请求返回结果封装
ResultVo.java
import lombok.Getter;
@Getter
public class ResultVO<T> {
/**
* 状态码,比如1000代表响应成功
*/
private int code;
/**
* 响应信息,用来说明响应情况
*/
private String msg;
/**
* 响应的具体数据
*/
private T data;
public ResultVO(T data) {
this(ResultCode.SUCCESS, data);
}
public ResultVO(ResultCode resultCode, T data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}
}
RpcServiceResult.java
import lombok.Data;
/**
* RPC服务返回结果
*/
@Data
public class RpcServiceResult<T> {
private boolean success;
private String errorCode;
private String errorMsg;
private T data;
public static <T> RpcServiceResult<T> getSuccessResult(T t) {
RpcServiceResult<T> result = new <>();
result.setSuccess(true);
result.setData(t);
return result;
}
public static <T> RpcServiceResult<T> getFailureResult(
String code, String msg) {
RpcServiceResult<T> result = new RpcServiceResult<>();
result.setSuccess(false);
result.setErrorCode(code);
result.setErrorMsg(msg);
return result;
}
}
3.2、通用JSON格式化封装方法
/**
* 将数组JSON字符串格式化返回List结果集
* @param response 响应的结果数据集,JSON字符串
* @param clas 要格式化的目标类Class对象
*/
public static <T> List<T> parseArray(String response,Class<T> clas){
List<T> modelList = JSON.parseArray(response, clas);
return modelList;
}
/**
* 将对象JSON字符串格式化返回对象实例结果,如果格式化失败则返回null
* @param json 响应的结果数据集,JSON字符串
* @param clas 要格式化的目标类Class对象
*/
public static <T> T parseJsonToObj(String json, Class<T> clas) {
try {
JSONObject jsonObject = JSONObject.parseObject(json);
return JSON.toJavaObject(jsonObject, clas);
} catch (Exception e) {
LOG.error(e.getMessage());
}
return null;
}
3.3、无法实例化泛型类型的对象
public class Box<T> {
private T value;
public Box(Class<T> clazz) throws Exception {
// 通过反射机制创建一个对象
// .getDeclaredConstructor()获取无参构造
// .newInstance()创建泛型的实例对象
this.value = clazz.getDeclaredConstructor().newInstance();
}
}
四、附外参考资料
Class对象详解
3751

被折叠的 条评论
为什么被折叠?



