【问题标题】:Problems with Collections.sort in Java 8Java 8 中的 Collections.sort 问题
【发布时间】:2015-01-18 22:50:38
【问题描述】:
 @Entity
 @NamedQueries({
   @NamedQuery(
     name = "FolderNode.findByName",
     query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode = :parentNode"),
   @NamedQuery(
     name = "FolderNode.findRootNodeByName",
     query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode is null")
 })
 public class FolderNode extends InstructorTreeNode {
   public FolderNode() {
     super();
   }

   public FolderNode(String name) {
     this();
     setName(name);
   }

   public FolderNode(int sortOrder, String name) {
     this(name);
     this.sortOrder = sortOrder;
   }

   public FolderNode(int sortOrder, String name, EmployeeState status) {
     this(sortOrder, name);
     this.status = status;
   }

   public static FolderNode addWaitingListNode(String name) {
     EntityManager em = getDao().getEntityManager();
     em.getTransaction().begin();
     FolderNode waitingListNode = getWaitingListFolder();
     FolderNode folderNode = new FolderNode(0, name);
     waitingListNode.addChild(folderNode);
     em.merge(waitingListNode);
     em.getTransaction().commit();
     em.close();
     return folderNode;
   }

   public static void addWaitingListStudent(String waitingList, Student s) {
     EntityManager em = FolderNode.getDao().getEntityManager();
     em.getTransaction().begin();
     FolderNode waitingListsNode = getWaitingListFolder();
     FolderNode waitingListNode = getDao().findFolderNodeByName(waitingListsNode, waitingList);
     waitingListNode.addChild(new EmployeeLeaf(s.getInmate()));
     em.merge(waitingListNode);
     em.getTransaction().commit();
     em.close();
   }

   public static FolderNode getAMClassFolder() {
     return getDao().findFolderNodeByName(getStudentsFolder(), "AM Class");
   }

   public static FolderNode getAttendanceFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Employee Attendance");
   }

   public static FolderNode getFormerParaprosFolder() {
     return getDao().findFolderNodeByName(getParaprosFolder(), "Former");
   }

   public static FolderNode getFormerStudentsFolder() {
     return getDao().findFolderNodeByName(getStudentsFolder(), "Former");
   }

   public static FolderNode getPMClassFolder() {
     return getDao().findFolderNodeByName(getStudentsFolder(), "PM Class");
   }

   public static FolderNode getParaprosFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Parapros");
   }

   public static FolderNode getPendingStudentsFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Pending Students");
   }

   public static FolderNode getRootFolder() {
     return getDao().findFolderNodeByName(null, EducationPreferences.getInstructor().getInstructorName());
   }

   public static FolderNode getStudentsFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Students");
   }

   public static FolderNode getWaitingListFolder(String name) {
     FolderNode waitingListsNode = getWaitingListFolder();
     return getDao().findFolderNodeByName(waitingListsNode, name);
   }

   public static FolderNode getWaitingListFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Waiting List");
   }

   public static void setClassFolder(Student aStudent, EntityManager entityManager) {
     EntityManager em = entityManager;
     if (entityManager == null) {
       em = FolderNode.getDao().getEntityManager();
       em.getTransaction().begin();
     }

     EmployeeLeaf leaf = EmployeeLeaf.findActiveStudentLeaf(aStudent);
     FolderNode node = aStudent.getShift() == Shift.AM ? getAMClassFolder() : getPMClassFolder();
     leaf.setParentNode(node);
     em.merge(leaf);
     GlobalEntityMethods.updateHistory(leaf);
     if (entityManager == null) {
       em.getTransaction().commit();
       em.close();
     }
   }

   public static void transferWaitingListStudent(String currentFolder, String toFolder, Student student) {
     EntityManager em = FolderNode.getDao().getEntityManager();
     em.getTransaction().begin();
     FolderNode waitingListsNode = getWaitingListFolder();
     FolderNode currentWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, currentFolder);
     EmployeeLeaf employeeLeaf = EmployeeLeaf.getDao().findWaitingListLeafByInmate(student.getInmate());
     currentWaitingListNode.removeChild(employeeLeaf);
     FolderNode toWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, toFolder);
     toWaitingListNode.addChild(employeeLeaf);
     em.merge(currentWaitingListNode);
     em.merge(toWaitingListNode);
     em.getTransaction().commit();
     em.close();
   }

   public void addChild(InstructorTreeNode node) {
     childNodes.add(node);
     node.setParentNode(this);
   }

   public List<InstructorTreeNode> getChildNodes() {
     Collections.sort(childNodes);
     return childNodes;
   }

   @Override
   public Set<Inmate> getInmates() {
     Set<Inmate> inmateSet = new HashSet<> (50);
     for (InstructorTreeNode node: getChildNodes()) {
       inmateSet.addAll(node.getInmates());
     }
     return inmateSet;
   }

   public int getSortOrder() {
     return sortOrder;
   }

   public EmployeeState getStatus() {
     return status;
   }

   @Override
   public List<InstructorTreeNode> getTree() {
     List <InstructorTreeNode> result = new ArrayList<> (25);
     for (InstructorTreeNode childNode: getChildNodes()) {
       if (childNode instanceof FolderNode) {
         result.add(childNode);
       }
       result.addAll(childNode.getTree());
     }
     return result;
   }

   @Override
   public JPanel getView(EmployeeViewController controller) {
     if ("Employee Attendance".equals(getName())) {
       return new AttendanceView();
     } else if ("Waiting List".equals(getName())) {
       return new AllWaitingListsPanel(controller);
     } else if (getParentNode().getName().equals("Waiting List")) {
       return new WaitingListPanel(controller);
     } else if ("Pending Students".equals(getName())) {
       return new PendingStudentsPanel(controller);
     } else if ("Students".equals(getName())) {
       return new AllStudentsPanel(controller);
     } else if ("AM Class".equals(getName())) {
       return new AllStudentsPanel(controller, Shift.AM);
     } else if ("PM Class".equals(getName())) {
       return new AllStudentsPanel(controller, Shift.PM);
     } else if (getParentNode().getName().equals("Students") && "Former".equals(getName())) {
       return new FormerStudentsPanel(controller);
     } else if ("Parapros".equals(getName())) {
       return new AllParaprosPanel(controller);
     } else if (getParentNode().getName().equals("Parapros") && "Former".equals(getName())) {
       return new FormerParaprosPanel(controller);
     }
     throw new UnsupportedOperationException("unknown folder");
   }

   public void removeChild(InstructorTreeNode node) {
     childNodes.remove(node);
     node.setParentNode(null);
   }

   public void removeEmployeeLeaf(Inmate inmate) {
     for (InstructorTreeNode node: childNodes) {
       if (node instanceof EmployeeLeaf) {
         EmployeeLeaf employeeLeaf = (EmployeeLeaf) node;
         if (employeeLeaf.getInmate().equals(inmate)) {
           childNodes.remove(employeeLeaf);
           break;
         }
       }
     }
   }

   public void setChildNodes(List<InstructorTreeNode> childNodes) {
     this.childNodes = childNodes;
   }

   public void setSortOrder(int sortOrder) {
     this.sortOrder = sortOrder;
   }

   public void setStatus(EmployeeState status) {
     this.status = status;
   }

   @OneToMany(mappedBy = "parentNode", cascade = CascadeType.ALL, orphanRemoval = true)
   private List<InstructorTreeNode> childNodes;

   private int sortOrder;

   @Enumerated(EnumType.STRING)
   private EmployeeState status;
 }


 @Entity
 @Table(catalog = "education", name = "instructortreenode", uniqueConstraints = @UniqueConstraint(columnNames = {
   "PARENTNODE_ID", "NAME"
 }))
 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
 public abstract class InstructorTreeNode implements Comparable<InstructorTreeNode> {
   public InstructorTreeNode() {
     super();
   }

   public static InstructorTreeNodeDAO getDao() {
     return dao;
   }

   @Override
   public int compareTo(InstructorTreeNode o) {
     if (o instanceof FolderNode && this instanceof FolderNode) {
       FolderNode thisFolder = (FolderNode) this;
       FolderNode otherFolder = (FolderNode) o;
       if (thisFolder.getSortOrder() != otherFolder.getSortOrder()) {
         return thisFolder.getSortOrder() - otherFolder.getSortOrder();
       } else {
         return thisFolder.getName().compareToIgnoreCase(otherFolder.getName());
       }
     } else if (o instanceof EmployeeLeaf && this instanceof EmployeeLeaf) {
       return getName().compareToIgnoreCase(((InstructorTreeNode) o).getName());
     }
     return (o instanceof FolderNode) ? -1 : +1;
   }

   public int getCount() {
     return getTree().size();
   }

   public abstract Set<Inmate> getInmates();

   public String getName() {
     return name;
   }

   public FolderNode getParentNode() {
     return parentNode;
   }

   public abstract List<InstructorTreeNode> getTree();

   public abstract JPanel getView(EmployeeViewController theController);

   public void setName(String name) {
     this.name = name;
   }

   public void setParentNode(FolderNode parentNode) {
     this.parentNode = parentNode;
   }

   @Override
   public String toString() {
     return name;
   }

   private static final InstructorTreeNodeDAO dao = new InstructorTreeNodeDAO();
   private String name;

   @ManyToOne
   private FolderNode parentNode;
 }

这是我的问题: Collections.sort 行在 Java 8u5 及之前的版本中工作得很好,但是 在 Java 8u20 中,他们似乎更改了 Collections.sort 的代码 并且它不再使用除了自然顺序之外的任何东西,即使您指定 比较器。

我应该使用其他方法对我的列表进行排序,还是有错误 Collections.sort。

任何帮助将不胜感激,因为这让我发疯。

我忘了说这段代码没有使用指定的比较器,但是根据文档,如果你的类实现了 Comparable,它应该使用 CompareTo,这就是我正在使用的。 我也尝试指定一个比较器,但它也不起作用。

【问题讨论】:

  • 但是您没有指定不同的比较器?什么代码给出了错误,你得到了什么错误信息?
  • 我没有收到任何类型的错误消息。我的类 InstructorTreeNode 实现了 Comparable。根据 JavaDocs,如果您不实现 Comparable,您的列表将按自然顺序排序,但如果您实现 Comparable,则应该使用 CompareTo 中指定的顺序对列表进行排序。这一直运行良好,直到升级到 Java 8u20。
  • 那是......不是它的工作原理。如果您不实施Comparable,则 is 没有自然顺序。您必须实现Comparable以使用Collections.sort(list)而不使用Comparator,并且Comparable实现定义自然顺序。
  • 我刚刚在 Oracle 的网站上找到了以下内容:以前 Collection.sort 将列表的元素复制到一个数组中排序,对该数组进行排序,然后更新列表,将这些元素放在数组,默认方法 List.sort 推迟到 Collection.sort。这是一个非最佳的安排。从 8u20 版本开始,Collection.sort 遵循 List.sort。这意味着,例如,使用 ArrayList 实例调用 Collection.sort 的现有代码现在将使用 ArrayList 实现的最佳排序。那么我是否需要更改代码才能获得与以前相同的功能?
  • 我的建议是创建一个简短的示例程序来复制排序问题。这个来源太复杂了,我们无法断章取义地诊断。看看你能不能创建一个简单的例子来演示这个问题。

标签: java sorting java-8


【解决方案1】:

由于Collections.sort 现在委托给List.sort,实际的List 实现会产生影响。 ArrayListVector 等实现利用机会以比默认实现更有效的方式实现 List.sort,因为它们将内部数组直接传递给 Arrays.sort,省略了默认实现的复制步骤。

这可以无缝工作,除非程序员使用子类化实现(而不是使用委托)覆盖方法的反模式来实现矛盾的行为。像这些来自 EclipseLink/JPA are known to have problems with this 的懒惰填充列表,因为它们试图在继续之前拦截每个读取方法以填充列表,但错过了新的 sort 方法。如果在调用 sort 时列表尚未填充,sort 将看到一个空列表状态。

在您的代码中,没有迹象表明该列表确实来自哪里以及它具有哪个实际实现类,但是由于我看到了很多看起来很熟悉的注释,我猜,您正在使用这样的一个框架……

【讨论】:

  • 我没有考虑惰性获取方面。
  • 确认!问题是延迟获取 JPA 包装器对象。当 fetch 变为 eager 时,排序开始起作用。在 Java 8u20 之前排序一直有效,所以现在看来​​ JPA 惰性获取机制与新的 Java 8 排序优化不兼容。
  • 在将 fetch 类型更改为 eager 解决了问题的同时,它还产生了其他问题,即长时间等待列表填充。因此,我发现作为一种折衷方案的解决方案是将惰性获取的列表复制到一个新的 ArrayList,然后对该列表进行排序。现在我得到了两全其美。
  • 我的 JPA 提供程序是 eclipselink。他们最近发布了 2.6.0 版,修复了包装器问题。
  • 谢谢!!!我的项目是一个非常古老的项目,在 JBoss 4 上运行了一个非常旧的过时 ORM (Mvcsoft),我在 JDK 8 上运行不兼容。甚至没有调用自定义比较器。用 JDK-ArrayList 重新打包 ORM 传递的列表解决了这个问题。
