【问题标题】:How can I sort a list with different objects that implement the same interface?如何使用实现相同接口的不同对象对列表进行排序?
【发布时间】:2016-04-24 20:10:19
【问题描述】:

我有一个抽象类:

public abstract class Employee implements Comparable<Employee>{
  public Double getSalary();


    @Override
    public Double compareTo(final Employee employee2) {
    }
}

扩展这个抽象类的两个类:

public class Manager {
  public Double getSalary(){}
}

public class Chef{
  public Double getSalary(){}
}

我想对它进行排序,以便列表中的所有 Chef 对象都在前面,然后在所有 Chef 对象之后,我希望 Manager 对象按照薪水最高的降序排序。换句话说,对于 Chef 对象,我只希望它们被推到列表的前面,没有别的。对于 Manager 对象,我希望它们按薪水排序,并放在 Chef 对象之后的列表末尾。

例如

Manager manager1 = new Manager(40000);
Manager manager2 = new Manager(50000);
Manager manager3 = new Manager(60000);
Chef chef1 = new Chef(25000);
Chef chef2 = new Chef(25500);
Chef chef3 = new Chef(28000);
Chef chef4 = new Chef(29000);
List<Employee> employees = new ArrayList<Employee>();
employees.add(manager3);
employees.add(manager1);
employees.add(chef1);
employees.add(chef3);
employees.add(manager2);
employees.add(chef4);
employees.add(chef2);

对列表中的元素进行排序后可能是这样的:

0. chef1 = 25000 // Chef objects pushed to front but not sorted by salary
1. chef3 = 28000
2. chef4 = 29000
3. chef2 = 25500
4. manager3 = 60000 // Manager objects pushed to the back and sorted by salary
5. manager2 = 50000
6. manager1 = 40000

我的 compareTo 方法:

@Override
public int compareTo(final Employee employee2) {
    if (this instanceof Manager|| employee2instanceof Manager) {
        return employee2.getSalary().compareTo(this.getSalary());
    }

    return -1;
}

但是,这会将 Manager 对象放在前面。我对如何在这个 compareTo 方法中使用 Chef 对象感到困惑。

【问题讨论】:

  • 请展示您到目前为止所做的尝试。
  • 你不能在原语上使用 compareTo [int].compareTo([int]) 不起作用。如果您想要这样的功能,请使用 Integer 包装类。
  • 已更正。谢谢。
  • 嗨,安迪,感谢您的意见。只是在 Comparable vs Comparator 上阅读自然排序。每天学习新东西!

标签: java sorting generics collections comparable


【解决方案1】:

您的情况听起来像是创建Comparator 比实现Comparable 更合适:后者专门用于建模类实例的自然顺序

例如,Integer implements Comparable&lt;Integer&gt;:整数的自然排序非常明显。

您的案例听起来更像是您希望如何对它们进行排序的一次性案例,例如在特定报告中呈现。

此外,当父类需要了解其子类时,我认为这是一种代码异味。没有什么可以阻止新子类的创建;没有必要更新 Employee 来处理这个事实。

从一种转换到另一种很容易:不是将int compare(Employee that) 方法添加到Employee,而是在比较器中实现一个方法:

class ChefsAndManagersComparator implements Comparator<Employee> {
  @Override
  public int compare(Employee employee1, Employee employee2) {
    // The logic from any of the other answers,
    // just replacing "this" references with "employee1"
  }
}

