"比较方法违反了其总合同!" - TimSort和GridLayout

s.a*_*lem 37 java swing grid-layout timsort java-7

我制作了一个带有jPanel和JLabel数组的调色板.起初它运行良好,但后来我把一些其他的jLabel从JPanel中添加并添加了一些事件.现在我一直收到这个错误:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:747)
at java.util.TimSort.mergeAt(TimSort.java:483)
at java.util.TimSort.mergeCollapse(TimSort.java:410)
at java.util.TimSort.sort(TimSort.java:214)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
at javax.swing.SortingFocusTraversalPolicy.enumerateAndSortCycle(SortingFocusTraversalPolicy.java:136)
at javax.swing.SortingFocusTraversalPolicy.getFocusTraversalCycle(SortingFocusTraversalPolicy.java:110)
at javax.swing.SortingFocusTraversalPolicy.getFirstComponent(SortingFocusTraversalPolicy.java:435)
at javax.swing.LayoutFocusTraversalPolicy.getFirstComponent(LayoutFocusTraversalPolicy.java:166)
at javax.swing.SortingFocusTraversalPolicy.getDefaultComponent(SortingFocusTraversalPolicy.java:515)
at java.awt.FocusTraversalPolicy.getInitialComponent(FocusTraversalPolicy.java:169)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:380)
at java.awt.Component.dispatchEventImpl(Component.java:4731)
at java.awt.Container.dispatchEventImpl(Container.java:2287)
at java.awt.Window.dispatchEventImpl(Window.java:2719)
at java.awt.Component.dispatchEvent(Component.java:4687)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:723)
at java.awt.EventQueue.access$200(EventQueue.java:103)
at java.awt.EventQueue$3.run(EventQueue.java:682)
at java.awt.EventQueue$3.run(EventQueue.java:680)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.awt.EventQueue$4.run(EventQueue.java:696)
at java.awt.EventQueue$4.run(EventQueue.java:694)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:693)
at java.awt.SequencedEvent.dispatch(SequencedEvent.java:116)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:721)
at java.awt.EventQueue.access$200(EventQueue.java:103)
at java.awt.EventQueue$3.run(EventQueue.java:682)
at java.awt.EventQueue$3.run(EventQueue.java:680)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.awt.EventQueue$4.run(EventQueue.java:696)
at java.awt.EventQueue$4.run(EventQueue.java:694)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:693)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:244)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:163)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:147)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:139)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:97)
Run Code Online (Sandbox Code Playgroud)

我试图删除我第一次遇到这个错误后所做的一切,但仍然继续得到它.当我将布局从GridLayout更改为其他任何内容时,错误消失,但代码变得无用.所以我需要GridLayout.当我将JPanel中的所有内容移动到另一个JPanel时,错误也会消失.但是当我删除第一个JPanel时,错误就会恢复.

顺便说一句,该程序可以正常工作,但它不是一直不容错误......

编辑:当我使用少于225色时,没有错误.我真的很好奇发生了什么.任何解释将不胜感激......

mad*_*th3 44

在我看来,你已经遇到了JDK中的一个错误,因为错误似乎来自Swing类.

选项:

  1. 将属性定义 java.util.Arrays.useLegacyMergeSorttrue.在代码中使用该行

    System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
    
    Run Code Online (Sandbox Code Playgroud)

    在任何Swing代码之前.因为该main方法的第一行应该工作.

    或者添加

    -Djava.util.Arrays.useLegacyMergeSort=true
    
    Run Code Online (Sandbox Code Playgroud)

    到您的启动选项(在控制台中,或IDE中的项目属性,Ant脚本等)

  2. 升级JDK并查看问题是否消失

  3. 降级到Java 6

  • 一般选项是添加`System.setProperty("java.util.Arrays.useLegacyMergeSort","true");`作为`main`方法的第一行. (5认同)
  • 我正在使用1.7.0_17-b02,这是本文撰写时的最新版本,所以似乎这个bug还没有修复.并将`java.util.Arrays.useLegacyMergeSort`系统属性设置为`true`确实让我解决了这个问题. (2认同)

Rob*_*ley 13

报告我的发现:

-Djava.util.Arrays.useLegacyMergeSort=true
Run Code Online (Sandbox Code Playgroud)

