------- android培训、java培训、期待与您交流! ----------
HashCode与equals方法详解
1. 首先equals()和hashcode()这两个方法都是从object类中继承过来的。
equals()方法在object类中定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。
2. hashCode方法是返回对象的哈希码值
定义如下:
Public inthashCode()
{
retuen value;
}
3. 而在collection集合中,HashCode和equals方法结合使用,下面详细分析一下程序构造,帮组理解collection中各种方法之间的潜在关系。
Example:
⑴. import java.util.*;
class univer
{
public static void main(String [] args)
{
ArrayList al = new ArrayList();
①al.add(new Person("john01",21)); al.add(new Person("john02",22)); al.add(new Person("john03",23)); al.add(new Person("john04",24));
|
Iterator it = al.iterator();
while(it.hasNext())
{
②Person p = (Person)it.next(); |
sop(p.getName()+""+p.age());
③ //sop(it.next().getName()+" "+it.next().p.age());
|
}
}
public static voidsop(Object obj)
{
System.out.println(obj);
}
}
class Person{
private String name;
private int age;
Person(String name,intage)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int age()
{
return age;
}
}
这个程序说明了一些迭代器基础,下面做一个分析:
①. 这几句话都用到了add方法,注意add方法的定义为:add(Object obj), ,而add(new Person(""));传参为new Person。既有:Object obj = new Person();故而就把new person提升为Object类型。所以,在这里,person类型被提升了,需要特别注意。
②. 首先要明确next方法返回的是Object类型的对象。故而可以用(Person)it.next();来强制转换被提升的it迭代器指向的Object obj=new Person()对象。
③. 第三条语句是被注释掉的,用来更好的说明it.next()的一些问题。
第一个it.next()打印的是jhon01,而第二个it.next()打印的是jhon02的年龄22;
因为it.next()是指向下一个对象地址的迭代器,故每次循环,it.next()都会指向下一个对象。注意:it.next()返回的类型为Object类型,因为迭代器不知道他将要接收什么类型,故都设为Object类型。
⑵.Example:
import java.util.*;
class univer
{
publicstatic void main(String [] args)
{
ArrayListal = new ArrayList();
al.add(newPerson("john01",21));
al.add(newPerson("john02",22));
al.add(newPerson("john03",23));
al.add(newPerson("john03",23));
al.add(newPerson("john04",24));
al.add(newPerson("john04",24));//注意;每new一次,都为不同的对象。
al=singleEelement(al);
Iteratorit = al.iterator();
while(it.hasNext())
{
Personp = (Person)it.next();
sop(p.getName()+""+p.age());
}
}
publicstatic void sop(Object obj)
{
System.out.println(obj);
}
publicstatic ArrayList singleEelement(ArrayList al)//代码块,去除相同元素。
{
ArrayList newAl = new ArrayList();//定义一个临时容器。
Iteratorit = al.iterator();
while(it.hasNext())
{
Objectobj = it.next();
① if(!newAl.contains(obj)) { newAl.add(obj); }
|
}
returnnewAl;
}
}
class Person{
privateString name;
privateint age;
Person(Stringname,int age)
{
this.name=name;
this.age=age;
}
② public boolean equals(Object obj) |
{
③ if(!(obj instanceof Person))
|
//instanceof,判断obj是否为Person类的一个实例。
returnfalse;
Personp = (Person)obj;
④ return this.name.equals(name)&&this.age==p.age;
|
}
publicString getName()
{
returnname;
}
publicint age()
{
returnage;
}
}
首先说明,这个代码块是去除相同元素,通过public static ArrayList singleEelement(ArrayList al)与public boolean equals(Object obj)函数的共同合作来完成这个功能。
设计思想是:通过publicstatic ArrayList singleEelement(ArrayList al)方法,定义一个临时容器,然后把去除相同元素的对象存入临时容器,然后返回给主容器。
下面我们做个一个详细的分析:
①.第一步:开始newAl为null,显然,null和al中的第一个对象不相同,那么newAl.contains(obj)就为false,加个!为true,就执行newAl.add(obj),然后,newAll集合对象中就存入了元素;
第二步:在while循环中继续newAll.contains(obj)的执行。这次new Person("john02",22)进入,contains将会自动调用equals方法来比较,两个内容是否相同。如果不同,就继续存入newAll.然后john03再进来与newAll中每个元素进行比较,如果相同就存入临时容器。
contains(Objectobj),参数obj传过来后,即为: newAll (obj==null ? e==null: obj.equals(e)) 的元素 e 时才返回 true。
例如:list.contains(o),系统会对list中的每个元素e调用o.equals(e) 方法, 加入list中有n个元素,那么会调用n次o.equals(e), 只要有一次o.equals(e)返回了true,那么list.contains(o)返回true,否则返回false。建议覆盖equals方法。
第三:
具体就本程序说一下运行流程:
第一步:开始newAl为null,显然,null和al中的第一个对象不相同,那么newAl.contains(obj)就为false,加个!为true,就执行newAl.add(obj),然后,newAll集合对象中就存入了元素;john01就进入了newAll中。
第二步:john02(为了方便,用john02代表new Person(“john02”,22))进入obj;然后newAll中元素john01自动调用equals方法来比较他们内容是否相同,经过比较,不相同,然后把john02存入newAll。
第三步:john03进入,然后newAll中john02,john01分别调用equals函数与john03比较,发现都不相同,故把john03存入newAll。
第四步:下面又一个john03进入,原理是newAll中所有对象都调用一次equals方法,来比较是否相同,然而newAll中john03第一次调用时就发现他们元素相同,故把thisjohn03舍去,不在存入newAll,而后面也不再需要比较了。以下后来的元素同以上三步相同。
②.public boolean equals(Objectobj)
equals方法继承自Object类,它是比较两个对象的内容是否相同。
③ .if(!(obj instanceof Person))
这句话的意思是判断:obj不是Person类的一个实例。当然去掉!符号,那么obj是Person的一个实例。
obj instanceofPerson 表示:obj是否为Person类的一个实例,如果是:返回true。显然本程序obj是Person的一个实例,返回为true。但是加了一!符合。
④. returnthis.name.equals(name)&&this.age==p.age;
这句话,我就想解释一下这里的this和p所表示的含义:
当john01进入存入newAll后,john02进来,执行到newAll.contains(obj)时,就转入了equals方法,那么当时this就代表当前对象的john02,p就代表john01.
总的来讲:就是需要newAl中的每个元素都调用一次equals方法。
注意:sop("remove:"+al.remove(new Person("john03",22))),删除al中的john03,22;但是注意:仅凭这一句,不和public boolean equals(Object obj)这个函数结合使用,那么,remove还是失败。原因在于:new一次Person,就为不同的对象,不同对象实际上具有不同的地址。
而且remove在底层也要使用equals方法,和contains一样。用要删除的元素与al中每个元素比较,相同即为要删元素。
注意:List集合判断元素是否相同,依据的是元素equals方法。其他集合不一样,特别注意。
注意:contains方法是关键点:之前之所以去掉重复元素不成功,
* 是因为没有public boolean equals(Object obj)这个函数,
* 因为contains函数运行时会自动调用equals函数来比较元素是否相同。
* API文档就有说明:
⑶.Example:
import java.util.*;
class univer
{
public static void main(String [] args)
{
HashSet al = new HashSet();
al.add(new Person("john01",21));
al.add(new Person("john02",22));
al.add(new Person("john03",23));
al.add(new Person("john03",23));
al.add(new Person("john04",24));
al.add(new Person("john04",24));
//al=singleEelement(al);
Iterator it = al.iterator();
while(it.hasNext())
{
Person p = (Person)it.next();
sop(p.getName()+"......"+p.age());
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
class Person{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public int hashCode()
{
System.out.println(this.name+".......hashCode");
return 60;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
System.out.println(this.name+"...equals..."+p.name);
return this.name.equals(name)&&this.age==p.age;
}
public String getName()
{
return name;
}
public int age()
{
return age;
}
}
①.System.out.println(this.name+"...equals..."+p.name);
this表示当前对象,p表示参数obj;特别注意this和p所代表的含义:
这里明确说一下:
②.
第一步:john01调用hashCode后返回哈希码,然后存入内存。结束当前对象的旅程。
第二步:john02调用hashCode后返回相同的哈希码值,存入内存时,发现此位置已被占据,然后john01对象就调用equals方法比较其内容是否相同,《注意是john01发起的调用》内容不相同,john02将被散列存入内存。
第三步:john03进入调用hashCode函数,当然的明确现时刻当前对象为john03,这很重要。还是返回相同的哈希吗,然后john03分别于john02比,john02调用equals方法,然后与john01比, john01调用equals方法。具体过程就是这样。与前面contains方法有异曲同工之妙。
③
程序运行步骤:
首先明确:HasHSet类容器会自动调用hashCode方法,
如果像本程序hashCode返回的哈希值都为60;
这就说明Hashset对象具有相同的地址3c,那么如果HashSet对象具有相同的地址,
就必须调用equals方法,john01,jhon02先后调用hashCode方法,
返回了相同的地址,故而调用equals方法。john02与john01比较,发现内容不同,故把john02存入。
下面在是john03调用hashCode方法,返回地址3c,john03分别于john02,john01比较,发现内容都不通,
然后存入,依次类推,发现内容相同时,不存入。
去除内容相同元素。
为什么要这样,因为HashSet不允许相同元素(equals==ture)同时存在在结构中。
假如employeeX(1111,“张三”)和employee(1111,"李四"),
而Employee.equals比较的是name。这样的话,
employeeX和employeeY的equals不相等。
它们会根据相同的散列码1111加入到同一个散列单元所指向的列表中。
这种情况多了,链表的数据将很庞大,散列冲突将非常严重,查找效率会大幅度的降低。
总结一下
HashSet不能重复存储equals相同的数据。原因就是equals相同
,数据的散列码也就相同(hashCode必须和equals兼容)。
大量相同的数据将存放在同一个散列单元所指向的链表中,造成严重的散列冲突,
对查找效率是灾难性的。
⑷.
Example:
①.import java.util.*;
class Text{
public static void main(String [] agrs)
{
TreeSet t = new TreeSet();
t.add("l");
t.add("u");
t.add("o");
t.add("p");
Iterator it = t.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
}
这个程序是TreeSet类的实例,TreeSet类具有自动排序的功能,所以程序输出:l o p u,
从小到大。
②.
Examp:
import java.util.*;
class univer
{
public static void main(String [] args)
{
TreeSet al = new TreeSet();
al.add(new Student("john01",22));
al.add(new Student("john02",22));
al.add(new Student("john03",21));
al.add(new Student("john04",20));
Iterator it = al.iterator();
while(it.hasNext())
{
Student p = (Student)it.next();
sop(p.getName()+"......"+p.getAge());
}
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
① class Student implements Comparable
|
{//该接口强制让学生具备比较性,因为学生一个对象,是没有比较性的。
private String name;
private int age;
Student(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
② public int compareTo(Object obj)
|
//覆盖的方法不能异常声明。
{
if(!(obj instanceof Student))
throw new RuntimeException("obj is not student");
Student s= (Student)obj;
if(this.age>s.age)
return 1;
if(this.age==s.age)
③ return this.name.compareTo(s.name);
|
return -1;
}
}
对TreeSet类的一些低端自动调用功能的一些说明:
①. 说先说明,add方法添加进来的new Person对象不具有可比性,如果可以比,那怎么比,不可能比地址吧,所以Student对象不具有可比性。那么我要比较它,就要强制把它转换为可比性。
那么这时就有:public interface Comparable<T>接口提供这个功能。此接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
那么在这个程序中实现接口的类是Student类。所以它强制对这个对象进行排序。通过compareTo方法。
②public int compareTo(Objectobj)
此方法是覆盖了接口中的方法,注意:覆盖的方法不能在方法头申明异常,所以抛出异常只能是RuntimeException类的异常。
②.this.name.compareTo(s.name)
这里这个compareTo方法是String类本身的一个方法,
public finalclass String
extends Object
implements Serializable, Comparable<String>, CharSequence
从上面发现:String类本身就实现了Comparable接口。
故而有:public int compareTo(String anotherString)
③.因为name是String类型的字符串,所以可以【this.name.compareTo(s.name)
】这样来比较他们大小。
按字典顺序比较两个字符串。该比较基于字符串中各个字符的 Unicode 值。将此 String 对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按字典顺序此 String 对象在参数字符串之前,则比较结果为一个负整数。如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。如果这两个字符串相等,则结果为 0;compareTo 只有在方法 equals(Object) 返回 true时才返回 0。
改程序排序结果为:john04......20 john03......21 john01......22
john02......22
------- android培训、java培训、期待与您交流! ----------
详情请查看: