Java反射实现根据field对JavaBean的排序

  1. 起源:
    前几天抄python的orm实现,里面有很多内省的用法,减少代码量。想到实验室SSH项目里面,有这么一个需求——一个包含很多javabean的list, 要根据javabean的不同的field对list排序。
  2. 实现之路:
    2.1 从泛型是获取不了javabean的class对象的,取巧了一下,用javabean的实例来getClass()
    2.2 尝试了用Field.get(Object)来获取javabean的field,后来实验出来,借Method.invode()来调用javabean一般都有的getter()函数稍快一点。到百万级的调用能有几百毫秒的差别。
    2.3 主要还是调用Collections.sort(list, Comparator)方法来排序,发现竟然比直接用经典的直接写闭包Comparator慢了10倍。简直不能人,遂寻找性能优化的方法。
    2.4 立竿见影的setAccessible(true)。反射调用/访问之所以慢,很大一部分原因是检查类型,检查能不能访问。设置了true后,关闭了对可访问性的检查。这样设置后,还能用Field.get()的方法访问的javabean的私有成员呢。
    2.5 上面说到慢的一个原因还在于类型检查,参数装箱和封箱。业界有得做法是操作字节码,我感觉那样太复杂了,在纯java语言范围内,还找到了一个提升的点。在Collections.sort(list, Comparator)里面的compare函数,如果用反射来调用,起码需要对形参的两个Object取field,小计:两次getter调用;一次o1.comparaTo(o2)的调用,小计:一次compareTo调用。sort函数的一次比较就要3次Method.invoke(), 虽然免去了访问检查,反射调用还是比较慢。
    2.6 MethodHandle.invokeExact(),用更加轻量级的调用方法,这个比Method.invoke()更轻量,更快。原因在于前者在调用前,返回值、参数列表和调用对象都必须从“符号上”确定,自然就免去了类型检查的时间。什么叫“符号上”呢? 比方说 你知道invokeExact作用对象是String, 你不能 String.class.cast(Object_instance)来放入invokeExact. 有鉴于此,只有 compareTo函数是可以用invokeExact的, 返回值是确定的int, 作用对象是Comparable,参数是Comparable或Object。 getter函数就不行,getter返回的不知道是什么东东。
    2.7 Talk is cheap, show me the code!
    code in github
/**
     * sort a list containing JavaBean according specific key( field ).  
     * Mostly, sortByField take ~1.5 time as much as Traditional implementation when list.size() > 100K
     * 
     * @param list:
     *            list to be sorted
     * @param fieldName:
     *            sort list according this field
     * @param order:
     *            asc(default) or desc
     * @author Tony
     * @email 360517703@163.com
     * @Time 2015-08-14 11:12
     */
    public static <E> void sortByField(List<E> list, String fieldName, String order) {
        if (list == null || list.size() < 2) { // no need to sort
            return;
        }
        if (fieldName == null || fieldName.trim().equals(""))
            return; // won't sort if fieldName is null or ""
        // get actual class of generic E
        Class<?> eClazz = null; // use reflect to get the actual class
        boolean isAllNull = true; // default all elements are null
        for (E e : list) {
            if (e != null) {
                isAllNull = false;
                eClazz = e.getClass();
                break;
            }
        }
        if (isAllNull)
            return; // no need to sort, because all elements are null
        // check fieldName in Class E
        Field keyField = null; // the <fieldName> Field as sort key
        try {
            keyField = eClazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e1) {
            e1.printStackTrace();
            System.out.println("The List<E> doesn't contain fieldName. That is "
                    + String.format("%s has no Field %s.", eClazz, fieldName));
        } catch (SecurityException e1) {
            e1.printStackTrace();
            System.out.println("deny access to  class or field.");
        }
        // check field is either Comparable
        Class<?> fieldClazz = keyField.getType();
        boolean isComparable = Comparable.class.isAssignableFrom(fieldClazz);
        if (isComparable == false)
            return; // if the class of fieldName is not comparable, don't sort

        // try to use getter method to get field first. Because a little faster
        // than Field.get(Object)
        StringBuilder getterName; // adapt to JavaBean getter method
        if (fieldClazz.getSimpleName().equals("Boolean")) {
            getterName = new StringBuilder("is");
        } else {
            getterName = new StringBuilder("get");
        }
        char[] cs = fieldName.toCharArray();
        if (cs[0] >= 'a' && cs[0] <= 'z')
            cs[0] -= 32; // change the first char to lowerCase
        getterName.append(cs);
        Method getterMethod = null;
        try {
            getterMethod = eClazz.getDeclaredMethod(getterName.toString());
        } catch (NoSuchMethodException | SecurityException e1) {
            // System.out.println("Field " + fieldName + " has no " + getterName
            // + "() . ");
            // e1.printStackTrace();
        }
        /*
         * // get compare method for specified field. //Abandoned. Because
         * MethodHandle.invokeExact() is a little faster than Method.invoke()
         * Method cmpMethod = null; try { cmpMethod =
         * fieldClazz.getDeclaredMethod("compareTo", fieldClazz); } catch
         * (NoSuchMethodException | SecurityException e1) { System.out.println(
         * "deny access to class or method(comparaTo).\nImpossible to show errorStrackTrace Because of Comparable check"
         * ); e1.printStackTrace(); cmpMethod.setAccessible(true); }
         */
        Cmp<E> cmp;
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType type = MethodType.methodType(int.class, Object.class);
        MethodHandle mh = null;
        try {
            mh = lookup.findVirtual(Comparable.class, "compareTo", type);
        } catch (NoSuchMethodException | IllegalAccessException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        if (getterMethod != null) {
            // cmpMethod.setAccessible(true);
            getterMethod.setAccessible(true);
            cmp = new Cmp<E>(mh, getterMethod);
        } else { // if cannot find getter method, use reflect to get specified
                    // field
            keyField.setAccessible(true);
            cmp = new Cmp<E>(mh, keyField);
        }

        if (order.equalsIgnoreCase("desc")) {
            Collections.sort(list, Collections.reverseOrder(cmp));
            return;
        }
        Collections.sort(list, cmp);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值