作品

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
Run Code Online (Sandbox Code Playgroud)

不起作用.

这是因为在JDK Arrays.class中

 static final class LegacyMergeSort {
    private static final boolean userRequested = ...
Run Code Online (Sandbox Code Playgroud)

它是一个静态变量,它是在jvm启动时定义的.如果已将类加载到jvm中,则在程序中设置System属性将不起作用.

我正在监视LegacyMergeSort.userRequested变量,并且上述声明证实了调查结果.

更新:程序必须在将java.util.Arrays加载到类加载器之前设置系统属性.否则,一旦加载,由于上述原因,设置属性将不会有用.

确保没有其他任何加载Arrays.class:

将以下代码放入您的程序进行测试:

    java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[] { String.class });
    m.setAccessible(true);
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    Object test1 = m.invoke(cl, "java.util.Arrays");
    System.out.println("test1 loaded? ->" + (test1 != null));
Run Code Online (Sandbox Code Playgroud)


Bur*_*uss 6

[更新] 遗憾的是,此解决方案无法保证在所有情况下都能解决问题.仅修补KeyboardFocusManager的默认SortingFocusTraversalPolicy是不够的.

我建议在下面阅读Robin Loxley的答案,包括他的更新. [/更新]

java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.TimSort.mergeHi(TimSort.java:868)
Run Code Online (Sandbox Code Playgroud)

这个问题是由一个bug引起的javax.swing.LayoutComparator.

以下类安装固定版本javax.swing.LayoutComparator,但不违反合同Comparator<Component>.这个(或任何其他)固定版本javax.swing.LayoutComparator应该由某个Oracle贡献者提交给Oracle.

package ...;

import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.ListIterator;

import javax.swing.JRootPane;
import javax.swing.SortingFocusTraversalPolicy;
import javax.swing.UIManager;

/**
 * Uses reflection to install a fixed version of {@link javax.swing.LayoutComparator} to solve the
 * LayoutFocusTraversalPolicy/TimSort problem.
 * 
 * <p>
 * <code>java.lang.IllegalArgumentException: Comparison method violates its general contract!</code>
 * <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;{@code     at java.util.TimSort.mergeHi(TimSort.java:868)}
 * </p>
 * <p>
 * Usage: call {@code Class.forName(LayoutFocusTraversalPolicyTimSortBugFixer.class.getName())}
 * before creating Swing components.
 * </p>
 * 
 * @author Burkhard Strauss
 * @since Feb 2015
 */
public class LayoutFocusTraversalPolicyTimSortBugFixer
{

   static
   {
      UIManager.getUI(new JRootPane()); // make Swing install the SortingFocusTraversalPolicy
      final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager
            .getCurrentKeyboardFocusManager();
      final FocusTraversalPolicy focusTraversalPolicy = keyboardFocusManager
            .getDefaultFocusTraversalPolicy();
      boolean fixed = false;
      if (focusTraversalPolicy instanceof SortingFocusTraversalPolicy)
      {
         try
         {
            final Field field = SortingFocusTraversalPolicy.class.getDeclaredField("comparator");
            final boolean accessible = field.isAccessible();
            try
            {
               field.setAccessible(true);
               field.set(focusTraversalPolicy, new LayoutComparator());
               fixed = true;
            }
            finally
            {
               field.setAccessible(accessible);
            }

         }
         catch (final Exception e)
         {
         }
      }
      if (!fixed)
      {
         Loggers.getLoggerFor(LayoutFocusTraversalPolicyTimSortBugFixer.class).warn("could not fix the bug");
      }
   }

   /**
    * Fixed version of {@link javax.swing.LayoutComparator}.
    * <p>
    * Search for 'bugfix' in the code.
    * </p>
    * 
    * @author Burkhard Strauss
    * @since Feb 2015
    */
   @SuppressWarnings("serial")
   private static class LayoutComparator implements Comparator<Component>, java.io.Serializable
   {

      private static final int ROW_TOLERANCE = 10;

      private boolean horizontal = true;
      private boolean leftToRight = true;

      @SuppressWarnings("unused")
      void setComponentOrientation(final ComponentOrientation orientation)
      {
         horizontal = orientation.isHorizontal();
         leftToRight = orientation.isLeftToRight();
      }

