简体   繁体   English

在Java 8中按通用方法对集合进行排序

[英]Sorting a collection in a generic method in Java 8

The following method performs ordering. 以下方法执行排序。

public List<Comparator<Entity>> sort(Map<String, String> map) {
    List<Comparator<Entity>> list = new ArrayList<Comparator<Entity>>();

    for (Map.Entry<String, String> entry : map.entrySet()) {
        boolean sortOrder = entry.getValue().equalsIgnoreCase("asc");

        switch (entry.getKey()) {
            case "id":
                list.add(sortOrder ? Comparator.comparing(Entity::getId) : Comparator.comparing(Entity::getId, Comparator.reverseOrder()));
                break;
            case "size":
                list.add(sortOrder ? Comparator.comparing(Entity::getSize) : Comparator.comparing(Entity::getSize, Comparator.reverseOrder()));
                //break;
        }
    }

    return list;
}

The list being returned by the above method is used in the following way. 上述方法返回的列表按以下方式使用。

// map is initialized somewhere based on client's interactions with sorting.
// Based on client's interactions, map may be empty or it may contain one or more ordering fields.

if (MapUtils.isNotEmpty(map)) {  // map = new LinkedHashMap<String, String>();

    List<Comparator<Entity>> comparators = sort(map);
    Comparator<Entity> comparator = comparators.remove(0);

    for (Comparator<Entity> c : comparators) {
        comparator = comparator.thenComparing(c);
    }

    list = list.stream().sorted(comparator).collect(Collectors.toList());
} else {
    // This is the default ordering.
    list = list.stream().sorted(Comparator.comparing(Entity::getId).reversed()).collect(Collectors.toList());
}

Entity contains two fields named id of type Integer and size of type BigDecimal and list is a type of List<Entity> . Entity包含两个名为id的类型为IntegersizeBigDecimal的类,而list是一种List<Entity>

Since there are several other classes having the same fields with the same datatypes, I want this method to be generic so that it has to be defined only once like so, 由于有几个其他类具有相同的数据类型的相同字段,我希望这个方法是通用的,所以它必须只被定义一次,所以,

public <T extends Object> List<Comparator<T>> sort(Map<String, String> map, Class<T> clazz) {
    List<Comparator<T>> list = new ArrayList<Comparator<T>>();

    // Sorting logic.

    return list;
}

But doing so, expressions like T::getId will not compile as obvious, since the generic type parameter T evaluates to Object . 但是这样做,像T::getId这样的表达式将无法编译,因为泛型类型参数T计算结果为Object

Is there a way to code sorting without knowing the actual class type so that this method can be prevented from being repeated everywhere, when it is needed? 有没有办法在不知道实际类类型的情况下对代码进行编码,以便在需要时可以防止此方法在任何地方重复进行?

A simple way, without having to rely on reflection magic, is to introduce a common interface for all the types having the same fields with the same datatypes as Entity . 一种简单的方法,就是不必依赖反射魔法,就是为所有具有相同字段且具有与Entity相同数据类型的类型引入公共接口。

Consider the following IdSize interface with the following Entity . 请考虑以下具有以下Entity IdSize接口。

interface IdSize {
    Integer getId();
    BigDecimal getSize();
}

class Entity implements IdSize {

    private Integer id;
    private BigDecimal size;
    @Override
    public Integer getId() {
        return id;
    }
    @Override
    public BigDecimal getSize() {
        return size;
    }

}

Then you can make your method generic like this: 然后你可以像这样使你的方法通用:

public <T extends IdSize> List<Comparator<T>> sort(Map<String, String> map) {
    List<Comparator<T>> list = new ArrayList<Comparator<T>>();
    for (Map.Entry<String, String> entry : map.entrySet()) {
        boolean sortOrder = entry.getValue().equalsIgnoreCase("asc");
        Comparator<T> comparator = null;
        switch (entry.getKey()) {
            case "id":
                comparator = Comparator.comparing(IdSize::getId);
                break;
            case "size":
                comparator = Comparator.comparing(IdSize::getSize);
                break;
            default: // do something here, throw an exception?
        }
        list.add(sortOrder ? comparator : comparator.reversed());
    }
    return list;
}

(I refactored a little the switch-case statement to remove the duplicated code.). (我重构了一些switch-case语句来删除重复的代码。)。 Also, you might want to add a default clause. 此外,您可能还想添加一个默认子句。

Use interfaces: 使用接口:

public interface Sizable {
    BigDecimal getSize();
}

public interface Id {
    int getId();
}

