自定义类型是否可以作为Map的Key问题

本文探讨使用自定义对象作为Java HashMap键的问题,解析为何直接使用会出现null值,及如何通过重写hashCode和equals方法解决。

 虽说经常用map,但是我用它的时候,key都是用String或者Integer类型,今天偶然想到,可不可以用自己定义的对象作为map的key呢?

talk is cheap give me code

class Student{
    private String name;
    private String sex;
    public String getName() {
        return name;
    }
    public Student(String name,String sex){
        this.name=name;
        this.sex=sex;
    }
    public Student(){}
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    public static void main(String[] args) {
        Map<Student,String> map = new HashMap();
        Student s1 = new Student();
        s1.setName("一号");
        s1.setSex("男");
        Student s2 = new Student();
        s2.setName("二号");
        s2.setSex("女");
        map.put(s1, "111");
        map.put(s2, "222");

        Set set = map.keySet(); //试了试遍历key,然后获取
        for(Object s:set){
            System.out.println(map.get(s));
        }
        map.put(new Student("二号","女"),"333");
        System.out.println(map.get(new Student("二号","女"))); //试了试单独根据key获取值
    }
}

 

结果是这个,为何会存在一个null呢????

因为map存储获取都是根据hashcode值和equals方法有联系,前边将map的key拿到再放进去获取value,并没有发生对象的变化,hashcode值也不变,下边我又以新的对象去map中获取,此时的hashcode值已经变了,所以返回为null,因为那是我新new的对象所以,map已经不认识了。

怎么才能解决这个问题呢。重写Student的hashcode方法和equals方法就可以了,如下:

class Student{
    private String name;
    private String sex;
    public String getName() {
        return name;
    }
    public Student(String name,String sex){
        this.name=name;
        this.sex=sex;
    }
    public Student(){}
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }@Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((sex == null) ? 0 : sex.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (sex == null) {
            if (other.sex != null)
                return false;
        } else if (!sex.equals(other.sex))
            return false;
        return true;
    }

    public static void main(String[] args) {
        Map<Student,String> map = new HashMap();
        Student s1 = new Student();
        s1.setName("一号");
        s1.setSex("男");
        Student s2 = new Student();
        s2.setName("二号");
        s2.setSex("女");
        map.put(s1, "111");
        map.put(s2, "222");

        Set set = map.keySet(); //试了试遍历key,然后获取
        for(Object s:set){
            System.out.println(map.get(s));
        }
        map.put(new Student("二号","女"),"333");
        System.out.println(map.get(new Student("二号","女"))); //试了试单独根据key获取值
    }
}

 

综上:如果要以可变对象作为key的话,需要重写hashcode和equal方法来达到这个目的。

本篇博客是刚刚踩坑之后写的,之前的博主坑还是很明显的。附上链接https://blog.youkuaiyun.com/wohaqiyi/article/details/57083444

 

