`

注意可变的 hashCode()

 
阅读更多

Map 是很好的集合,为我们带来了在其他语言(比如 Perl)中经常可见的好用的键/值对集合。 JDK 以 HashMap 的形式为我们提供了方便的 Map 实现,它在内部使用哈希表实现了对键的对应值的快速查找。 但是这里也有一个小问题:支持哈希码的键依赖于可变字段的内容,这样容易产生 bug,即使最耐心的 Java 开发人员也会被这些 bug 逼疯。

假设清单 3 中的 Person 对象有一个常见的 hashCode() (它使用 firstName、lastName 和 age 字段 — 所有字段都不是 final 字段 — 计算 hashCode() ), 对 Map 的 get() 调用会失败并返回 null

清单 3. 可变 hashCode() 容易出现 bug

 

// Person.java
import java.util.*;

public class Person implements Iterable<Person> {
    public Person(String fn, String ln, int a, Person... kids) {
        this.firstName = fn; this.lastName = ln; this.age = a;
        
        for (Person kid : kids)
            children.add(kid);
    }
    
    // ...
    
    public void setFirstName(String value) { this.firstName = value; }
    public void setLastName(String value) { this.lastName = value; }
    public void setAge(int value) { this.age = value; }
    
    public int hashCode() {
        return firstName.hashCode() & lastName.hashCode() & age;
    }

    // ...

    private String firstName;
    private String lastName;
    private int age;
    private List<Person> children = new ArrayList<Person>();
}


// MissingHash.java
import java.util.*;

public class MissingHash {
    public static void main(String[] args) {
        Person p1 = new Person("Ted", "Neward", 39);
        Person p2 = new Person("Charlotte", "Neward", 38);
        System.out.println(p1.hashCode());
        
        Map<Person, Person> map = new HashMap<Person, Person>();
        map.put(p1, p2);
        
        p1.setLastName("Finkelstein");
        System.out.println(p1.hashCode());
        
        System.out.println(map.get(p1));
    }
}


很显然,这种方法很糟糕,但是解决方法也很简单:永远不要将可变对象类型用作 HashMap 中的键。
分享到:
评论

相关推荐

    HashCode相同equals不同的2位字符集合算法

    这个问题在处理自定义类对象时尤为关键,特别是当这些对象包含可变元素,如字符集合时。 首先,让我们理解这两个方法的基本概念: 1. **hashCode()**:这个方法是Object类中的,返回一个整数值,代表对象的哈希码...

    string-hashcode:java.lang.String.hashCode

    字符串哈希码 字符串的其他实用程序。安装npm install string-hashcode 例子var hashCode = require ( 'string-hashcode' ) ;var s = 'abc' ;... 请注意,哈希码对于特定字符串是不可变的。执照麻省理工学院

    vals:来自标准Java接口的可扩展的不可变值对象

    该实现是不可变的,并基于定义的属性实现toString,equals和hashCode。 @Val接口可以使用命名约定来否决默认的hashCode,equals和toString方法。 除非方法为@ javax.annotation.Nullable,否则在构造/

    Java 一些值得注意的细节

    2. **字符串常量池**:Java字符串是不可变的,所有的字符串字面量都存储在字符串常量池中。使用`+`连接字符串时,每次连接都会创建新的字符串对象,效率较低。推荐使用`StringBuilder`或`StringBuffer`进行拼接。 3...

    Java 基础(3-8) - 图谱 & Q-A.pdf

    `String`在Java中是不可变的,主要是为了线程安全和缓存`hashcode`。从Java 7开始,`switch`语句支持`String`类型。不可变对象意味着一旦创建,其状态不能改变,创建不可变对象通常使用私有构造函数和final字段。...

    Java解惑PPT6

    这个例子展示了`BigInteger`类的一个特性,即其实例是不可变的。这意味着一旦创建了一个`BigInteger`对象,就不能改变它的值。在代码的第7-9行,我们看到尝试通过调用`add()`方法累加`total`,但实际的`total`并没有...

    java个人总结.docx

    `StringBuffer`和`StringBuilder`都是可变的,用于在字符串操作中提供更好的性能。`StringBuffer`是线程安全的,适合多线程环境,而`StringBuilder`则在单线程环境中更快,因为它不需要同步。它们都继承自`...

    effective-java 配套代码

    4. **可变与不可变对象(Mutable vs Immutable Objects)**: 通过示例代码展示了如何创建不可变对象,以及不可变对象的益处和实现策略。 5. **泛型(Generics)**: 书中深入讲解了Java泛型的用法,包括类型擦除、...

    【牛客网】Java开发校招面试考点汇总(附面试题和答案).pdf

    - String与StringBuffer的区别:String是不可变的,每次修改都会生成新的String对象;StringBuffer是可变的,适合大量字符串操作。 5. Java异常处理 - 关键字throws、throw、try、catch、finally的含义:throws...

    effecctivejava 第三版中文

    注意equals()和hashCode()的一致性原则。 12. **优先考虑异常安全的函数**:设计函数时,应尽量避免在正常路径上抛出异常,避免"空指针异常"等常见错误。 13. **多态优于条件表达式**:通过多态实现代码的扩展性和...

    Java Interview Questions for 5 years Experience.pdf

    该参数类型是数组类型,用于接收可变数量的参数。 5. 线程安全的单例模式 线程安全的单例模式可以通过双重检查锁定和volatile关键字来实现。首先,检查实例是否存在,如果不存在,则创建实例,并使用volatile...

    JAVA文档

    值得注意的是,String对象是不可变的,这意味着一旦创建,就不能更改其内容。例如,当我们执行`s = s + " world!"`时,s实际上并未改变,而是创建了一个新的String对象,原来的对象依然存在,但s现在指向了新对象。 ...

    java初学者要注意的

    Java中的String类是不可变的,这意味着一旦创建了一个String对象,就不能更改它的内容。例如: ```java String s = "Hello"; s = s + " world!"; ``` 上述代码中,`s`并没有改变,而是创建了一个新的String...

    Java 实例 - 数组转集合源代码-详细教程.zip

    在上面的例子中,`Arrays.asList(intArray)`会将数组转换为一个不可变的列表,然后`new ArrayList(...)`创建了一个可变的`ArrayList`,并将其初始化为之前列表的内容。 对于对象数组,转换过程相似,但需要注意的是...

    eclipse插件 lombok.jar

    如果一个类使用了 `@Data`,那么默认所有字段都是可变的,如果需要创建不可变对象,可以使用 `@Value` 注解。 - 配合使用 `@Builder` 和 `@AllArgsConstructor` 可以构建更方便的对象构造方式。 总之,Lombok 是一...

    Java常用类day09.docx

    - `String`是不可变的,一旦创建就不能修改,适合在不需要修改字符串的情况下使用,效率较高。 - `StringBuffer`是线程安全的可变类,适用于多线程环境下的字符串操作。 - `StringBuilder`是线程不安全的可变类,它...

    Lombok安装及使用

    11. **@Value**:类似于@Data,但生成的类为final,字段为private且不可变,没有setter,适用于不可变对象。 12. **@NonNull**:表示字段不允许为null,可用于校验输入。 请注意,虽然Lombok极大地简化了代码,但...

    EFFECT JAVA 中文

    5. **不可变对象**:不可变对象一旦创建,其状态就不能改变。它们更易于并发编程,且不易出错。书中介绍了如何构建不可变对象,包括避免提供修改状态的方法、确保正确初始化以及使所有字段都是final的。 6. **避免 ...

    autoworld:汽车世界

    创建一个不可变的类牌照(不可变的  final 字段)。 将类放在包 be.vdab.vehicles.div 中有一个构造函数,它接受一个字符串板并具有默认可见性。 提供一个 getPlate()。 提供一个toString、一个equals 和一个...

Global site tag (gtag.js) - Google Analytics