2009-07-09 26 views
14

Tôi đang cố tìm một khóa trong HashMap. Tôi có thể in khóa đã chọn bằng cách sử dụng 'get' nhưng khi tôi sử dụng 'containsKey' trong câu lệnh if, nó không được tìm thấy.Java HashMap nhận được công trình nhưng chứaKey không

Tôi BIẾT khóa hiện diện trong Bản đồ nhưng nó vẫn trả về false. Có ý tưởng nào không?

Mã của tôi:

public static boolean checkLowerStructuralSupport(Location location) { 

    boolean hasSupport = false; 

    Location supportingLocation = new Location(location.getX(), location.getY(), location.getZ() - 1); 

    System.out.println(_levels.get(supportingLocation.getZ()).getLevelSites2().get(supportingLocation)); //works 

    if (_levels.get(supportingLocation.getZ()).getLevelSites2().containsKey(supportingLocation)) { 
     hasSupport = true; 
    } else { 
     hasSupport = false; 
    } 

    return hasSupport; 
} 

Đây là mã cho lớp Location:

public class Location { 

    protected int _x; 
    protected int _y; 
    protected int _z; 

    public Location(int xAxis, int yAxis, int zAxis) { 
     this._x = xAxis; 
     this._y = yAxis; 
     this._z = zAxis; 
    } 

    public void equals() { 
     //not implemented yet 
    } 

    public void HashCode() { 
     //not implemented yet 
    } 

    public String toString() { 
     String locationString = Integer.toString(_x) + Integer.toString(_y) + Integer.toString(_z); 
     return locationString; 
    } 

    public void setX(int XAxis) { 
     this._x = XAxis; 
    } 

    public int getX() { 
     return this._x; 
    } 

    public void setY(int YAxis) { 
     this._y = YAxis; 
    } 

    public int getY() { 
     return this._y; 
    } 

    public void setZ(int ZAxis) { 
     this._z = ZAxis; 
    } 

    public int getZ() { 
     return this._z; 
    } 

} 
+0

Đây có phải là mã chính xác bạn đang chạy không? Nếu không, vui lòng cung cấp mã chính xác mà bạn đang sử dụng (tôi chỉ hỏi vì trong một câu hỏi khác, những gì bạn cung cấp không phải là đại diện cho vấn đề của bạn và gây ra nhiều nhầm lẫn). –

+0

Ngoài ra, hành vi này có phù hợp không? Tức là, nhận được (...) * luôn luôn * trả về một cái gì đó khi dự kiến, và containsKey (...) * không bao giờ * làm gì? –

+0

Bạn đang phát triển với một JDK bao gồm mã nguồn? Nếu vậy, có lẽ bạn nên thử gỡ lỗi mã và bước vào các phương pháp HashMap (containsKey và nhận được) để xem nếu bạn có thể tìm thấy bất kỳ hành vi bất thường. Đừng lo lắng, những phương pháp này cực kì phức tạp. –

Trả lời

23

Bạn phải đảm bảo rằng lớp Location đã thực hiện đúng hashCode()equals(Object) của phương pháp (documentation). Tức là, nếu hai đối tượng Location có hiệu quả như nhau, họ nên chia sẻ mã băm chung và phương thức equals của họ sẽ trả lại true.

+1

Nhưng sau đó tại sao sẽ nhận được() làm việc và containsKey() không? – butterchicken

+1

@butterchicken: Có thể get() không sử dụng tối ưu hóa "hashCode" để lấy giá trị, trong khi "containsKey" là. –

+0

Tôi đã sử dụng containsKey một cách hiệu quả trong các phương pháp khác .... bị mờ – burntsugar

2

Trong Location lớp, hãy chắc chắn bạn đang trọng hashCodebằng phương pháp.

Nếu có, bạn có thể đăng chúng không?

1

Cả hai get()containsKey() đang sử dụng phương thức hashCode() của lớp học Location. Phương thức equals() không được gọi trừ khi có xung đột băm. (do đó, getMap của HashMap sẽ không sử dụng equals() trong mọi tình huống.)

Đối với lớp Location, bạn có vô tình xảy ra để triển khai phiên bản hashCode() của riêng bạn? Phương pháp hashCode() cần được triển khai cẩn thận. Joshua Bloch mô tả tất cả các chi tiết trong cuốn sách Hiệu quả Java, các phần trong số đó đang trực tuyến ... Tôi sẽ tìm liên kết đến các chương mẫu đó: Effective Java Sample Chapters. Bạn muốn chương 3.

Như tôi đã hỏi trong nhận xét cho câu hỏi, Biến số _levels của bạn đến từ đâu? Tôi không thấy nó được khai báo bên trong phương thức đó và đặt tên của bạn (tiền tố gạch dưới, bạn có nhập quy ước đó từ một số ngôn ngữ khác không?) Gợi ý rằng nó "sống" bên ngoài phương thức này. Có lẽ mã khác đang thay đổi nó trong quá trình thực hiện? Vui lòng cho chúng tôi biết khi bạn giải quyết; Sự hồi hộp đang giết chết tôi.

+2

Tôi nghĩ cả hai đều sử dụng equals(). Hãy nhớ, nó hoàn toàn hợp pháp cho hai trường hợp của một đối tượng để trả về 'hashCode()'. Thực tế, nó không phải là bất hợp pháp đối với tất cả 'hashCode()' chỉ đơn giản trả về cùng một hằng số (ví dụ, 0), chỉ thực sự thực sự phụ tối ưu. –

+0

Không - tìm trong nguồn JDK: get và containsKey (qua getEntry) cả hai kiểm tra bình đẳng chính (trừ khi nhận dạng khóa là như nhau) trong dòng: if (e.hash == hash && ((k = e.key) Phím == || key.equals (k))) – paulcm

+1

bằng sẽ được gọi trong khi nhận được nếu có xung đột băm. –

2

containsKey sử dụng phương thức bằng để so sánh thông số với các mục nhập trong tập hợp khóa. Vì vậy, lớp Vị trí cần có phương thức bằng tốt. Phương thức equals mặc định trong java.lang.Object chỉ trả về true khi cả hai đối tượng là cùng một đối tượng. Trong trường hợp này, bạn có thể có 2 trường hợp khác nhau cần được so sánh và cần phương thức equals tùy chỉnh.

1

Để tránh sự cố, các phương pháp equals()hashCode() của bạn phải nhất quán và tuân thủ các yêu cầu (như được lưu ý ở nơi khác).

Bên cạnh đó, hashCode() nên không dựa vào các thành viên có thể thay đổi, nếu không mã băm tính của bạn có thể thay đổi, và điều này ảnh hưởng đến các hoạt động nội bộ của HashMap. Điều đó sẽ tiết lộ chính nó trong một không có khả năng để lấy các công cụ từ Hash* bộ sưu tập.

5

Như được giải thích ở đây, bạn phải ghi đè phương thức bằng (Đối tượng).

Lý do tại sao nhận được (Object) đang hoạt động là, HashMap sẽ tính Hash cho lớp Vị trí của bạn và trả về đối tượng mà hascode trỏ tới.

containsKey (Object) tính toán khóa băm và lấy đối tượng mà băm được trỏ đến. Đối tượng từ HashMap sẽ so sánh với đối tượng mà bạn đưa vào. Đối với các so sánh này, phương thức equals được sử dụng. Khi bạn không ghi đè bằng phương thức, true được trả về, khi đối tượng tham chiếu đến cùng một cá thể.

Từ HashMap

/** 
* Check for equality of non-null reference x and possibly-null y. 
*/ 
static boolean eq(Object x, Object y) { 
    return x == y || x.equals(y); 
} 

Từ Object

public boolean equals(Object obj) { 
    return (this == obj); 
    } 

Từ javadoc của bình đẳng

Các bằng phương pháp cho lớp Object thực hiện các discriminat nhất ing mối quan hệ tương đương có thể có trên các đối tượng ; tức là, đối với bất kỳ giá trị tham chiếu không rỗng là x và y, phương thức này trả về true nếu và chỉ khi x và y tham chiếu đến cùng một đối tượng (x == y có giá trị đúng).

Lưu ý rằng nó thường là cần thiết để ghi đè phương thức hashCode bất cứ khi nào phương pháp này được ghi đè, để duy trì hợp đồng chung cho các phương pháp hashCode, trong đó nêu rằng đối tượng bằng nhau phải có mã băm bằng .

+3

Tôi có thể sai, nhưng tôi khá chắc chắn có được (...) sẽ so sánh khóa (Vị trí) cho bình đẳng cũng, và sử dụng băm chỉ để xác định vị trí nó một cách nhanh chóng (giảm số lượng so sánh). Làm thế nào khác nó sẽ phân biệt giữa nhiều đối tượng với cùng một băm (đó là hoàn toàn hợp pháp). –

2

Điều duy nhất tôi có thể nghĩ rằng sẽ gây ra điều này là nếu tình trạng supportingLocation được bằng cách nào đó được biến đổi giữa get(...) cuộc gọi và containsKey(...).

Giả sử các đoạn mã bạn được đăng là mã chính xác đó là gây ra vấn đề, nơi duy nhất này có thể xảy ra là nếu một trong Location#getZ(...), Location#hashCode() hoặc Location#equals(Object) đột biến tình trạng Location (hoặc các nhà xây dựng Vị trí, hoặc một trong những phương pháp bắt đầu một chuỗi ngẫu nhiên thay đổi trạng thái của cá thể Vị trí, nhưng tôi nghĩ chúng ta có thể loại trừ nó ra).

Bạn có thể xác minh rằng không có phương pháp nào ở trên đang thay đổi trạng thái của cá thể supportingLocation? Mặc dù tôi không quen thuộc với chính lớp học Location, tôi muốn mạo hiểm để đoán rằng một lớp như thế lý tưởng là không thay đổi được.

Edit: Để làm rõ, khi tôi nói rằng Location#getZ() vv không biến đổi Vị trí, những gì tôi có nghĩa là:

Location x = new Location(1,2,3); 
Location y = new Location(1,2,3); 

