做过 java/Android 的童鞋都知道,不管我们后台写 json 接口返回,还是移动端解析 json 接口,我们会将最终结果做统一的格式处理,具体代码可以是这样的:
public class Result {
private Integer code; //状态码
private String msg;//消息
private Object data;//最终返回的数据体
// ...省略 set get 方法
}
最终调用如下:
Result result = new Result();
result.setCode(200);
result.setMsg("成功");
result.setData("我是最终数据结果");
//执行序列化,反序列化操作
分析:
优点:看到 Object 上帝类,就知道最终 data 这个参数可以设置任意类型参数,对于数据格式固定,这种写法明显降低了代码量,对最终结果做了统一处理。
缺点:我们试图获取构造的对象数据时,有可能不清楚当初传入的是什么类型,强制类型转换为其他类型,或者由于程序员的疏忽而强制转换为其他类型,这在编译时是正常通过的,因为 Object 可以强制转换为任意类型,只是对象中我们传入的类型有可能不能强制转换,最终在运行时,由 java 抛出类型转换异常,这无疑是一种程序潜在的不安全。如下代码:
//编译正常,运行时抛异常
Integer data = (Integer) result.getData();
System.out.println("data:" + data);
实际传入为 String ,强制转为 Integer,抛出 ClassCastException 异常。
那么,基于此,java 提供的泛型就是为了解决这个痛点的。
泛型特点
泛型是计算机程序中一种重要的思维方式,它将数据结构和算法与数据类型分离开,使得同一套数据结构和算法能够应用于各种数据类型,而且可以保证类型安全,提高可读性。
普通泛型类
用泛型对 Result
类做下修改:
public class Result<T> {
private Integer code; //状态码
private String msg;//消息
private T data;//数据体
// ...省略 部分 set,get 方法
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
//指定泛型为 String
Result<String> result = new Result();
result.setCode(200);
result.setMsg("成功");
result.setData("我是最终数据结果");
//不需要类型转换,直接返回泛型类型
String data = result.getData();
System.out.println("data:" + data);
}
}
分析:
- 取消
Object
,改用T
类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入 - 创建对象的时候,指定了泛型类型,最终获取数据时,java 编译器在内部会帮助识别类型,不需要类型转换,在编译时期就把类型安全解决掉,不会有后期的安全隐患。
多参数类型参数
类型参数可以有多个,用逗号隔开,简单改写代码如下:
public class Result<T,U> {
private Integer code; //状态码
private String msg;//消息
private T data1;
private U data2;
// ...省略部分 set get 方法
public T getData1() {
return data1;
}
public U getData2() {
return data2;
}
public static void main(String[] args) {
Result<String,String> result = new Result();
result.setCode(200);
result.setMsg("成功");
result.setData1("我是第一个结果");
result.setData2("我是第二个结果");
String data1 = result.getData1();
String data2 = result.getData2();
System.out.println("data1:" + data1 + ",data2:" + data2);
}
}
泛型方法
泛型也可以指定在具体的方法上,与所在的类是否是泛型没关系,如下,添加静态方法:
计算:找出 泛型数组中,指定元素的索引值,找不到则返回 -1
//查找数组指定元素索引
public static <T> int indexOf(T[] arr,T element){
for (int i = 0; i < arr.length; i++) {
if (arr[i].equals(element)){
return i;
}
}
return -1;
}
调用如下:
int index = indexOf(new Integer[]{1, 3, 5}, 10);
System.out.println(index);
//结果为:-1
int index1 = indexOf(new String[]{"张少林", "福建", "漳州", "25"}, "张少林");
System.out.println(index1);
//结果:0
以上可知,泛型方法与泛型类达到的效果一致,同一段代码,与所传入的参数类型无关,可以方便的在各种数据类型之间进行复用,且是类型安全的。
同泛型类一样,泛型方法也可以传入多个类型参数,用户最终调用不需要关心传入的具体类型是啥,java 编译器会自动处理。代码如下:
public static <U,V> Result<U,V> makeResult(U first,V second){
Result<U, V> pair = new Result<>(first, second);
return result;
}
//调用:
Result<String, Integer> result = makeResult("张少林", 25);
泛型接口
接口也可以是泛型的,java 中的 Comparable<T>,Comparator<T> 就是泛型接口,定义如下:
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Comparator<T> {
......
}
实现 接口,必须制定泛型类型,如:Integer 类 指定泛型类型为 Integer
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
}
限定类型参数
之前的泛型写法中,类型参数默认是派生自 Object 的,这时候,类型参数传入任何类型都是可以的,而我们可以自定义类型参数的上界,类型参数上界可以是个具体类,接口,其他类型参数,此时类型参数必须为上界类型的子类或者是它本身,或者实现了上界接口。
- 上界为具体类
public class Pair<U,V> {
U first;
V second;
public Pair(U first, V second) {
this.first = first;
this.second = second;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
}
public class NumberPair<U extends Number,V extends Number> extends Pair<U,V>{
public NumberPair(U first, V second) {
super(first, second);
}
public static void main(String[] args) {
NumberPair<Integer, Integer> numberPair = new NumberPair<>(666, 999);
System.out.println(numberPair.getFirst() + "," + numberPair.getSecond());
//结果是:666,999
//编译错误
new NumberPair<String,String>("cdsc","csdc")
}
}
很明显,假如传入的类型参数,不是派生自 Number 类,在编译时,就会提示错误。
- 上界为接口
泛型方法中,类型参数必须实现某个接口,从而依赖接口的方法,具体例子如下,获取数组中的最大值,具体类型依赖 Comparable<T> 接口的方法,compareTo,也就是每个具体类型实现 compareTo 的比较逻辑即可:
public static <T extends Comparable<T>> T max(T[] arr){
T max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i].compareTo(max)>0) {
max = arr[i];
}
}
return max;
}
- 上界为其他类型参数,在不知道类型参数具体的上界是什么的时候,可以定义为其他的类型参数,类似如下代码:
public static <T extends E> void addAll(List<T> c){
for (int i = 0; i < c.size(); i++) {
add(c.get(i));
}
}
至此,java 泛型基础总结先到这里了,还没完,路还很长。
更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学