Have your classes implement those interface and use them in your generic methods: 让您的类实现这些接口并在通用方法中使用它们:

public <T extends Id & Sizable> List<Comparator<T>> sort(Map<String, String> map) {
    // ...
}

You'll probably need something more dynamic. 你可能需要一些更有活力的东西。 Some annotations may help 一些注释可能会有所帮助

class Shoe

    @Column("id")
    @Sortable
    public int getId(){ ... }

    @Column("Description")
    public String getDescription(){...}

Given any class, you can reflect on columns to display, columns that can be sorted ("id", ...), and values of columns ( "getId()" , ...). 给定任何类,您可以反映要显示的列,可以排序的列(“id”,...)和列的值( "getId()" ,...)。

If you want to create a compound Comparator anyway, there is no point in filling a List first. 如果你想创建一个复合Comparator ,那么首先填充List是没有意义的。 Just do it in one operation: 只需在一个操作中执行:

public static <T> Comparator<T> getOrdering(
    Map<String, String> map, Map<String,Comparator<T>> defined) {

    return map.entrySet().stream().map(e -> {
        Comparator<T> c=defined.get(e.getKey());
        return e.getValue().equalsIgnoreCase("asc")? c: c.reversed();
    })
    .reduce(Comparator::thenComparing)
    .orElseThrow(()->new IllegalArgumentException("empty"));
}

This works for arbitrary types but requires to provide a map of existing comparators for a type. 这适用于任意类型,但需要提供类型的现有比较器的映射。 But this map isn't a restriction, it actually improves the operation as it removes the hardcoded set of existing named property comparators. 但是这个map不是一个限制,它实际上改进了操作,因为它删除了硬编码的现有命名属性比较器集。 You can use it with an arbitrary type, Entity being exemplary here, as follows: 您可以使用任意类型, Entity在这里示例,如下所示:

Map<String,Comparator<Entity>> map=new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
map.put("id", Comparator.comparing(Entity::getID));
map.put("size", Comparator.comparing(Entity::getSize));

Comparator<Entity> cmp=getOrdering(param, map);

whereas param is the ordered map of your question, mapping from property name to either "asc" or "desc" . param是你问题的有序映射,从属性名称映射到"asc""desc" The map holding the predefined comparators can be created once in initialization code and then be re-used. 保存预定义比较器的map可以在初始化代码中创建一次,然后重新使用。

The creation code doesn't look so complicated that it deserves implementing a dynamic solution, however, if you still wish to do it, here is the code to generate such a map for arbitrary classes: 创建代码看起来并不复杂,值得实现动态解决方案,但是,如果您仍希望这样做,下面是为任意类生成这样一个映射的代码:

public final class DynamicComparators<T> {
    public static <T> Map<String,Comparator<T>> getComparators(Class<T> cl) {
        return CACHE.get(cl).cast(cl).comps;
    }

    private static final ClassValue<DynamicComparators> CACHE
                         =new ClassValue<DynamicComparators>() {
        @Override protected DynamicComparators computeValue(Class<?> type) {
            return new DynamicComparators<>(type);
        }
    };
    private final Class<T> theClass;
    private final Map<String, Comparator<T>> comps;

    private DynamicComparators(Class<T> cl) {
        theClass=cl;
        Map<String,Comparator<T>> map=new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        try {
            BeanInfo bi=Introspector.getBeanInfo(cl);
            MethodHandles.Lookup l=MethodHandles.lookup();
            MethodType invoked=MethodType.methodType(Function.class);
            for(PropertyDescriptor pd: bi.getPropertyDescriptors()) {
                Method m=pd.getReadMethod();
                if(m==null) continue;
                Class<?> t=m.getReturnType();
                if(!t.isPrimitive() && !Comparable.class.isAssignableFrom(t))
                    continue;
                MethodHandle mh=l.unreflect(m);
                MethodType mt=mh.type();
                @SuppressWarnings("unchecked")Comparator<T> cmp
                  = Comparator.comparing((Function<T,Comparable>)LambdaMetafactory
                    .metafactory(l, "apply", invoked, mt.generic(), mh, mt)
                    .getTarget().invokeExact());
                map.put(pd.getName(), cmp);
            }
        } catch(Throwable ex) {
            throw new RuntimeException(ex);
        }
        this.comps=Collections.unmodifiableMap(map);
    }
    @SuppressWarnings("unchecked") <U> DynamicComparators<U> cast(Class<U> cl) {
        if(cl!=theClass) throw new ClassCastException();
        return (DynamicComparators<U>)this;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM