【java】HashMap中自定义类型key以及修改后的查找问题

    HashMap集合中的储存的是偶对象,即键值对应关系:key = value。在调用put()方法添加数据时,保存的顺序并不是添加的顺序。简单来理解就是,首先根据key的hashCode进行一定的运算来实现分类,保存在对应的“桶”中。在数据量较小时,运算所得值相同的对象在同一“桶”中是以链表的形式存在的;当数据增大到一定量(未达到集合扩容条件前),则变为红黑树的形式储存,以提高查询等方面的效率。

    同样的,而在使用get()方法进行取出/查询时,首先是根据传入的key的hashCode找到所在的“桶”,再在此桶中使用key的equals()方法进行对比查询。

    由上可知,如果要使用自定义类的对象作为key,需要覆写hashCode()和equals()方法,否则它将会调用Object父类的方法,无法满足需要的对比条件。

一:自定义Person类作为key,并覆写hashCode()和equals():

package com.java.demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
class Person {
	private String name;
	public Person() {}
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public int hashCode() {	    //覆写hashCode()方法
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {    //覆写equals()方法
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	public String toString() {
		return "姓名:"+ this.name;
	}
}

二:测试类:

public class TestDemo {
	public static void main(String[] args){
		Map keyPerson = new HashMap();
		Person person = new Person("李四");
		Person person2 = person;
		keyPerson.put(person, new String("LS"));
		keyPerson.put(new Person("一"), new String("1"));
		keyPerson.put(new Person("二"), new String("2"));
		
		System.out.println(keyPerson.get(person));    //LS
		System.out.println(keyPerson.get(person2));    //LS
		System.out.println(keyPerson.get(new Person("李四")));    //LS
		
	}
}

运行结果如预期:

LS
LS

LS

    那么如果此时将person对象的name属性修改会如何呢:person.setName("滚滚");?

public class TestDemo {
	public static void main(String[] args){
		Map keyPerson = new HashMap();
		Person person = new Person("李四");
		Person person2 = person;
		keyPerson.put(person, new String("LS"));
		keyPerson.put(new Person("一"), new String("1"));
		keyPerson.put(new Person("二"), new String("2"));
		
		person.setName("滚滚");
		Iterator> iter = keyPerson.entrySet().iterator();
		while(iter.hasNext()) {
			Map.Entry entry= iter.next();
			Person p = entry.getKey();
			String s = entry.getValue();
			System.out.println(p+" = "+s);
		}
		
		System.out.println();
		System.out.println(keyPerson.get(new Person("李四")));//null
		System.out.println(keyPerson.get(new Person("滚滚")));//null
		System.out.println(keyPerson.get(person));//null
		System.out.println(keyPerson.get(person2));//null
	}
}

运行结果:

姓名:滚滚 = LS
姓名:二 = 2
姓名:一 = 1

null
null
null

null

    迭代查询可以发现,person及其对应的值LS都还在Map集合中,只是person的内容改变了:李四→滚滚。但是后面的get()方法,不论是用之前的new Person("李四"),还是new Person("滚滚")/person对象,均无法找到对应值,返回为null。

    为什么呢?

    (此时数据较小,暂时只讨论链表存储的内部结构。)

    之前对HashMap的储存和查询取出有一定的了解,那么就知道了,在修改之前,根据person:new Person("李四")对象的hashCode,数据储存在一个对应的“桶”中(比如桶2中),在setName后,person的hashCode和内容均发生了变化,改变后的hashCode本应该是对应桶3,但是此时它还是在桶2中。

【java】HashMap中自定义类型key以及修改后的查找问题_第1张图片

    此后在调用get()方法查找时,“滚滚”是在桶3中查找,桶3中没有对应数据,所以返回null;“李四”是在桶2中查询,但是此时桶二中的key值已变为“滚滚”,equals()方法返回false,即也找不到对应值,get()返回null。

【java】HashMap中自定义类型key以及修改后的查找问题_第2张图片

    

    为了印证,我们将“李四”和“滚滚”的hashCode统一为一样的,即保证即使setName()后,key的hashCode对应的仍然是同一个桶,这样在get()查询时,是在同一个桶中查询的:

@Override
	public int hashCode() {	//覆写hashCode()方法
		if(this.name.equals("李四")||this.name.equals("滚滚")) {
			return 37;
		}
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

get():

              System.out.println(keyPerson.get(new Person("李四")));//null
		System.out.println(keyPerson.get(new Person("滚滚")));//LS
		System.out.println(keyPerson.get(person));//LS
		System.out.println(keyPerson.get(person2));//LS

运行结果如预期:

null
LS
LS

LS

完整代码:

package com.java.demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
class Person {
	private String name;
	public Person() {}
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public int hashCode() {	//覆写hashCode()方法
		if(this.name.equals("李四")||this.name.equals("滚滚")) {
			return 37;
		}
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {//覆写equals()方法
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	public String toString() {
		return "姓名:"+ this.name;
	}
}

public class TestDemo {
	public static void main(String[] args){
		Map keyPerson = new HashMap();
		Person person = new Person("李四");
		Person person2 = person;
		keyPerson.put(person, new String("LS"));
		keyPerson.put(new Person("一"), new String("1"));
		keyPerson.put(new Person("二"), new String("2"));
		
		System.out.println(keyPerson.get(person));//LS
		System.out.println(keyPerson.get(person2));//LS
		System.out.println(keyPerson.get(new Person("李四")));//LS
		System.out.println();
		
		person.setName("滚滚");
		Iterator> iter = keyPerson.entrySet().iterator();
		while(iter.hasNext()) {
			Map.Entry entry= iter.next();
			Person p = entry.getKey();
			String s = entry.getValue();
			System.out.println(p+" = "+s);
		}
		
		System.out.println();
		System.out.println(keyPerson.get(new Person("李四")));//null
		System.out.println(keyPerson.get(new Person("滚滚")));//LS
		System.out.println(keyPerson.get(person));//LS
		System.out.println(keyPerson.get(person2));//LS
	}
}

总结:

  • 在使用HashMap时,key值尽量采用基本数据类型和String(String的特殊性:不可更改),因为其中的hashCode()和equals()方法已被覆写好(也可以保证key的不变性????);
  • 如果要使用自定义类型作为key,需要覆写hashCode()和equals()方法;如果可以,将自定义类型变为不可修改的(如:类中属性设为final);
  • 在存入集合后,尽量不要去修改key/value的值;如果必须修改,应该原数据全部删除,再将修改后的数据重新存入。

    

你可能感兴趣的:(note,java)