【解决方案2】:

如果您使用Collections#sort(List&lt;T&gt; list) 方法,它遵循List#sort(Comparator comparator) 方法,比较器为null。来自java.util.Collections的源码如下:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

如果要指定自己的比较器,则需要使用方法Collections#sort(List&lt;T&gt; list, Comparator&lt;T&gt; comparator),它将比较器传递给列表排序方法。来自java.util.Collections的源码如下:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

到目前为止一切顺利。现在,正如您正确指出的那样,如果您不指定比较器,则使用类的自然顺序,即您定义的compareTo 方法。

但是,Comparabledocumentation also states the following

强烈建议(尽管不是必需的)自然排序与等号一致。之所以如此,是因为没有显式比较器的排序集(和排序映射)在与自然顺序与等于不一致的元素(或键)一起使用时表现“奇怪”。特别是,这样的有序集合(或有序映射)违反了集合(或映射)的一般约定,它是根据 equals 方法定义的。

由于类InstructorTreeNode 没有覆盖Object#equals,即使== 返回false,您的compareTo 方法也可能返回0。我认为这导致了文档所说的“奇怪”。

【讨论】:

  • OP 和她的 cmets 建议永远不会调用 compareTo 方法。虽然您的回答解释了应该发生的事情,但她所看到的并非如此。
  • @ChuckKrutsinger:感谢您指出。就在几周前,我经历了类似的事情。为我的答案添加了可能的解释(覆盖 equalshashCode 解决了问题)。
  • 优秀的补充。这可能就是正在发生的事情。 +1
【解决方案3】:

您可能不喜欢这个答案,因为它不会为您的情况提供快速解决方案,但从长远来看,它会为您提供更多帮助。

这是一种您可以通过一些调试自己找出的错误。我不知道您使用的是什么 IDE,但是使用 Eclipse,您甚至可以单步执行 JDK 中的代码!

所以,我要做的就是在子节点上调用 sort() 的那一行设置一个断点。然后我会单步执行 JDK 代码,然后自己过一遍。它会变得非常清楚发生了什么以及为什么它没有调用您的比较函数。

【讨论】:

    【解决方案4】:

    您可以尝试构建自定义比较器。这是一个看起来应该如何的示例。这是为了比较 BigDecimals。

    class YourComparator implements Comparator<InstructorTreeNode> {
    @Override
    public int compare(final  InstructorTreeNode 01, final InstructorTreeNode o2) {
        return o2.getYourCompVal().compareTo(o1.getYourCompVal());
    }
    

    }

     public List<InstructorTreeNode> getChildNodes() {
     Collections.sort(childNodes, new YourComparator());
     return childNodes;}
    

    【讨论】:

    • OP 明确指出:“即使你指定了 Comparator”,所以已经尝试过了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-11
    • 2010-10-31
    • 2015-02-23
    • 1970-01-01
    相关资源
    最近更新 更多