美文网首页基本技能技术干货技术文
java中泛型的正确使用姿势

java中泛型的正确使用姿势

作者: laien | 来源:发表于2017-07-13 10:07 被阅读249次
    image.png

    泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用。利用好泛型,在系统架构中是一把利器。

    泛型类

    先看一个例子,restful架构中,需要定义api接口返回结构,定义如下:

    package com.company.project.core;
    
    import com.alibaba.fastjson.JSON;
    
    /**
     * 统一API响应结果封装
     */
    public class Result {
        private int code;
        private String message;
        private Object data;
    
        public Result setCode(ResultCode resultCode) {
            this.code = resultCode.code;
            return this;
        }
    
        public int getCode() {
            return code;
        }
    
        public Result setCode(int code) {
            this.code = code;
            return this;
        }
    
        public String getMessage() {
            return message;
        }
    
        public Result setMessage(String message) {
            this.message = message;
            return this;
        }
    
        public Object getData() {
            return data;
        }
    
        public Result setData(Object data) {
            this.data = data;
            return this;
        }
    
        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }
    }
    

    这是最常见的做法,这样做的一个坏处是消息体data的元素我们不知道是什么类型,需要强制转换,另外如果用swagger的话,在线文档解析不出data具体的字段内容,api接口使用者不知道应该怎么解决这个data对象,使用泛型可以很好的解决这个问题。

    package cn.watchvip.mall.common.core;
    
    import com.alibaba.fastjson.JSON;
    
    /**
     * 统一API响应结果封装
     */
    public class Result<T> {
        private int code;
        private String msg;
        private T data;
    
        public Result<T> setCode(ResultCode resultCode) {
            this.code = resultCode.code;
            return this;
        }
    
        public int getCode() {
            return code;
        }
    
        public Result<T> setCode(int code) {
            this.code = code;
            return this;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public Result<T> setMsg(String msg) {
            this.msg = msg;
            return this;
        }
    
        public T getData() {
            return data;
        }
    
        public Result<T> setData(T data) {
            this.data = data;
            return this;
        }
    
        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }
    }
    
    

    泛型方法

    泛型方法只需要在方法签名的返回值前加一个类似<T>形式即可,我们拿jdk中Collections工具类中的方法加以说明
    获取一个空集合:

    public static final <T> List<T> emptyList() {
         return (List<T>) EMPTY_LIST;
    }
    

    获取一个空Map

    public static final <K,V> Map<K,V> emptyMap() {
         return (Map<K,V>) EMPTY_MAP;
    }
    

    这样就可以获取任何类型的空集合了,如下:

    List<Boolean> emptyList = Collections.emptyList();
    Map<String, Integer> emptyMap = Collections.emptyMap();
    

    边界符 <T extends Comparable<T>>

    掌握了泛型类与泛型方法基本能满足大部分开发中的需要了,但还远不够,考虑这样一个需求,查找一个泛型数组中大于某个特定元素的个数,我们可以通过泛型方法这样实现:

    public static <T> int countGreaterThan(T[] anArray, T elem) {
            int count = 0;
            for (T e : anArray)
                if (e > elem)  // compiler error
                    ++count;
            return count;
        }
    

    但是这样很明显是错误的,因为除了short, int, double, long, float, byte, char等原始类型,其他的类并不一定能使用操作符>,所以编译器报错,那怎么解决这个问题呢?答案是使用边界符。

    public interface Comparable<T> {
        public int compareTo(T o);
    }
    

    做一个类似于下面这样的声明,这样就等于告诉编译器类型参数T代表的都是实现了Comparable接口的类,这样等于告诉编译器它们都至少实现了compareTo方法。

    public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
        int count = 0;
        for (T e : anArray)
            if (e.compareTo(elem) > 0)
                ++count;
        return count;
    }
    

    通配符<? extends T>

    不能往里存,只能往外取
    在讨论通配符之前,我们设想一下如果我们考虑实现一个可以对int,flow,bouble类型都可以进行做加法的方法,我想90%的人都会写出以下方法

    public static void add(Collection<Number> c){
        }
    
    
        public static void main(String[] args) {
            List<Integer> integers = Arrays.asList(1, 2, 3);
            List<Double> doubles = Arrays.asList(1.1, 2.1);
            add(integers);//不允许
            add(doubles);//不允许
        }
    

    众所周知,java是可以向上转型的,这也是我们面向接口编程的基础,如下:

    List list = new ArrayList();
    list = new LinkedList<>();
    

    但泛型却是不支持向上转的,

    List<Number> list = new ArrayList<Integer>();   //编译不通过
    

    虽然Integer是Number的子类,但在泛型中,两个是完全没有关系的。
    add方法中的参数Collection<Number>按我们熟悉的继承,不是可以传入Collection<Integer>,Collection<Double>么?答案是否定的,虽然,Integer和Double都是Number的子类,但在泛型中,Collection<Number>与Collection<Integer>,Collection<Double>没有任何关系。那么如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。即告诉编译器,使用者可以传入Collection<Integer>,也可以传入Collection<Double>,利用 <? extends Number> 形式的通配符,就可以实现泛型的向上转型:

    public static void add(Collection<? extends Number> c){
        }
    
    
        public static void main(String[] args) {
            List<Integer> integers = Arrays.asList(1, 2, 3);
            List<Double> doubles = Arrays.asList(1.1, 2.1);
            add(integers);
            add(doubles);
        }
    

    通配符

    只能往里存,不能往外取

    其它

    List,List<Object>,List<?>的区别
    List<?>,也就是没有任何限定。不做任何限制,跟不用类型参数的 List 有什么区别呢?
    List<?> list 表示 list 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。而单独的 List list ,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 Object,因此可以添加任何类型的对象,只不过编译器会有警告信息。

    相关文章

      网友评论

        本文标题:java中泛型的正确使用姿势

        本文链接:https://www.haomeiwen.com/subject/hnigbttx.html