【讨论】:

    【解决方案2】:

    您的代码无法编译,您不能在 getSalary 返回的原始 int 上调用 compareTo()...相反,您可以使用实用方法 Long.compare()

    if (this instanceof Manager || employee2 instanceof Manager) {
        return Long.compare(this.getSalary(), employee2.getSalary());
    }
    

    另外,鉴于这不是“自然搜索顺序”,我会将其表示为 Comparator 而不是 Comparable...,即在 Employee 类的外部,而不是嵌入其中。

    Collections.sort(employees, new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            if (o1 instanceof Manager && o2 instanceof Chef) {
                return 1;
            } else if (o1 instanceof Chef && o2 instanceof Manager) {
                return -1;
            } else if (o1 instanceof Chef && o2 instanceof Chef) {
                return 0;
            } else {
                return Long.compare(o1.getSalary(), o2.getSalary());
            }
        }
    });
    

    输出

    Chef 25000
    Chef 28000
    Chef 29000
    Chef 25500
    Manager 40000
    Manager 50000
    Manager 60000
    

    【讨论】:

    • 嗨,不错的收获。原语只是一个例子,实际上我们使用的是 Double。我会相应地改变它。
    • 这并不完全符合规定:厨师不应该按薪水排序。
    • 对于额外的布朗尼积分,您也可以对厨师进行排序,以迎接挑战。但这不是问题的一部分,在我的真实代码中也不需要:)
    • @JohnathanAu 老实说,不是对厨师进行分类需要更多的努力。
    • @adam 如果需要将一个新的 Employee 子类(即 Waiter)集成到这个逻辑中会发生什么?此解决方案将导致“意大利面条代码”
    【解决方案3】:

    您可以创建自定义比较器并将其传递给Collections.sort(..)。一旦你有了比较器,你可以做Collections.sort(employees, new CustomComparator());

    以下是执行此操作的一种方法,但我不强烈建议这种做法,因为它可能会在运行时导致复杂性,甚至在检查/针对子类时出错。

    public class CustomComparator implements Comparator<Employee> {
        @Override
        public int compare(Employee o1, Employee o2) {
            if(o1 instanceof Chef && o2 instanceof Manager)
                return -1;
    
            if(o1 instanceof Manager && o2 instanceof Chef)
                return +1;
    
            if(o1 instanceof Manager && o2 instanceof Manager) 
                return Double.compare(o1.getSalary(), o2.getSalary());
    
            return 0;
        }  
    }
    

    在我写这篇文章时,我认为更好的方法是使用多个列表来存储每个 Employee 类型。这将解决在运行时使用检查的问题。

    或者,也许您可​​以在接口中实现一些字段,为每个实现类型提供不同的分数,然后可以用来比较 Employee 对象。

    【讨论】:

    • 请注意,您对Manager 薪水的比较不符合compare 的约定:如果它们相等,则需要返回零。原始包装器 .compare 方法是更好的选择(例如 Integer.compare(o1.getSalary(), o2.getSalary()))。
    • Integer.compare(o1.getSalary(), o2.getSalary()) 会不会更简单。编辑:安迪在我之前到达那里。
    • @AndyTurner 我会做出改变的。非常感谢!
    【解决方案4】:

    您可以使用 3 个列表,因此您不必检查特定的子类型:

    List<Chef> chefs = new ArrayList<Chef>();
    List<Manager> managers = new ArrayList<Manager>();
    
    managers.add(manager3);
    managers.add(manager1);
    chefs.add(chef1);
    chefs.add(chef3);
    managers.add(manager2);
    chefs.add(chef4);
    chefs.add(chef2);
    
    Collections.sort(managers);
    
    List<Employee> employees = new ArrayList<Employee>();
    employees.addAll(chefs);
    employess.addAll(managers);
    

    但是,如果您无法更改员工存储的实现方式,您仍然可以遍历员工列表,检查每种类型并插入到特定列表中。然后,在最后连接:

    ...
    List<Chef> chefs = new ArrayList<Chef>();
    List<Manager> managers = new ArrayList<Manager>();
    List<Employee> others = new ArrayList<Employee>();
    
    for(Employee e : employees) {
        if(e instanceof Chef)
            chefs.add(e);
        else if(e instanceof Manager)
            managers.add(e);
        else
            others.add(e);
    }
    
    Collections.sort(managers);
    
    List<Employee> result = new ArrayList<Employee>();
    result.addAll(chefs);
    result.addAll(managers);
    result.addAll(others);
    

    ArrayList::addAll 使用 System::arrayCopy 所以它非常快。这也与使用比较器大致相同,但它可能更具可读性。这真的取决于偏好。

    【讨论】:

    • 这可能是最简单的解决方案。但是以防万一,由于任何商业原因或遗留代码不允许某人执行上述操作,那么我认为其他答案对于如何以复杂的方式对具有不同类型对象的列表进行排序非常好。
    • @JohnathanAu 没关系,我只是一般不喜欢在运行时检查类型信息。我以前自己使用过这样的解决方案来解决类似的问题。值得建议 ;)
    猜你喜欢
    • 2023-04-07
    • 2016-05-21
    • 2012-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-06
    相关资源
    最近更新 更多