Java,用反射列表排序

Java, list sorting with reflection

我想允许按 class 中的每个字段排序,而不必编写 switch/if 语句。 我的想法是通过名称找到与给定字符串值匹配的字段,然后使用 Stream API 整齐地排序。 IntelliJ 尖叫说我需要用 try-catch 包围它,所以它看起来不太整齐,但这并不重要,因为它不起作用。

    private List<MyEntity> getSorted(List<MyEntity> list, SearchCriteria criteria) {
        Field sortByField = findFieldInListByName(getFieldList(MyEntity.class), criteria.getSortBy());
        return list.stream().sorted(Comparator.comparing(entity-> {
            try {
                return (MyEntity) sortByField.get(entity);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return entity;
        })).collect(Collectors.toList());
    }

在 MyEntity class 中,我添加了 Comparable 接口,但我不确定 Compare() 的主体中应该包含什么,因为我不想指定如何比较对象,因为它会改变根据选择的排序。

编辑: 添加以下实体:

@Data
@Entity
@Table(name = "role_management", schema = "mdr")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode

public class MyEntity implements Comparable{

    @Id
    @Column(name = "uuid", unique = true, insertable = false, updatable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID uuid;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private UserEntity user;

    @Basic
    @NonNull
    @Column(name = "role")
    private String role;

    @Basic
    @Column(name = "action")
    @Enumerated(EnumType.STRING)
    private RoleAction action;

    @Basic
    @Column(name = "goal")
    @Enumerated(EnumType.STRING)
    private RoleGoal goal;

    @Column(name = "date")
    private LocalDateTime date;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "reporter_id", referencedColumnName = "uuid")
    private UserEntity reporter;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "authorizer_id", referencedColumnName = "uuid")
    private UserEntity authorizer;

    @Basic
    @Column(name = "ezd")
    private String ezd;

    @Basic
    @Column(name = "is_last")
    private boolean isMostRecent;

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

编辑 2: 我的代码基于 @Sweeper 解决方案:

UserEntity(可为空)

    @Override
    public int compareTo(UserEntity other) {
        if (other == null) {
            return 1;
        }
        return this.getMail().compareTo(other.getMail());
    }

比较器:

    public static Comparator getSortComparator(Field sortByField) {
        return Comparator.nullsLast(Comparator.comparing(entity -> {
            try {
                Object fieldValue = sortByField.get(entity);
                if (!(fieldValue instanceof Comparable<?>) && fieldValue != null) {
                    throw new IllegalArgumentException("...");
                }
                return (Comparable) fieldValue;
            } catch (IllegalAccessException e) {
                throw new MdrCommonException(e.getLocalizedMessage());
            }
        }));
    }

MyEntity 不应实施 Comparable 字段,您将根据这些字段对 MyEntity 个对象的列表进行排序,需要 Comparable。例如,如果您按字段 user 排序,这是一个 UserEntity,那么 UserEntity 是需要比较的东西,而不是 MyEntity.

lambda 的工作应该只是检查字段是否确实 Comparable,如果不是则抛出异常。

但是,由于您在编译时不知道字段的类型,因此您必须在此处使用原始类型。 comparing 调用如下所示:

Comparator.comparing(entity -> {
    try {
        Object fieldValue = sortByField.get(entity);

        // This check still passes if the type of fieldValue implements Comparable<U>, 
        // where U is an unrelated type from the type of fieldValue, but this is the
        // best we can do here, since we don't know the type of field at compile time
        if (!(fieldValue instanceof Comparable<?>) && fieldValue != null) {
            throw new IllegalArgumentException("Field is not comparable!");
        }
        return (Comparable)fieldValue;
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
})

您可以为任何 class 的任何字段自动创建比较器,但最好创建特定的比较器(将进行类型检查)。

您的实体是一个普通的 class,具有普通的字段,通常的 Java 分拣机器应该可以完成这项工作:

基本上,如果您为每个字段定义一个比较器(甚至是实体中的深层字段):

public final static Comparator<MyEntity> ByField1 = comparing(MyEntity::getField1);
public final static Comparator<MyEntity> ByField2 = comparing(MyEntity::getField2);
public final static Comparator<MyEntity> ByField3 = comparing(MyEntity::getField3);
public final static Comparator<MyEntity> ByDeep1 = comparing(a -> a.getField4().getDeep1());
public final static Comparator<MyEntity> ByDeep2 = comparing(a -> a.getField4().getDeep2());
public final static Comparator<MyEntity> ByDeep3 = comparing(a -> a.getField4().getDeep3());

您可以使用复杂的排序表达式进行排序:

data.stream()
        .sorted(ByField2.reversed().thenComparing(ByDeep2))
        .forEach(System.out::println);

一个完整的例子可以是

public static void main(String[] args) {

    List<MyEntity> data =
            Stream.of("Row1", "Row2").flatMap(field1 ->
            Stream.of(101, 102).flatMap(field2 ->
            Stream.of(true, false).flatMap(field3 ->
            Stream.of("Row1", "Row2").flatMap(deep1 ->
            Stream.of(101, 102).flatMap(deep2 ->
            Stream.of(true, false).map(deep3 ->
                new MyEntity(field1, field2, field3, new MyDeepField(deep1, deep2, deep3))))))))
            .collect(toList());

    data.stream()
            .sorted(ByField2.reversed().thenComparing(ByDeep2))
            .forEach(System.out::println);

}

@Getter
@Setter
@AllArgsConstructor
static class MyDeepField {
    private String deep1;
    private Integer deep2;
    private Boolean deep3;
}

@Getter
@Setter
@AllArgsConstructor
static class MyEntity {
    private String field1;
    private Integer field2;
    private Boolean field3;
    private MyDeepField field4;

    public final static Comparator<MyEntity> ByField1 = comparing(MyEntity::getField1);
    public final static Comparator<MyEntity> ByField2 = comparing(MyEntity::getField2);
    public final static Comparator<MyEntity> ByField3 = comparing(MyEntity::getField3);
    public final static Comparator<MyEntity> ByDeep1 = comparing(a -> a.getField4().getDeep1());
    public final static Comparator<MyEntity> ByDeep2 = comparing(a -> a.getField4().getDeep2());
    public final static Comparator<MyEntity> ByDeep3 = comparing(a -> a.getField4().getDeep3());

    @Override
    public String toString() {
        return "MyEntity{" +
                "field1='" + field1 + '\'' +
                ", field2=" + field2 +
                ", field3=" + field3 +
                ", deep1=" + field4.getDeep1() +
                ", deep2=" + field4.getDeep2() +
                ", deep3=" + field4.getDeep3() +
                '}';
    }
}

有输出

MyEntity{field1='Row1', field2=102, field3=true, deep1=Row1, deep2=101, deep3=true}
MyEntity{field1='Row1', field2=102, field3=true, deep1=Row1, deep2=101, deep3=false}
...
MyEntity{field1='Row2', field2=101, field3=false, deep1=Row2, deep2=102, deep3=true}
MyEntity{field1='Row2', field2=101, field3=false, deep1=Row2, deep2=102, deep3=false}

您的 SearchCriteria class 中的条件字段是 Comparator<MyEntity> 类型的某个字段或使用枚举或解析字符串表达式等的映射...