java T和Function 的使用方式,学习写泛型工具函数 (教程),内含:list随机取、list分发、list变tree、list变map 等工具函数

前言

T和Function 的使用,是一名JAVA工程师通向JAVA架构师的必经之路。

若你在研究源码和别人的代码里发现了 T 和Function ,觉得不知所云,那么本篇博客,将带你掌握他们。

学会写泛型工具函数,让代码可读性更强、复用性强更雅 。

用JDK 1.8 以下的可以走开了,不用看了。

开篇问题

我有一个如下代码的类,我想打印一下某个变量的值,但是有时候想打印 name1 ,有时候想打印 namename4 ,

有时候想打印 name3  ...等等。按照传统的方式需要写4函数才能完成需求吧。这也太烦了,怎么能省事呢?

public class Test {
    private String  name1;
    private String  name2;
    private String  name3;
    private String  name4;

    public String getName1() {
        return name1;
    }

    public void setName1(String name1) {
        this.name1 = name1;
    }

    public String getName2() {
        return name2;
    }

    public void setName2(String name2) {
        this.name2 = name2;
    }

    public String getName3() {
        return name3;
    }

    public void setName3(String name3) {
        this.name3 = name3;
    }

    public String getName4() {
        return name4;
    }

    public void setName4(String name4) {
        this.name4 = name4;
    }
}

方式一 反射:

反射很多人都会,但感觉有点烦,通过传进去的字符串找到属性然后打印,不推荐。

 public static void main(String[] args) {
        Test test = new Test();
        test.setName1("user1");
        test.setName2("user2");
        test.setName3("user3");
        test.setName4("user4");
        printName(test,"name1");

    }

    private static void printName(Test test, String name1) {
        Class<? extends Test> tClass = test.getClass();
        Field[] field = tClass.getDeclaredFields();
        for (int i = 0; i < field.length; i++) {
            Field f = field[i];
            //设置可以访问私有变量
            f.setAccessible(true);
            if(f.getName().equals(name1)){
                try{
                    PropertyDescriptor pd = new PropertyDescriptor(f.getName(), tClass);
                    Method readM = pd.getReadMethod();//获得读方法
                    String string = (String)readM.invoke(test);
                    System.out.println(string);
                }catch (Exception e){
                    System.out.println(e.getMessage());
                }
            }
        }
    }

方式二  别人的工具类:

在《Java常用的工具类 》里有提到,是 Apache Commons 下的 BeanUtils。

看这也不错,但是这个别人的东西。需要去管理jar包版本吧。引了一个包,可能带了一会我不需要的。

还有就是 ,传入一个字符串作为参数,来对应,总是不好的,万一对应不上会报错的,编译并不能帮我们检查出错误。

