概念证明:如何使用反射创建通用比较器方法?

时间:2014-05-21 00:54:47

标签: java generics reflection comparator

我有很多这样的课程:

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

忘记了捕获异常和现在的String转换,重点是Collections.sort(Collection,Comparator)不存在,但我不知道创建一个Comparator ... 我非常好奇地知道我的想法是否有任何意义(原因)以及实施它的正确方法。

谢谢!

3 个答案:

答案 0 :(得分:1)

问题在于sortCollection方法的签名。您应该将list参数的类型从Collection<T>更改为List<T>。换句话说,您的方法标题应为:

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

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

编辑:

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

@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)这个impl。将适用于任何返回Comparable对象的方法。你的实现是使用字符串比较,如果方法返回一个int,那么它将无法正常工作。

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

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

答案 1 :(得分:0)

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

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.sort(listofpersons, new PersonNameComparator());
Collections.sort(listofpersons, new PersonSurameComparator());

它可能有点代码味道,因为它会导致IDE生成大量的圆形板代码,但它至少具有易于编写一些单元测试的优点,并且比使用反射更具性能(如果只是略微)。

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

答案 2 :(得分:0)

您无法对集合进行排序,只能对列表进行排序。这是因为并非所有集合(例如Set)都支持元素&#34; order&#34;的概念。

将方法签名更改为:

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