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 中的键。
分享到:
相关推荐
这个问题在处理自定义类对象时尤为关键,特别是当这些对象包含可变元素,如字符集合时。 首先,让我们理解这两个方法的基本概念: 1. **hashCode()**:这个方法是Object类中的,返回一个整数值,代表对象的哈希码...
字符串哈希码 字符串的其他实用程序。安装npm install string-hashcode 例子var hashCode = require ( 'string-hashcode' ) ;var s = 'abc' ;... 请注意,哈希码对于特定字符串是不可变的。执照麻省理工学院
该实现是不可变的,并基于定义的属性实现toString,equals和hashCode。 @Val接口可以使用命名约定来否决默认的hashCode,equals和toString方法。 除非方法为@ javax.annotation.Nullable,否则在构造/
2. **字符串常量池**:Java字符串是不可变的,所有的字符串字面量都存储在字符串常量池中。使用`+`连接字符串时,每次连接都会创建新的字符串对象,效率较低。推荐使用`StringBuilder`或`StringBuffer`进行拼接。 3...
`String`在Java中是不可变的,主要是为了线程安全和缓存`hashcode`。从Java 7开始,`switch`语句支持`String`类型。不可变对象意味着一旦创建,其状态不能改变,创建不可变对象通常使用私有构造函数和final字段。...
这个例子展示了`BigInteger`类的一个特性,即其实例是不可变的。这意味着一旦创建了一个`BigInteger`对象,就不能改变它的值。在代码的第7-9行,我们看到尝试通过调用`add()`方法累加`total`,但实际的`total`并没有...
内容创世纪.md基本类型方法.mdjshell 与 java 对比数字.md控制流.md界面.mdlambda.md列表和地图.md字符串格式化.md类和封装.mdequals_hashCode_toString 函数合同.md可修改与可变空值和可选值.md继承.md异常.md枚举....
`StringBuffer`和`StringBuilder`都是可变的,用于在字符串操作中提供更好的性能。`StringBuffer`是线程安全的,适合多线程环境,而`StringBuilder`则在单线程环境中更快,因为它不需要同步。它们都继承自`...
4. **可变与不可变对象(Mutable vs Immutable Objects)**: 通过示例代码展示了如何创建不可变对象,以及不可变对象的益处和实现策略。 5. **泛型(Generics)**: 书中深入讲解了Java泛型的用法,包括类型擦除、...
- String与StringBuffer的区别:String是不可变的,每次修改都会生成新的String对象;StringBuffer是可变的,适合大量字符串操作。 5. Java异常处理 - 关键字throws、throw、try、catch、finally的含义:throws...
注意equals()和hashCode()的一致性原则。 12. **优先考虑异常安全的函数**:设计函数时,应尽量避免在正常路径上抛出异常,避免"空指针异常"等常见错误。 13. **多态优于条件表达式**:通过多态实现代码的扩展性和...
该参数类型是数组类型,用于接收可变数量的参数。 5. 线程安全的单例模式 线程安全的单例模式可以通过双重检查锁定和volatile关键字来实现。首先,检查实例是否存在,如果不存在,则创建实例,并使用volatile...
值得注意的是,String对象是不可变的,这意味着一旦创建,就不能更改其内容。例如,当我们执行`s = s + " world!"`时,s实际上并未改变,而是创建了一个新的String对象,原来的对象依然存在,但s现在指向了新对象。 ...
Java中的String类是不可变的,这意味着一旦创建了一个String对象,就不能更改它的内容。例如: ```java String s = "Hello"; s = s + " world!"; ``` 上述代码中,`s`并没有改变,而是创建了一个新的String...
在上面的例子中,`Arrays.asList(intArray)`会将数组转换为一个不可变的列表,然后`new ArrayList(...)`创建了一个可变的`ArrayList`,并将其初始化为之前列表的内容。 对于对象数组,转换过程相似,但需要注意的是...
如果一个类使用了 `@Data`,那么默认所有字段都是可变的,如果需要创建不可变对象,可以使用 `@Value` 注解。 - 配合使用 `@Builder` 和 `@AllArgsConstructor` 可以构建更方便的对象构造方式。 总之,Lombok 是一...
- `String`是不可变的,一旦创建就不能修改,适合在不需要修改字符串的情况下使用,效率较高。 - `StringBuffer`是线程安全的可变类,适用于多线程环境下的字符串操作。 - `StringBuilder`是线程不安全的可变类,它...
11. **@Value**:类似于@Data,但生成的类为final,字段为private且不可变,没有setter,适用于不可变对象。 12. **@NonNull**:表示字段不允许为null,可用于校验输入。 请注意,虽然Lombok极大地简化了代码,但...
5. **不可变对象**:不可变对象一旦创建,其状态就不能改变。它们更易于并发编程,且不易出错。书中介绍了如何构建不可变对象,包括避免提供修改状态的方法、确保正确初始化以及使所有字段都是final的。 6. **避免 ...