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

时间:2015-10-12 15:15:44

标签: java sorting generics java-8

以下方法执行排序。

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

上述方法返回的列表按以下方式使用。

// 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包含名为id的{​​{1}}类型Integersize类型BigDecimallist类型{{1} }}

由于有几个其他类具有相同的数据类型的相同字段,我希望这个方法是通用的,所以它必须只被定义一次,

List<Entity>

但是这样做,像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; } 这样的表达式不会编译得那么明显,因为泛型类型参数T::getId的计算结果为T

有没有办法在不知道实际类类型的情况下对代码进行编码,以便在需要时可以防止此方法在任何地方重复?

4 个答案:

答案 0 :(得分:4)

一种简单的方法,不必依赖反射魔法,就是为具有相同字段的所有类型引入一个公共接口,其数据类型与Entity相同。

请考虑以下IdSize接口与以下Entity

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

}

然后你可以使你的方法像这样通用:

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

(我重构了一些switch-case语句以删除重复的代码。)。此外,您可能还想添加一个默认子句。

答案 1 :(得分:3)

使用接口:

public interface Sizable {
    BigDecimal getSize();
}

public interface Id {
    int getId();
}

让您的类实现这些接口并在通用方法中使用它们:

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

答案 2 :(得分:2)

你可能需要一些更有活力的东西。一些注释可能会有所帮助

class Shoe

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

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

给定任何类,您可以反映要显示的列,可以排序的列(&#34; id&#34;,...)和列的值("getId()",...) 。

答案 3 :(得分:2)

如果你想创建一个复合Comparator,那么首先填写List是没有意义的。只需在一次操作中完成:

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

这适用于任意类型,但需要为类型提供现有比较器的映射。但是这个map不是限制,它实际上改进了操作,因为它删除了硬编码的现有命名属性比较器集。您可以使用任意类型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);

param是您问题的有序地图,从属性名称映射到"asc""desc"。保存预定义比较器的map可以在初始化代码中创建一次,然后重新使用。

创建代码看起来并不复杂,值得实现动态解决方案,但是,如果你仍然希望这样做,下面是为任意类生成这样一个映射的代码:

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