Monday, August 27, 2012

Differences between equals() and hashCode() methods for String and Object reference

Before moving to the explanation we will check the contract, copied from the Object specification [JavaSE6]:

  • Whenever it is invoked on the same object more than once during an execution
    of an application, the hashCode method must consistently return the
    same integer, provided no information used in equals comparisons on the
    object is modified. This integer need not remain consistent from one execution
    of an application to another execution of the same application. 

  • If two objects are equal according to the equals(Object) method, then calling
    the hashCode method on each of the two objects must produce the same
    integer result.

  • It is not required that if two objects are unequal according to the equals(Object)
    method, then calling the hashCode method on each of the two objects
    must produce distinct integer results. However, the programmer should be
    aware that producing distinct integer results for unequal objects may improve
    the performance of hash tables.

First we consider the String object. 

Assume I create two String instances (without using new keyword) like as bellow,

String s1 = "Hello World";
String s2 = "Hello World";


When we print s1 == s2 it should print true. Because these two String references refer the same location.
Likewise s1.equals(s2) is also returns true.

Note:  When we create the two different String instance(without new keyword) with the same value, both references refer the same location in memory.

Now we create two different String objects(with new keyword) but the contents are same.

String s1 = new String("Hello World");
Strung s2 = new String("Hello World");

When we print s1.equals(s2) it should print true because the the String equals methods checks the content of the String instance. If the content is true then the equals methods returns true.

According to the java contract if equals objects returns true the the hashCode also should returns true(this is not applicable for all scenarios). So s1.equals(s2) returns true.

s1 == s2 print false as these are two different objects.

Now we move to the real word objects

Assume we already have the Student class with two properties studentId, name, and age.

public class Student {

    private int studentId;
    private String name;
    private int age;

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


Now I am creating two instances for student class.

Student student1 = new Student();
student1.setStudentId(11111);
student1.setName("K.Senthuran");
student1.setAge(29);

Student student2 = new Student();
student2.setStudentId(11111);
student2.setName("K.Senthuran");
student2.setAge(29);


We will discuss what is the output for following print statements.

1. student1 == student2
With out any confusion we can tell the output should be false as these student1 and student2 are two different objects.

2. student1.equals(student2)
This prints false because when this statement executes, it calls the Object equals() method. The current implementation for the object equals() method is as following,

public boolean equals(Object obj) {
   return (this == obj)
}

So this should print false.

3. student1.hashCode() == student2.hashCode()
This returns false because the two different objects have different hashcodes.

Now we override the equals method to return true for the objects which has same contents.

@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 (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (studentId != other.studentId)
            return false;
        return true;
    }


Now if we print student1.equals(student2) it should print true (According to the above equals() method implementation).


According to the contract if two objects are equal according to the equals(Object) method, then calling
the hashCode method on each of the two objects must produce the same integer result. But when we print the hash code for these two objects those return different value.

So we break the contract by overriding the equals() method. Check the bellow example.

For example, consider the following simplistic PhoneNumber class, whose equals method is constructed:

public final class PhoneNumber {
   private final short areaCode;
   private final short prefix;
   private final short lineNumber;
   public PhoneNumber(int areaCode, int prefix, int lineNumber) {
   rangeCheck(areaCode, 999, "area code");
   rangeCheck(prefix, 999, "prefix");
   rangeCheck(lineNumber, 9999, "line numberthis.areaCode = (short) areaCode;
   this.prefix = (short) prefix;
   this.lineNumber = (short) lineNumber;
}


private static void rangeCheck(int arg, int max, String name) {


   if (arg < 0 || arg > max)
      throw new IllegalArgumentException(name +": " + arg);
}


@Override public boolean equals(Object o) {
   if (o == this)
      return true;
   if (!(o instanceof PhoneNumber))
      return false;
   PhoneNumber pn = (PhoneNumber)o;
   return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
}
// Broken - no hashCode method!
... // Remainder omitted
}

Suppose you attempt to use this class with a HashMap:
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");


At this point, you might expect m.get(new PhoneNumber(707, 867, 5309)) to return "Jenny", but it returns null. Notice that two PhoneNumber instances are involved: one is used for insertion into the HashMap, and a second, equal, instance is used for (attempted) retrieval. The PhoneNumber class’s failure to override
hashCode causes the two equal instances to have unequal hash codes, in violation of the hashCode contract. Therefore the get method is likely to look for the phone number in a different hash bucket from the one in which it was stored by the put method. Even if the two instances happen to hash to the same bucket, the get
method will almost certainly return null, as HashMap has an optimization that caches the hash code associated with each entry and doesn’t bother checking for object equality if the hash codes don’t match. Fixing this problem is as simple as providing a proper hashCode method for the PhoneNumber class as bellow.

@Override
    public int hashCode() {
        int result = hashCode;
        if (result == 0) {
            result = 17;
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            hashCode = result;
        }
        return result;
    }

Now we can access the value "Jenny" by using the key new PhoneNumber(707, 867, 5309).


Likewise if we want to avoid the contract problem mentioned in Student class we need to override the hashcode() method properly as bellow,

@Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + studentId;
        return result;
    }


Conclusion: If we override the equals() method we should override the hashCode() method also.





Saturday, March 10, 2012

Difference between Absolute and Relative Paths

Absolute Path

Absolute paths are called that because they refer to the very specific location, including the domain name. The absolute path to a Web element is also often referred to as the URL. For example, the absolute path to this Web page is:
http://webdesign.about.com/library/weekly/aa040502a.htm

You typically use the absolute path with the domain to point to Web elements that are on another domain than your own. For example, if I want to link to the Graphic Design Guide's site - I need to include the domain in the URL: http://graphicdesign.about.com/. So a link to her glossary entry would look like this:
<a href="http://graphicdesign.about.com/library/glossaries/web/blabsoluteurl.htm"> ...</a>

If you're referring to a Web element that is on the same domain that you're on, you don't need to use the domain name in the path of your link. Simply leave off the domain, but be sure to include the first slash (/) after the domain name.

For example, the Beginner's Resource Center has the URL: http://webdesign.about.com/library/beginning/bl_begin.htm If I were to link to this URL from another page on my site, I could link to it in this way:
<a href="/library/beginning/bl_begin.htm">...</a>

It is a good idea to use absolute paths, without the domain name, on most Web sites. This format insures that the link or image will be usable no matter where you place the page. This may seem like a silly reason to use longer links, but if you share code across multiple pages and directories on your site, using absolute paths will speed up your maintenance.


Relative Path

Relative paths change depending upon what page the links are located on. There are several rules to creating a link using the relative path:

  • links in the same directory as the page have no path information listed
    filename
  • sub-directories are listed without any preceding slashes
    weekly/filename
  • links up one directory are listed as
    ../filename

How to determine the relative path:

  1. Determine the location of the page you are editing.
    This article is located in the/library/weekly folder on my site.
  2. Determine the location of the page or image you want to link to.
    The Beginner's Resource Center is located here: /library/beginning/
  3. Compare the locations and to decide how to point to it
    From this article, I would need to step up one directory (to/library) and then go back down to the beginning directory
  4. Write the link using the rules listed above:
    &lt;a href="../beginning/bl_begin.htm"> ... </a>