JAVA内存分页排序小框架

本文介绍了一种JAVA内存中的排序分页小框架设计,通过使用Stream简化了集合的排序和分页操作。框架允许动态指定排序字段,并处理了空值逻辑,提升了代码的复用性和易用性。示例展示了如何使用该框架对一个学生类集合进行按年龄排序和分页。此外,还解释了框架内部的逻辑,包括排序、分页的实现以及反射获取字段值的方法。
摘要由CSDN通过智能技术生成

JAVA内存分页排序小框架

对于程序员来说,排序和分页是再普通不过的需求,需要在内存中排序分页的情况也时有发生。
而自从有了JAVA8 Stream之后,对一个集合的排序分页貌似也在变的简单。

来看个例子,定义一个学生类:

public class Student {

    private String name;

    private Integer age;

    private String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

下面是对一个学生类集合的按照年龄字段排序,分页的写法。

    public static void main(String[] args) {
        ArrayList<Student> students = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            Student student = new Student();
            student.setName("小" + i);
            student.setAge(i);
            student.setSex(i % 2 == 0 ? "男" : "女");
            students.add(student);
        }
        int page = 1;//当前页
        int size = 10;//每页记录条数
        List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getAge))
                .limit(2).skip((page - 1) * size).limit(size)
                .collect(Collectors.toList());
    }

是不是已经很简单了吧。

最开始我们项目组也是这样写的,但是也有问题,根据什么字段进行排序往往是前端指定的,
Comparator.comparing(Student::getAge)中lamada表达式怎么写还要自己建立对应关系,不够友好。
而且我们项目中大量api用到了排序分页,,总是在写类似的代码,程序员的最大的成就当然就是干掉这些重复或者类似的事情了,一套小框架势在必行。

先来看一下小框架最终的使用情况:

        School school = new School();
        school.setStudents(students);
        school.doOrderOrPaging(1,10,"age","desc");

doOrderOrPaging方法中传入排序分页参数,students的排序分页就做好了。

怎么完成的呢?我们一步一步来看
首先,定义了一个抽象类AbstractMultiLevelSortVO,School类要继承该类,doOrderOrPaging就定义在AbstractMultiLevelSortVO中

public abstract class AbstractMultiLevelSortVO<T extends BaseCompareBean> {
	//子类提供实现方法,通常就是返回需要比较的集合
    @Transient
    public abstract List<T> provideSortList();
	//子类提供实现方法,通常就是覆盖子类中的集合
    @Transient
    public abstract void updateSortList(List<T> list);
    
    @Transient
    public List<T> doOrderOrPaging(String orderField, String orderType) {
        return doOrderOrPaging(new DefaultBasePageReq(orderField, orderType));
    }

    @Transient
    public List<T> doOrderOrPaging(Integer page, Integer size, String orderField, String orderType) {
        return doOrderOrPaging(new DefaultBasePageReq(page, size, orderField, orderType));
    }
    @Transient
    public List<T> doOrderOrPaging(BasePageReq req) {
        String orderField = req.getOrderField();
        String orderType = req.getOrderType();
 		//取子类中的集合
        Stream<T> tickerStream = provideSortList().stream();

        if (req.needOrder()) {
            Comparator<T> comparator = (o1, o2) -> doCompare(orderField, o1, o2, ASC.equalsIgnoreCase(orderType));
            if (!ASC.equalsIgnoreCase(orderType)) {
                comparator = comparator.reversed();
            }
            tickerStream = tickerStream.sorted(comparator);
        }

        if (req.needPaging()) {
            Integer pageSize = req.checkAndGetDefaultPageSize();
            Integer pageNum = req.checkAndGetDefaultPage();
            tickerStream = tickerStream.skip(pageSize * (pageNum - 1)).limit(pageSize);
        }
        List<T> list = tickerStream.collect(Collectors.toList());
        //更新子类中的集合
        updateSortList(list);
        return list;
    }
}

中间的逻辑比较简单,通过provideSortList拿到实现类对象的要排序分页的集合, needOrder时通过doCompare来进行order, needPaging就是简单地stream分页

