概念的证明:我如何创建一个通用的比较器方法与反射



我有很多这样的类:

Class Person {
  protected String name;
  protected String surname;
  ...getters and setters...
}

我想按姓名或姓氏订购一本人物选集。实际上我只是这样做:

Collections.sort(listofpersons, new Comparator<Person>(){
  @Override 
  public int compare(Person p1, Person p2) { return p1.getName().compareTo(p2.getName()); }      
})

Collections.sort(listofpersons, new Comparator<Person>(){
  @Override 
  public int compare(Person p1, Person p2) { return p1.getSurname().compareTo(p2.getSurname()); }      
})

我正在尝试实现一个通用比较器,如:

MyUtils.sort(listofpersons,"getName");
MyUtils.sort(listofpersons,"getSurame");

,我正试图理解如何用泛型和反射来做它,但我被卡住了。我正在这样做:

public static <T> void sortCollection(Collection<T> list, final String methodName) {
  Comparator<T> comparator = new Comparator<T>() {
    @Override
    public int compare(T o1, T o2) {
      try {
        String a=o1.getClass().getMethod(methodName).invoke(o1).toString();
        String b=o2.getClass().getMethod(methodName).invoke(o2).toString();
        return a.compareTo(b);
      } catch (Exception ex) {
        ...log somewhere...
        return 0;
      }
    }
  };
  Collections.sort(list, comparator);
}

暂时忘记catch Exception和String类型转换,重点是Collections.sort(Collection,Comparator)不存在,但我不知道要创建一个Comparator…我很想知道我的想法是否有意义(以及为什么),什么是实现它的正确方法。

谢谢!

问题在于sortCollection方法的签名。将"list"参数的类型由"Collection<T>"改为"List<T>"。换句话说,你的方法头应该是:

public static <T> void sortCollection(List<T> list, final String methodName)

这是因为Collections.sort()期望它的第一个参数是List(参见它的javadoc)

编辑:

关于你的实现,有一些你可以使用的通用/反射技巧。这是一个可能的替代impl。

@SuppressWarnings({ "unchecked", "rawtypes" })
public static<T> Comparator<T> 
  newMethodComparator(Class<T> cls, String methodName) throws Exception {    
  Method method = cls.getMethod(methodName);
  if (method.getParameterTypes().length != 0) 
    throw new Exception("Method " + method + " takes parameters");
  Class<?> returnType = method.getReturnType();
  if (!Comparable.class.isAssignableFrom(returnType))
    throw new Exception("The return type " + returnType + " is not Comparable");
  return newMethodComparator(method, (Class<? extends Comparable>) returnType);      
}
private static<T,R extends Comparable<R>> Comparator<T> newMethodComparator(
    final Method method, final Class<R> returnType) throws Exception {    
  return new Comparator<T>() {
    @Override
    public int compare(T o1, T o2) {
      try {
        R a = invoke(method, o1);
        R b = invoke(method, o2);
        return a.compareTo(b);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
    private R invoke(Method method, T o) throws Exception {
      return returnType.cast(method.invoke(o));
    }
  };
}

你可以这样使用它:

  List<Person> ps = new ArrayList<>(Arrays.asList(
    new Person("A", "D"), new Person("B",  "C")));
  ...
  Comparator<Person> byNameComparator = 
    newMethodComparator(Person.class, "getName");
  Collections.sort(ps,  byNameComparator);

原理:

(a)这意味着。将适用于任何返回Comparable对象的方法。您的实现使用了字符串比较,这将不能很好地工作,例如,如果该方法返回int。

(b)这意味着。检查方法的返回类型以及它在创建Comparator时(即排序开始之前)接受零参数的事实。因此,Collections.sort()向您抛出异常的可能性较小。

(c)注意@SuppressWarnings({ "unchecked", "rawtypes" })不构成任何实际风险。如果方法的返回类型是Comparable,那么向下转换将成功。如果返回类型不是Comparable,则Comparable.class.isAssignableFrom(returnType))将返回false,该方法将抛出显式异常,并且永远不会到达向下强制转换。

不能对Collection进行排序,只能对List进行排序。这是因为不是所有的集合(例如Set)都支持元素"order"的概念。

将方法签名更改为:

public static <T> void sort(List<T> list, final String methodName) {

我倾向于说,您可能最好只是创建不同比较器的具体实现,类似于您在问题开头所展示的。

public class PersonNameComparator implements Comparator<Person>
{
   public int compareTo(Person o1, Person o2)
   {
       return o1.getName().compareTo(o2.getName());
   }
}
public class PersonSurnameComparator implements Comparator<Person>
{
   public int compareTo(Person o1, Person o2)
   {
       return o1.getSurname().compareTo(o2.getSurname());
   }
}

然后调用标准库Collections排序方法

Collections.sort(listofpersons, new PersonNameComparator());
Collections.sort(listofpersons, new PersonSurameComparator());

这可能有点代码的味道,因为它是大量的碗板代码,你最终会让IDE生成,但它至少有一个优点,很容易编写一些单元测试,并且比使用反射性能更好(如果只是一点点)。

这种方法的缺点是,每次向Person类添加新方法时,都必须实现一个新的比较器,因此这里有很多内容。

如果'T'包含其他复杂类型并且不可比较,则下面的解决方案可能还需要更多的检查。

public <T> void sortList(List<T> objList, String[] attributes) throws ClassNotFoundException {
  Comparator<T> comparator = null;
  Comparator<T> temp = null;
  if (objList != null) {
    Class classObj = objList.get(0).getClass();
    Method[] methods = classObj.getMethods();
    for(String attribute : attributes) {
      for(Method method : methods) {
        if(method.getName().contains("get") && method.getName().toLowerCase().contains(attribute.toLowerCase()))
          temp = new Comparator<T>() {
            @Override
            public int compare(T o1, T o2) {
              try {
                Comparable v1 = (Comparable) method.invoke(o1);
                Comparable v2 = (Comparable) method.invoke(o2);
                return v1.compareTo(v2);
              } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
              }
              return 0;
            }
          };
          if (temp != null)
            if (comparator == null)
              comparator = temp;
            else
              comparator = comparator.thenComparing(temp);
            }
          }
          if (comparator != null)
            objList.sort(comparator);
          }
        }

相关内容

  • 没有找到相关文章

最新更新