[英]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
的类型为Integer
且size
为BigDecimal
的类,而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.