接下主要看doCompare的实现,这个方法也定义到AbstractMultiLevelSortVO中


    @Transient
    public int doCompare(String orderField, T o1, T o2, boolean asc) {
        try {
            /*
                空边界之一
                先解决bean为空的情况,原则:无论升序还是降序,空bean无需比较,直接往后放
             */
            if (o1 == null || o1.abnormal()) {
                return thisValuePutBehind(asc);
            }
            if (o2 == null || o2.abnormal()) {
                return thisValuePutForward(asc);
            }
            Comparable v1 = o1.tryBestToFindCompareValue(orderField);
            Comparable v2 = o2.tryBestToFindCompareValue(orderField);
            return compareThinkAboutNull(v1, v2, asc);
        } catch (Exception e) {
            logger.error("获取指定比较字段失败,放弃比较。 orderField {} e {}", orderField, ExceptionUtils.getFullStackTrace(e));
            //若仍有意外发生,也先往后放
            return thisValuePutBehind(asc);
        }
    }

    /**
     * 根据排序方式 把当前值往前放
     *
     * @param asc
     * @return
     */
    private int thisValuePutForward(boolean asc) {
        return asc ? -1 : 1;
    }

    /**
     * 根据排序方式 把当前值往后放
     *
     * @param asc
     * @return
     */
    private int thisValuePutBehind(boolean asc) {
        return asc ? 1 : -1;
    }

    private int compareThinkAboutNull(Comparable v1, Comparable v2, boolean asc) {
        /*
            空边界之二
            解决最后真正比较字段的空问题,原则相同
         */
        if (v1 == null) {
            return thisValuePutBehind(asc);
        }
        if (v2 == null) {
            return thisValuePutForward(asc);
        }
        return v1.compareTo(v2);
    }

这之中有比较多的空值逻辑的判断处理,重要的就是通过具体比较对象的tryBestToFindCompareValue方法拿到具体值之后返回v1.compareTo(v2)就可以了
tryBestToFindCompareValue定义在一个接口中

import com.onepiece.shipelves.common.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

import java.beans.Transient;
import java.lang.reflect.Field;
import java.math.BigDecimal;

public interface BaseCompareBean {

    Logger logger = LoggerFactory.getLogger(BaseCompareBean.class);

    @Transient
    default Comparable tryBestToFindCompareValue(String fieldName) {
        Comparable value = tryBestToFindFieldValue(fieldName, Comparable.class);
        if (value instanceof String) {
            //有一些数值在响应报文里用的String,为了避免String.compare的影响,尝试转BigDecimal进行比较,若value不是数值,则依旧用原值比较
            try {
                return new BigDecimal(value.toString().trim().replaceAll(",", "").replaceAll("%", ""));
            } catch (Exception e) {
                logger.error("排序时String类型转换为BigDecimal类型失败 原始值 {}", value);
            }
        }
        return value;
    }

    /**
     * @param fieldName 支持snake风格,可自动转换为驼峰风格再取值;若失败,会尝试用蛇形风格直接取值
     * @return
     */
    @Transient
    default <T> T tryBestToFindFieldValue(String fieldName, Class<T> c) {
        //因为传入的排序字段为snake风格
        String humpName = StringUtil.toHumpStr(fieldName);
        try {
            return simpleFindFieldValue(humpName, c);
        } catch (Exception e) {
            try {
                return simpleFindFieldValue(fieldName, c);
            } catch (Exception e2) {
                logger.warn("找不到要目标排序字段 {}", fieldName);
                return null;
            }
        }
    }

    /**
     * 简单获取指定属性名的属性值
     *
     * @param fieldName
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    @Transient
    default <T> T simpleFindFieldValue(String fieldName, Class<T> c) throws IllegalAccessException {
        Field field = ReflectionUtils.findField(getClass(), fieldName);
        field.setAccessible(true);
        return c.cast(field.get(this));
    }

    default boolean abnormal() {
        return false;
    }
}

利用ReflectionUtils.findField可以拿到一个对象的某个字段的值,达到我们的目的。

所以最终在使用时需要
用来比较的集合中的对象也就是student实现BaseCompareBean对象
包装类School继承AbstractMultiLevelSortVO并实现两个对应方法 provideSortList updateSortList就可以了

public class Student implements BaseCompareBean 
public class School extends AbstractMultiLevelSortVO<Student> {

    private List<Student> students;

    private Integer count;

    @Override
    public List<Student> provideSortList() {
        return students;
    }

    @Override
    public void updateSortList(List<Student> list) {
        students = list;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }
}

最终达到调用一个方法传入简单参数就可以支持分页排序的目的

    School school = new School();
        school.setStudents(students);
        school.doOrderOrPaging(1,10,"age","desc");
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值