<think>我们正在回答用户的问题:在Go语言中,哪些类型可以作为map的键(key)? 根据引用[1][3][4]的内容,我们知道在Go语言中,map的键必须是可比较的类型。具体来说,这些类型需要支持`==`和`!=`操作符,因为map需要通过键的相等性来判断唯一性。 引用[3]提到:除了slice、map、functions这几种类型,其他类型都是可以的。具体包括: - 布尔值(boolean) - 数字(numeric,包括整型、浮点型,以及复数) - 字符串(string) - 指针(pointer) - 通道(channel) - 接口类型(interface) - 结构体(structs)和数组(arrays),但要求它们包含的元素都是可比较的类型。 引用[4]也给出了类似的说明,并强调:两个指针类型相等表示指向同一个变量或者同为nil;两个通道类型相等表示被相同的make调用创建或者同为nil;两个接口类型相等表示动态类型和动态值都相等或者同为nil。 因此,我们可以整理出以下内容: ### 可以作为map键的类型: 1. **基本类型**: - 布尔类型(bool) - 整数类型(int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr) - 浮点数类型(float32, float64)[注意:由于浮点数的精度问题,通常不推荐用作key,但语法上允许] - 复数类型(complex64, complex128)[同样需要注意精度问题] - 字符串(string) 2. **复合类型**(要求其元素或字段都是可比较的类型): - 数组(array):例如`[2]int`,但注意数组的长度也是类型的一部分,不同长度的数组是不同的类型。 - 结构体(struct):结构体的所有字段都必须是可比较的类型。 3. **引用类型**(但有限制): - 指针(pointer):例如`*int`,比较的是指针指向的地址(或者同为nil)。 - 通道(channel):比较的是通道的创建引用(即是否由同一个make创建)或同为nil。 - 接口(interface):比较的是接口的动态类型和动态值(注意:如果两个接口的动态类型相同且动态值相等,或者同为nil,则相等)。 ### 不能作为map键的类型: - 切片(slice) - 映射(map) - 函数(function) - 包含上述不可比较类型的数组或结构体 ### 原因: 这些不可比较的类型(slice, map, function)不支持`==`操作符,因此不能作为map的键。如果尝试使用这些类型作为键,编译器会报错。 ### 示例代码: ```go package main import "fmt" func main() { // 合法键类型示例 m1 := make(map[string]int) // 字符串作为键 m1["key"] = 1 m2 := make(map[float64]int) // 浮点数作为键(语法允许,但需谨慎) m2[3.14] = 2 type Point struct { X, Y int } m3 := make(map[Point]string) // 结构体作为键(结构体的所有字段都是可比较的) m3[Point{1,2}] = "point" // 非法键类型示例 // m4 := make(map[[]int]int) // 编译错误:invalid map key type []int } ``` ### 注意事项: 1. **浮点数作为键**:由于浮点数的精度问题,两个数学上相等的浮点数可能因为二进制表示不同而不相等。因此,除非有特殊控制,否则不推荐使用浮点数作为键。 2. **结构体和数组作为键**:它们必须是可比较的,即不能包含不可比较的元素(如slice)。例如,以下结构体不能作为键: ```go type BadKey struct { sliceField []int // 包含一个slice,不可比较 } ``` 3. **接口类型作为键**:如果接口的动态值是不可比较的类型(如slice),那么当两个接口具有相同的动态类型(不可比较)时,在运行时进行键比较会导致panic。例如: ```go var m map[interface{}]int m[[]int{1,2}] = 1 // 运行时panic: runtime error: hash of unhashable type []int ``` ### 引用总结: 根据引用[3][4],可比较的类型都可以作为map key,包括布尔值、数字、字符串、指针、通道、接口、以及仅包含可比较类型的结构体和数组[^3][^4]。 ### 相关问题: 根据用户可能感兴趣的延伸问题,我们可以提出以下相关问题: §§相关问题§§ 1. 为什么Go语言中slice类型不能作为map的键? 2. 在Go语言中,如何实现使用自定义结构体作为map键时的正确比较? 3. 浮点数类型作为map键时可能遇到哪些问题?如何避免? 4. 接口类型作为map键时需要注意什么? 5. Go语言中map的键类型是否影响查找性能?</think>### Go语言中可作为 map 键(key)的类型 在Go语言中,**map 的键必须是可比较的类型**,即支持 `==` 和 `!=` 操作符的类型。以下是可作 map 键的类型分类及说明: #### ✅ 允许作为键的类型 1. **基本类型**: - 布尔值(`bool`) - 数字类型: - 整型(`int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8` 等) - 浮点型(`float32`, `float64`)[^3][^4] - 复数(`complex64`, `complex128`) - 字符串(`string`) 2. **复合类型**: - 指针(`pointer`):指向相同内存地址或同为 `nil` 时视为相等[^4] - 通道(`channel`):由相同 `make` 调用创建或同为 `nil` 时相等[^4] - 接口(`interface`):动态类型和动态值均相等或同为 `nil` 时相等[^4] 3. **容器类型**: - 结构体(`struct`):所有字段均为可比较类型[^3][^4] - 数组(`array`):所有元素均为可比较类型[^3][^4] ```go // 合法示例 type Point struct { X, Y int } m1 := make(map[Point]bool) // 结构体作键 m2 := make(map[[2]int]string) // 数组作键 ``` #### ❌ 禁止作为键的类型 1. **不可比较类型**: - 切片(`slice`) - 映射(`map`) - 函数(`function`) ```go // 非法示例(编译错误) m3 := make(map[[]int]bool) // 错误:invalid map key type []int m4 := make(map[map[int]int]bool) // 错误:invalid map key type map[int]int ``` 2. **包含不可比较元素的复合类型**: ```go type InvalidKey struct { SliceField []int // 包含切片字段 } m5 := make(map[InvalidKey]bool) // 错误:invalid map key type InvalidKey ``` #### ⚠️ 注意事项 1. **浮点数精度问题** 浮点数作键时需注意精度误差(如 `0.1 + 0.2 != 0.3`),可能导致意外行为[^3]: ```go m := make(map[float64]string) m[0.1 + 0.2] = "A" fmt.Println(m[0.3]) // 可能输出空值(因浮点精度) ``` 2. **接口类型的运行时风险** 若接口动态值为不可比较类型(如 `[]int`),会引发运行时 panic: ```go var key interface{} = []int{1,2} m := make(map[interface{}]int) m[key] = 1 // panic: runtime error: hash of unhashable type []int ``` 3. **结构体/数组的深度比较** 结构体或数组作键时,会递归比较所有字段/元素: ```go type Key struct { A, B int } m := make(map[Key]bool) m[Key{1, 2}] = true fmt.Println(m[Key{1, 2}]) // true(值相等) fmt.Println(m[Key{1, 3}]) // false ``` ### 总结 | **类型分类** | **是否允许** | 示例 | |--------------------|--------------|---------------------| | 基本类型 | ✅ | `int`, `string` | | 指针/通道/接口 | ✅ | `*int`, `chan int` | | 结构体/数组 | ✅(需元素可比较)| `[2]int`, `struct{X int}` | | 切片/映射/函数 | ❌ | `[]int`, `map[int]int` | | 含不可比较字段的类型| ❌ | `struct{ Slice []int }` | > 核心原则:**类型必须支持 `==` 操作符**,且不包含切片、映射或函数[^3][^4]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值