按输入的字段 Java 8 排序

Sorting with Java 8 by Field given as Input

我有一个 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;
    }
}

关于如何更好地实现它以便将其扩展为适用于几乎不需要维护的企业应用程序的任何想法?

原版post

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

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

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

然后,集合可以这样排序:

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

玩反射

正如我所承诺的,我正在添加我对注释处理的看法来帮助您。请注意,这不是您必须牢牢坚持的版本。这是一个很好的起点。

我声明了 2 个注释。

  • 澄清一个getter名称(如果没有指定,<get + FieldName>就是模式):

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    @interface FieldExtractor {
    
        String getterName();
    
    }
    
  • 为 class 定义所有可能的排序键:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @interface SortingFields {
    
        String[] fields();
    
    }
    

class 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;
    }

    ...

}

我没有更改 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));
}

如您所见,我需要介绍 2 classes 才能完成:

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!");
    }

}

看起来有点复杂,但这是泛化的代价。当然还有改进的地方,如果你指出来,我很乐意过来看看。

我会使用 jOOR 库和以下代码段:

public static <T, U extends Comparable<U>> void sort(final List<T> collection, final String fieldName) {
    collection.sort(comparing(ob -> (U) on(ob).get(fieldName)));
}

好吧,您可以创建一个 Function 对您的类型通用的:

private static <T, R> Function<T, R> findFunction(Class<T> clazz, String fieldName, Class<R> fieldType) throws Throwable {

    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodType getter = MethodType.methodType(fieldType);
    MethodHandle target = caller.findVirtual(clazz, "get" + fieldName, getter);
    MethodType func = target.type();
    CallSite site = LambdaMetafactory.metafactory(caller,
            "apply",
            MethodType.methodType(Function.class),
            func.erase(),
            target,
            func);

    MethodHandle factory = site.getTarget();
    Function<T, R> function = (Function<T, R>) factory.invoke();

    return function;
}

唯一的问题是您需要通过最后一个参数了解类型 fieldType

考虑使用来自 apache commons 的 ComparatorChain。

看看这个答案 看看如何使用它。