使用Java 8按字段排序作为输入

jDu*_*ub9 8 java sorting reflection comparator java-8

我有一个REST端点和我想的UI来传递他们希望通过自己的排序结果中的字段名称"id","name"等我想出了下面,但真的想使用反射/泛型所以这可能是扩大到包括每我的项目中的对象.

如果我想为100个不同的类提供相同的功能,我觉得这个解决方案不易维护.

public static void sort(List<MovieDTO> collection, String field) {
    if(collection == null || collection.size() < 1 || field == null || field.isEmpty()) {
        return;
    }

    switch(field.trim().toLowerCase()) {
        case "id": 
            collection.sort(Comparator.comparing(MovieDTO::getId));
            break;
        case "name": 
            collection.sort(Comparator.comparing(MovieDTO::getName));
            break;
        case "year": 
            collection.sort(Comparator.comparing(MovieDTO::getYear));
            break;
        case "rating": 
            collection.sort(Comparator.comparing(MovieDTO::getRating));
            break;
        default: 
            collection.sort(Comparator.comparing(MovieDTO::getId));
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

关于如何更好地实现这一点的任何想法,以便它可以扩展到几乎没有维护的企业应用程序?

And*_*lko 9

原帖

我不会重复评论中所说的一切.那里有很好的想法.我希望你明白反思不是这里的最佳选择.

我建议保留一个Map<String, Function<MovieDTO, String>>,其中键是一个field名称,值是一个映射器movie -> field:

Map<String, Function<MovieDTO, String>> extractors = ImmutableMap.of(
        "id", MovieDTO::getId,
        "name", MovieDTO::getName
);
Run Code Online (Sandbox Code Playgroud)

然后,集合可以像:

Function<MovieDTO, String> extractor = extractors.getOrDefault(
        field.trim().toLowerCase(),
        MovieDTO::getId
);
collection.sort(Comparator.comparing(extractor));
Run Code Online (Sandbox Code Playgroud)

玩反射

正如我所承诺的,我正在添加我的注释处理愿景来帮助你.请注意,这不是一个你必须牢牢坚持的版本.这是一个很好的开始.

我宣布了2个注释.

  • 澄清getter名称(如果未指定,<get + FieldName>则为模式):

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    @interface FieldExtractor {
    
        String getterName();
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 要为类定义所有可能的排序键:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @interface SortingFields {
    
        String[] fields();
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

该课程MovieDTO的外观如下:

@SortingFields(fields = {"id", "name"})
class MovieDTO implements Comparable<MovieDTO> {

    @FieldExtractor(getterName = "getIdentifier")
    private Long id;
    private String name;

    public Long getIdentifier() {
        return id;
    }

    public String getName() {
        return name;
    }

    ...

}
Run Code Online (Sandbox Code Playgroud)

我没有更改sort方法签名(但是,它会简化任务):

public static <T> void sort(List<T> collection, String field) throws NoSuchMethodException, NoSuchFieldException {
    if (collection == null || collection.isEmpty() || field == null || field.isEmpty()) {
        return;
    }

    // get a generic type of the collection
    Class<?> genericType = ActualGenericTypeExtractor.extractFromType(collection.getClass().getGenericSuperclass());

    // get a key-extractor function
    Function<T, Comparable<? super Object>> extractor = SortingKeyExtractor.extractFromClassByFieldName(genericType, field);

    // sort
    collection.sort(Comparator.comparing(extractor));
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我需要介绍两个类来完成:

class ActualGenericTypeExtractor {

    public static Class<?> extractFromType(Type type) {
        // check if it is a waw type
        if (!(type instanceof ParameterizedType)) {
            throw new IllegalArgumentException("Raw type has been found! Specify a generic type for further scanning.");
        }

        // return the first generic type
        return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
    }

}

class SortingKeyExtractor {

    @SuppressWarnings("unchecked")
    public static <T> Function<T, Comparable<? super Object>> extractFromClassByFieldName(Class<?> type, String fieldName) throws NoSuchFieldException, NoSuchMethodException {
        // check if the fieldName is in allowed fields
        validateFieldName(type, fieldName);

        // fetch a key-extractor method
        Method method = findExtractorForField(type, type.getDeclaredField(fieldName));

        // form a Function with a method invocation inside
        return (T instance) -> {
            try {
                return (Comparable<? super Object>) method.invoke(instance);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        };
    }

    private static Method findExtractorForField(Class<?> type, Field field) throws NoSuchMethodException {
        // generate the default name for a getter
        String fieldName = "get" + StringUtil.capitalize(field.getName());

        // override it if specified by the annotation
        if (field.isAnnotationPresent(FieldExtractor.class)) {
            fieldName = field.getAnnotation(FieldExtractor.class).getterName();
        }

        System.out.println("> Fetching a method with the name [" + fieldName + "]...");

        return type.getDeclaredMethod(fieldName);
    }

    private static void validateFieldName(Class<?> type, String fieldName) {
        if (!type.isAnnotationPresent(SortingFields.class)) {
            throw new IllegalArgumentException("A list of sorting fields hasn't been specified!");
        }

        SortingFields annotation = type.getAnnotation(SortingFields.class);

        for (String field : annotation.fields()) {
            if (field.equals(fieldName)) {
                System.out.println("> The given field name [" + fieldName + "] is allowed!");
                return;
            }
        }

        throw new IllegalArgumentException("The given field is not allowed to be a sorting key!");
    }

}
Run Code Online (Sandbox Code Playgroud)

它看起来有点复杂,但这是推广的代价.当然,还有改进的余地,如果你指出它们,我很乐意回顾一下.

  • 适合中期解决方案.适度可扩展,易于测试.提供了一种在某种程度上轻松验证输入的方法,并且映射键可以是命名空间(例如`movie.id`),以允许对不同类型的对象进行排序.+1 (3认同)
  • 这是很好的代码,+ 1.但是,我想你在这里重新发明轮子.为此我会使用优秀的[Reflections库](https://github.com/ronmamo/reflections),它允许您扫描整个类路径以获取带注释的方法.然后,您可以从那里创建函数. (3认同)