黑马程序员——HashCode与equals

 ------- 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;特别注意thisp所代表的含义:

       这里明确说一下:

    第一步: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方法。john02john01比较,发现内容不同,故把john02存入。

    下面在是john03调用hashCode方法,返回地址3cjohn03分别于john02john01比较,发现内容都不通,

      然后存入,依次类推,发现内容相同时,不存入。

      去除内容相同元素。

    为什么要这样,因为HashSet不允许相同元素(equals==ture)同时存在在结构中。

    假如employeeX(1111张三”)employee(1111,"李四")

    Employee.equals比较的是name。这样的话,

    employeeXemployeeYequals不相等。

    它们会根据相同的散列码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培训、期待与您交流! ----------

 详情请查看:http://edu.csdn.net/heima

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值