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>
类型的某个字段或使用枚举或解析字符串表达式等的映射...
我想允许按 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>
类型的某个字段或使用枚举或解析字符串表达式等的映射...