【问题标题】:java Map removal unexpected behaviorjava Map删除意外行为
【发布时间】:2014-01-13 16:37:06
【问题描述】:

代码:

public class FeatureBuilder implements IFeatureBuilder {

   private final Map< java.awt.Shape, ShapeAttributes > shapes = new HashMap<>();

   [...]

   @Override
   public void addToContainer( java.awt.Shape shape ) {
      System.err.println( shape.getClass());
      shapes.put( shape, new ShapeAttributes( ... ));
   }

   @Override
   public void removeFromContainer( double x, double y ) {
      final Set< Shape > rm  = new HashSet<>();
      final Point2D      loc = new Point2D.Double( x, y );
      for( final Shape shape : shapes.keySet()) {
         if( shape.contains( loc )) {
            System.err.println( "shapes.containsKey( shape ): " +
               shapes.containsKey( shape ));
            rm.add( shape );
         }
      }
      System.err.println( "cardinality before removal : " + shapes.size());
      shapes.keySet().removeAll( rm );
      System.err.println( "cardinality after  removal : " + shapes.size());
   }

输出:

class java.awt.geom.Rectangle2D$Double
shapes.containsKey( shape ): false      <<<<<<< This is unexpected!
cardinality before removal : 1
rm cardinality             : 1
cardinality after  removal : 1

我很惊讶:for 迭代器在Map.keySet() 上检索到的实例不是Map 中的键!

这怎么可能?

此方法的主要错误是放置在rm 中的选定Shape 实例不会从shapes 中删除。

阅读您的答案后,代码变为:

public class DecoratedShape {

   public final Shape _shape;
   public /* */ Color _stroke      = Color.BLACK;
   public /* */ float _strokeWidth = 3.0f;
   public /* */ Color _fill        = Color.BLACK;

   public DecoratedShape( Shape shape, Color stroke, float strokeWidth, Color fill ) {
      _shape       = shape;
      _stroke      = stroke;
      _strokeWidth = strokeWidth;
      _fill        = fill;
   }

   public boolean contains( Point2D loc ) {
      return _shape.contains( loc );
   }

   public void paint( Graphics2D g ) {
      g.setStroke( new BasicStroke( _strokeWidth ));
      g.setColor( _fill );
      g.fill( _shape );
      g.setColor( _stroke );
      g.draw( _shape );
   }
}

public class FeatureBuilder implements IFeatureBuilder {

   private final List< DecoratedShape > _shapes = new LinkedList<>();
   [...]

   @Override
   public void addToContainer( Object o ) {
      _shapes.add( new DecoratedShape((Shape)o, _stroke, _strokeWidth, _fill ));
   }

   @Override
   public void removeFromContainer( double x, double y ) {
      final Set<DecoratedShape> rm = new HashSet<>();
      final Point2D loc = new Point2D.Double( x, y );
      for( final DecoratedShape shape : _shapes ){
         if( shape.contains( loc ) ){
            rm.add( shape );
         }
      }
      _shapes.removeAll( rm );
   }

   public void paint( Graphics2D g ) {
      for( final DecoratedShape shape : _shapes ) {
         shape.paint( g );
      }
   }
}

现在,它按预期工作......非常感谢!

【问题讨论】:

  • 这通常发生在equals和hashcode方法不一致的时候。但是,Rectangle2D... 似乎并非如此
  • 你能给出一个 SSCCE(即创建一个重现行为的 Rectangle2D.Double 实例)吗?
  • 也许一些更多的调试信息,例如打印出hashcode() 将有助于比较两者以及equals() 是否返回true。
  • 首先,您不应该在HashMap 中使用可变对象作为键。我想这就是在这里造成问题的原因。但是我们需要更多的代码来破解它。
  • @Aubin 您可以扩展您感兴趣的形状,并给它们一个在调整大小时不会改变的名称或 ID。或者更好的是,定义您自己的ShapeHolder,而不是使用形状和字符串/整数 id。然后,您可以使用该 ID 作为地图的键。否则这将是一个棘手的练习。

标签: java collections map awt


【解决方案1】:

跟进@RohitJain 的评论,这是一个重现行为的简单示例:

Map<Shape, Object> map = new HashMap<>();
java.awt.geom.Rectangle2D.Double shape = new java.awt.geom.Rectangle2D.Double(1, 1, 1, 1);
map.put(shape, null);
System.out.println(map.size()); //1
shape.setRect(2, 2, 2, 2); //mutate
System.out.println(map.size()); //still 1
map.keySet().remove(shape);
System.out.println(map.size()); //still 1

根本问题是Shape在地图中发生了变异。

【讨论】:

  • 实际上,我正在考虑for 循环的false 输出。这可能吗?因为shape 仅从地图中获取。奇怪:(
  • 是的,这是可能的。 keySet 是一个简单的迭代器,它将显示所有内容。当您使用 containsKey 返回时,它会散列并查看“错误”存储桶。
  • @Affe 哦!伟大的。不知道我是怎么错过的。 containsKey 重新计算hash 的值,与之前的不同。
  • +1:你的回答很好。但是关于形状(更准确地说是 Rectangle2D)作为地图键的资格仍然存在一个问题......今天的教训!
  • @Aubin 一般来说,不推荐使用可变键,如果你需要确保你永远不会改变它们。您可以在javadoc 中找到有关它的更多信息:“注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值在当对象是映射中的键时影响等于比较的方式。"
【解决方案2】:

这并不能解释为什么您的代码不起作用,但它会解决您的问题并使该方法更有效,因为没有分配临时集:

public void removeFromContainer(double x, double y) {
    final Point2D loc = new Point2D.Double(x, y);
    Iterator<Shape> iter = shapes.keySet().iterator();
    while (iter.hasNext()) {
        if (iter.next().contains(loc))
            iter.remove();
    }
}

编辑:我怀疑您在将形状添加到地图后对其进行了修改,因此检查containsKey 时的哈希码与将形状添加到地图时使用的哈希码不匹配。

【讨论】:

  • +1:谢谢!我更喜欢通过使用列表来避免这个问题。在这里使用地图是一个错误。
猜你喜欢
  • 2012-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多