main函数不变稍作改造。

    import org.apache.commons.beanutils.BeanUtils;


    private static void printName(Test test, String name1) {
        try{
            String string = BeanUtils.getProperty(test, name1);
            System.out.println(string);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

方式三 泛型式函数:

我们不在以属性名作为入参,而是采用泛型函数的形式,把属性对应的get方法作为入参。

代码如下:

  public static void main(String[] args) {
        Test test = new Test();
        test.setName1("user1");
        test.setName2("user2");
        test.setName3("user3");
        test.setName4("user4");
        printName(test,Test::getName1);

    }

 private static void printName(Test test, Function<Test,String> getName1) {
    String apply = getName1.apply(test);
    System.out.println(apply);
 }

这种方式不需要处理异常,若是根本没有getName1() 则编译都过不了。

编译器好的话,写的时候,打上 "Test::" 之后直接提示可选的函数名,是非常好的编码体验。

Function<Test,String>  这个种接值方式是用在get方法时的,使用时用 apply 函数取到值。

 问题升级

我们还不想局限在Test这个类,想写成通用的类,还不想局限字符串类型的属性打印。

总之 ,想任何类的任意属性都可以调用这个方法

我们只需要扩展我们的函数即可,这就需要用到 T 泛型了。

改造如下:

   public static void main(String[] args) {
        Test test = new Test();
        test.setName1("user1");
        test.setName2("user2");
        test.setName3("user3");
        test.setName4("user4");
        printName(test,Test::getName2);

    }

    private static <T>  void printName(T test, Function<T,Object> getName1) {
        Object object = getName1.apply(test);
        if(object != null){
            String apply = object.toString();
            System.out.println(apply);
        }
    }

至此,教学部分结束。

下面是一下,经典工具类例子展示:

例子一:

我知道python里有个list列表随机取一个的方法。我用java实现一个。

 public static <T> T randomChoice(List<T> data) {
        //随机取一个工具函数,不理解泛型的,把T换成String
        int num = (int) (Math.random() * data.size());
        return data.get(num);
    }
————————————————
版权声明:本文为CSDN博主「wang_lianjie」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wang_lianjie/article/details/103194523

这段代码出自我的另一篇博客,讲文章生成器的,中间一处使用过。

例子二:

list列表分发,就是把长list切换成多个小list。

主要用在sql拼 in () 里的东西时,上限1000个,不得不切分后调用。

还有,很多数据需要多线程处理时,100多万数据,分100个线程分别处理时,先切分好了再分别处理(这个有机会单开个博客写)。

   public static <T> List<List<T>> divSublist(List<T> originalList, int len) {
        List<List<T>> answer = new ArrayList();
        int size = originalList.size();
        int count = (size + len - 1) / len;

        for(int i = 0; i < count; ++i) {
            answer.add(originalList.subList(i * len, (i + 1) * len > size ? size : len * (i + 1)));
        }

        return answer;
    }

例子三 : 

是一个后端面临很普遍的问题,listToTree,list表变tree格式。

他有个前提就是 : list里的对象需要有三个东西 : 本身的ID ,父级的ParentID , 以及一个子集list 列表。

工具函数如下: 觉得有用点个赞支持一下

 public static <T> List<T> toTree(List<T> data,
                                     Function<T, Object> getIdFunction,
                                     Function<T, Object> getParentIdFunction,
                                     BiConsumer<T, List<T>> addChildFunction) {
        if(data == null){
            throw new ListToTreeException("树目标节点为空");
        }
        List<T> result = new ArrayList<>();
        Map<String, List<T>> tmpMap = new HashMap<>();
        //按“parentId值”分组
        for (T record : data) {
            Object parentIdValue = getParentIdFunction.apply(record);
            if (parentIdValue == null) {
                result.add(record);
            } else {
                String parentId = String.valueOf(parentIdValue);
                if (!tmpMap.containsKey(parentId)) {
                    tmpMap.put(parentId, new ArrayList<>());
                }
                tmpMap.get(parentId).add(record);
            }
        }
        //正式处理
        for (T record : data) {
            String idValue = String.valueOf(getIdFunction.apply(record));
            if (tmpMap.containsKey(idValue)) {
                addChildFunction.accept(record, tmpMap.get(idValue));
                tmpMap.remove(idValue);
            }
        }
        if (tmpMap.size() > 0) {
            throw new ListToTreeException("以下节点,存在上下级断档,无法正常显示[" + tmpMap + "]");
        }
        return result;
    }

调用时代码如下,大家可以试一试。

TreeUtil.toTree(answer,
                    CodeBean::getCode,
                    CodeBean::getParentCode,
                    CodeBean::setChildren);

注意: BiConsumer<T, List<T>> 这个种接值方式是用在set方法时的,使用时用 accept 函数。

例子四

list转map,我用过很多次 。

用途很多,我在 《Map格式化 》里,提到过好处,可以解决很多哈希表问题。

实际工作中,需要转码啊,java代码做对应啊,map格式化频繁使用。

/**
     * 提取list的两个属性,组成map
     * 默认不覆盖
     * @param data
     * @param getKeyFunction
     * @param getValueFunction
     * @param <T>
     * @return
     */
public static <T> Map<String , String> toMap(List<T> data, Function<T, Object> getKeyFunction, Function<T, Object> getValueFunction) {
        Map<String , String> res = new HashMap<>();
        if(data != null && !data.isEmpty()){
            for(T item : data){
                if(getKeyFunction.apply(item)==null||getValueFunction.apply(item)==null){
                    continue;
                }
                String key = getKeyFunction.apply(item).toString();
                String value = getValueFunction.apply(item).toString();
                if (!res.containsKey(key)){
                    res.put(key,value);
                }
            }
        }
        return res;
    }

欢迎各位斧正,提出异议、建议、以及技术问题。

发布了51 篇原创文章 · 获赞 35 · 访问量 6646

猜你喜欢

转载自blog.csdn.net/wang_lianjie/article/details/103204140