繁体   English   中英

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

[英]Sorting a collection in a generic method in 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的类型为IntegersizeBigDecimal的类,而list是一种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计算结果为Object

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

一种简单的方法,就是不必依赖反射魔法,就是为所有具有相同字段且具有与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;
    }

}

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

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语句来删除重复的代码。)。 此外,您可能还想添加一个默认子句。

使用接口:

public interface Sizable {
    BigDecimal getSize();
}

public interface Id {
    int getId();
}

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

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

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

class Shoe

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

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

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

如果你想创建一个复合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;
    }
}

暂无
暂无

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

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