      @Override
      public int compare(Component a, Component b)
      {
         if (a == b)
         {
            return 0;
         }

         // Row/Column algorithm only applies to siblings. If 'a' and 'b'
         // aren't siblings, then we need to find their most inferior
         // ancestors which share a parent. Compute the ancestory lists for
         // each Component and then search from the Window down until the
         // hierarchy branches.
         if (a.getParent() != b.getParent())
         {
            final LinkedList<Component> aAncestory = new LinkedList<Component>();
            for (; a != null; a = a.getParent())
            {
               aAncestory.add(a);
               if (a instanceof Window)
               {
                  break;
               }
            }
            if (a == null)
            {
               // 'a' is not part of a Window hierarchy. Can't cope.
               throw new ClassCastException();
            }
            final LinkedList<Component> bAncestory = new LinkedList<Component>();
            for (; b != null; b = b.getParent())
            {
               bAncestory.add(b);
               if (b instanceof Window)
               {
                  break;
               }
            }
            if (b == null)
            {
               // 'b' is not part of a Window hierarchy. Can't cope.
               throw new ClassCastException();
            }
            for (ListIterator<Component> aIter = aAncestory.listIterator(aAncestory.size()), bIter = bAncestory
                  .listIterator(bAncestory.size());;)
            {
               if (aIter.hasPrevious())
               {
                  a = aIter.previous();
               }
               else
               {
                  // a is an ancestor of b
                  return -1;
               }
               if (bIter.hasPrevious())
               {
                  b = bIter.previous();
               }
               else
               {
                  // b is an ancestor of a
                  return 1;
               }
               if (a != b)
               {
                  break;
               }
            }
         }

         final int ax = a.getX(), ay = a.getY(), bx = b.getX(), by = b.getY();
         int zOrder = a.getParent().getComponentZOrder(a) - b.getParent().getComponentZOrder(b);
         {
            //
            // Here is the bugfix:
            // Don't return 0 if a != b. This would violate the contract of
            // Comparator<Component>.compare().
            //
            if (zOrder == 0)
            {
               zOrder = -1;
            }
         }
         if (horizontal)
         {
            if (leftToRight)
            {

               // LT - Western Europe (optional for Japanese, Chinese, Korean)

               if (Math.abs(ay - by) < ROW_TOLERANCE)
               {
                  return (ax < bx) ? -1 : ((ax > bx) ? 1 : zOrder);
               }
               else
               {
                  return (ay < by) ? -1 : 1;
               }
            }
            else
            { // !leftToRight

               // RT - Middle East (Arabic, Hebrew)

               if (Math.abs(ay - by) < ROW_TOLERANCE)
               {
                  return (ax > bx) ? -1 : ((ax < bx) ? 1 : zOrder);
               }
               else
               {
                  return (ay < by) ? -1 : 1;
               }
            }
         }
         else
         { // !horizontal
            if (leftToRight)
            {

               // TL - Mongolian

               if (Math.abs(ax - bx) < ROW_TOLERANCE)
               {
                  return (ay < by) ? -1 : ((ay > by) ? 1 : zOrder);
               }
               else
               {
                  return (ax < bx) ? -1 : 1;
               }
            }
            else
            { // !leftToRight

               // TR - Japanese, Chinese, Korean

               if (Math.abs(ax - bx) < ROW_TOLERANCE)
               {
                  return (ay < by) ? -1 : ((ay > by) ? 1 : zOrder);
               }
               else
               {
                  return (ax > bx) ? -1 : 1;
               }
            }
         }
      }
   }
}
Run Code Online (Sandbox Code Playgroud)


Cee*_*kay 5

我刚遇到同样的错误,花了很多时间跟踪它.为了帮助遇到此错误的其他人,了解如何测试TimSort非常重要.违反传递性合同并抛出此错误的检查在算法中很深入,并且需要在满足此特定条件之前进行测试才能再现此问题.

  1. 创建包含32个或更多对象的列表.
  2. 在该列表中,需要两次或更多次运行.
  3. 每次运行必须包含3个或更多对象.

一旦达到这两个标准,就可以开始测试这个失败.

运行被定义为列表的子集,其中每个项目已经处于期望的有序状态.