【问题标题】:HashMap keys return null when the map demonstrably contains them [duplicate]当地图明显包含它们时,HashMap 键返回 null [重复]
【发布时间】:2018-03-25 00:13:00
【问题描述】:

我目前正在尝试解决题为战舰:沉没损坏还是未触及?的代码大战问题。给定一个包含“ships”的二维数组和另一个包含攻击坐标的二维数组,我必须生成一个分数。我用船舶位置填充哈希图,然后根据地图检查攻击位置。打印后,第一个测试用例的原始位置和攻击是相同的。尽管如此,map.get() 继续返回 nullmap.containsKey() 返回 false

public class BattleshipsSDN {
public static Map<String,Double> damagedOrSunk(final int[][] board, final int[][] attacks) {

    int y;
    HashMap<int[], Integer> ships = new HashMap<>();

    // build map of boat locations
    for (int i = 0; i < board.length; i++) {
        y = board.length - i;
        for (int j = 0; j < board[i].length; j++) {
            if (board[i][j] == 0) continue;
            else {
                int[] location = {j+1,y};
                ships.put(location, board[i][j]);
                System.out.println("Location: "+location[0]+","+location[1]);
                //System.out.println("Value: "+ships.get(location));
            }
        }
    }

    //establish original boat lengths
    int ship1 = Collections.frequency(new ArrayList<Integer>(ships.values()), 1);
    int ship2 = Collections.frequency(new ArrayList<Integer>(ships.values()), 2);
    int ship3 = Collections.frequency(new ArrayList<Integer>(ships.values()), 3);
    System.out.println("Ships: "+ship1+ship2+ship3);

    for(int[] x : ships.keySet()) System.out.println(x[0]+","+x[1]);

    //check for hits
    for (int[] x : attacks) {
        System.out.println(x[0]+","+x[1]);
        if (ships.get(x) == null) continue;
        else{
            System.out.println("Hit");
            ships.remove(x);
        }
    }

    double sunk = 0;
    double hit = 0;
    double missed = 0;

    //find number of ship spots after attacks
    int leftShip1 = Collections.frequency(new ArrayList<Integer>(ships.values()), 1);
    int leftShip2 = Collections.frequency(new ArrayList<Integer>(ships.values()), 2);
    int leftShip3 = Collections.frequency(new ArrayList<Integer>(ships.values()), 3);
    System.out.println("Ships: "+leftShip1);

    if (ship1 > 0) {
        if (leftShip1 == 0) sunk++;
        else if (ship1 % leftShip1 > 0) hit++;
        else if (ship1 == leftShip1) missed++;
    }
    if (ship2 > 0) {
        if (leftShip2 == 0) sunk++;
        else if (ship2 % leftShip2 > 0) hit++;
        else if (ship2 == leftShip2) missed++;
    }
    if (ship3 > 0) {
        if (leftShip3 == 0) sunk++;
        else if (ship3 % leftShip3 > 0) hit ++;
        else if (ship3 == leftShip3) missed++;
    }
    HashMap<String, Double> score = new HashMap<>();
    score.put("sunk", sunk);
    score.put("damaged", hit);
    score.put("notTouched", missed);
    score.put("points", sunk + hit/2 - missed);
    return score;

}

}

我不是要你为我解决问题。我完全不知道为什么我的 HashMap 会这样。这可能意味着这是一些非常小的愚蠢的事情。

注意:位置的 y 值是翻转的,因为在问题“板”中,y 坐标是从底部测量的。因此,在 4x4 板或数组中,索引 [0][0] 对应于坐标 (1,4)

【问题讨论】:

  • int[]s 不要覆盖equalshashCode;他们按身份进行比较。
  • 一般用于数组。数组作为映射中的键不起作用。尝试其他数据结构。
  • 对于&lt;your favourite deity&gt; 的喜爱,使用集合而不是数组。如果您改用List&lt;Integer&gt; 而不是int[],您的问题就会消失。

标签: java null hashmap


【解决方案1】:

说明

问题是您使用int[] 作为HashMap 的密钥。数组不会覆盖 equalshashCode 方法。因此,对于他们来说,这些方法通过对象的身份而不是内容来比较对象。

考虑一下:

int[] first = new int[] { 1, 2, 3 };
int[] second = new int[] { 1, 2, 3 };

System.out.println(first.equals(second)); // Prints 'false'

两个数组具有相同的内容,但它们被视为不相等,因为它们是不同的对象 (first != second)。

当您现在调用 map.get(key) 之类的东西时,地图会使用其 hash-codehashCode 方法返回的那个)搜索键。但是,此方法也适用于数组的 identity-base

如果你现在用一个key存储数据,之后又重新创建一个内容相同的key,为了获取数据,你就找不到了:

Map<int[], String> map = new HashMap<>();

// Put data
int[] key = new int[] { 1, 2, 3 };
map.put(key, "test");

// Retrieve it
int[] similarKey = new int[] { 1, 2, 3 };
String data = map.get(similarKey); // Is 'null', not ' test'

// Try it with 'key' instead of 'similarKey'
String otherData = map.get(key); // Works now since same object

虽然similarKey 具有相同的内容,但它具有不同的hashCode,因为它不是同一个对象(按身份)。


解决方案

要解决这个问题,只需使用实现hashCodeequals 的数据结构,而不是根据身份,而是根据比较内容。您可以使用来自Collection (documentation)、ArrayList (documentation) 的内容,例如:

Map<List<Integer>, String> map = new HashMap<>();

// Put data
int[] key = new int[] { 1, 2, 3 };
List<Integer> keyAsList = new ArrayList<>(Arrays.asList(key));
map.put(keyAsList, "test");

// Retrieve it
int[] similarKey = new int[] { 1, 2, 3 };
List<Integer> similarKeyAsList = new ArrayList<>(Arrays.asList(similarKey));
String data = map.get(similarKeyAsList); // Is 'test' now

【讨论】:

  • 将变量定义为最抽象的类型实际上总是更可取的,即List&lt;Integer&gt; 而不是ArrayList&lt;Integer&gt;。见Liskov substitution principle
  • 没错。我避免这样做以保持简单。但是,如果您想用List 替换int[],则最好强制执行ArrayList,这样每个人都知道基于索引的访问 可以快速工作,但这只是OP 的旁注.
  • 感谢您抽出宝贵时间为我解答。在意识到使用对象作为哈希键存在问题后,肯定会出现类似这样的重复问题。
  • @DillanShmog 欢迎您。这很好,特别是如果你不知道问题是什么。我的意思是,你应该如何搜索你不知道的东西。
猜你喜欢
  • 2023-03-19
  • 2020-11-05
  • 1970-01-01
  • 2019-11-03
  • 2011-05-09
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多