boolean eq1 = x.equals(y); 
int hash1 = x.hashCode(); 
x.getZ(); // this should *not* mutate the state of x 
boolean eq2 = x.equals(y); 
int hash2 = x.hashCode(); 

Cuối cùng, EQ1 nên bằng EQ1, và hash1 nên bằng hash2.Nếu đây không phải là trường hợp, getZ() đang biến đổi trạng thái của x (hoặc bằng, hoặc hashCode, hoặc tệ hơn, các phương thức đó hoàn toàn tắt), và sẽ dẫn đến hành vi mà bạn quan sát được.

+0

Mã tôi đã đăng là triển khai chính xác. Lớp Location có các phương thức mutator. Chúc mừng. – burntsugar

+0

Đột biến không thành vấn đề. bằng và hashCode không được ghi đè, vì vậy các phiên bản mặc định sẽ được sử dụng và chúng chỉ nhìn vào con trỏ chứ không phải trạng thái của đối tượng. – finnw

+0

Yup, tôi đồng ý, câu trả lời của tôi đã được đăng trước khi mã nguồn Vị trí đầy đủ được đăng. Tôi đang ở một sự mất mát với những gì sai bây giờ, có lẽ là một bản đồ 'thực thi' rogue, hoặc một lỗi trong 'getLevelSites2()'. –

1

Tham gia đỉnh tại mã nguồn để triển khai HashMap. Cả get và containsKey đều sử dụng các phương thức hasCode() và equals() của đối tượng khóa của bạn.

Sự khác biệt duy nhất thực sự, và như đã được chỉ ra, đó là một kiểm tra null tầm thường, là trong sự so sánh:

get:

((k = e.key) == key || key.equals(k)) 

containsKey:

((k = e.key) == key || (key != null && key.equals(k))) 

nơi e là kiểu Nhập cho HashMap.

Vì vậy, nếu bạn không triển khai mạnh mẽ hashCode() và/hoặc bằng(), bạn sẽ gặp sự cố. Ngoài ra, nếu các phím của bạn bị đột biến (tôi thấy rằng bạn không khai báo các trường lớp cuối cùng), bạn có thể gặp sự cố.

Lấy ví dụ sau đây:

public class HashMapTest { 
    static class KeyCheck { 
     int value; 
     public KeyCheck(int value) { this.value = value; } 
     public void setValue(int value) { this.value = value; } 
     @Override public int hashCode() { return value; } 
     @Override public boolean equals(Object o) { 
      return ((KeyCheck)o).value == this.value; 
     } 
    } 

    public static void main(String args[]) { 
     HashMap<KeyCheck, String> map = new HashMap<KeyCheck, String>(); 
     KeyCheck k1 = new KeyCheck(5); 
     KeyCheck k2 = new KeyCheck(5); 

     map.put(k1, "Success"); 

     System.out.println("Key: " + k1 + " Get: " + map.get(k1) + 
          " Contains: " + map.containsKey(k1)); 
     System.out.println("Key: " + k2 + " Get: " + map.get(k2) + 
          " Contains: " + map.containsKey(k2)); 

     k1.setValue(10); 

     System.out.println("Key: " + k1 + " Get: " + map.get(k1) + 
          " Contains: " + map.containsKey(k1)); 
     System.out.println("Key: " + k2 + " Get: " + map.get(k2) + 
          " Contains: " + map.containsKey(k2)); 
    } 
} 

này sẽ in ra:

chính: HashMapTest $ KeyCheck @ 5 Nhận: Thành công Chứa: đúng
chính: HashMapTest $ KeyCheck @ 5 Nhận : Thành công chứa: true
Key: HashMapTest $ KeyCheck @ a Get: null Chứa: false
Key: HashMapTest $ KeyCheck @ 5 Get: null Chứa: false

Như bạn có thể thấy, trong trường hợp này, tính đột biến đã làm cho hashCode() thay đổi, làm hỏng mọi thứ.

+0

Tôi không nghĩ rằng sự khác biệt giữa get và containsKey là quan trọng, bởi vì nó chỉ dành cho khi khóa là null, và trong trường hợp đó nhận được có một đường dẫn mã trường hợp đặc biệt (mà đã được trả về). Tuy nhiên, điểm của bạn về việc thay đổi các giá trị khóa cũng được thực hiện tốt. – paulcm

0

tôi nghĩ rằng đôi khi bạn cần mã băm và đôi khi không vì vậy tôi nghĩ rằng bằng cách này bạn có thể biến của mã băm kiểm tra khi bạn muốn mua thay đổi mã băm cho tất cả các đối tượng mà bạn muốn 0

public class sample(){ 
    @JsonIgnore 
    private int hashCode = super.hashCode(); 

    public void setHashCode(int hashCode){ 
     this.hashCode = hashCode; 
    }  

    @Override 
    public int hashCode(){ 
     return this.hashCode; 
    }  

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     final ReflectObject other = (ReflectObject) obj; 
     if (this.hashCode != other.hashCode) { 
      return false; 
     } 
     return true; 
    } 
} 
Các vấn đề liên quan