RU beehive logo ITEC dept promo banner
ITEC 120
2008spring
ibarland,
jdymacek

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs

lect08c
equality: == vs equals
some Animals are more equal than others

(m == n)
(someChar == 'a')
(myFavoriteDog == yourFavoriteDog)
But what about:
myFavoriteDog   = new Dog( "arf", 3);
yourFavoriteDog = new Dog( "arf", 3);

(myFavoriteDog == yourFavoriteDog)
Are these two the same dog, or are they two different dogs which each happen to have the same name and age? The latter! Every time you say new, a new object is created, distinct from all other objects. Thus Java is able to handle both situations: when our favorite dogs are the same dog, vs. when then are simply two dogs which look alike but are different.

The meaning of == for object-references: When given two object-references, == determines whether both references are referring to the exact same object. (So, two different objects won't be ==, even if they have the same values in every field.)

Often, when our java objects correspond to physical objects in the world, we have a distinction between two objects which look alike but are different. The mantra “one new, one instance” gets you through that. Sometimes, though, it's not what we want. Consider:

 import java.math.BigInteger;
 BigInteger num1 = new BigInteger("7");
 BigInteger num2 = new BigInteger("7");
 (num1.add(num2)).toString();  // Returns "14", as expected.
 (num1 == num2)    // False?!

 num1.toString()   // "7"
 num2.toString()   // "7"
 // Can we compare BigIntegers by sneakily/hackily comparing their String versions?
 (num1.toString() == num2.toString())   // Nope, still false!
This problem never arose with primitive int, because unlike Dogs, there was no notion of “two sevens which look alike, but are really different”.

Just as the authors of the BigInteger class provided useful methods like add, it turns out they also provide a handy method so that you can compare one BigInteger to another:

num1.equals(num2)    // Aha, finally, true!
This method name equals(·) is actually used in all classes (just as toString() is in all classes):
myFavoriteDog.equals(yourFavoriteDog)  // true.
myFavoriteDog.equals( new Dog("fido", 4) ) // False, even though they look alike?!

  1. Use == to compare primitive types.
  2. (Oh, except for doubles -- don't use ==, but instead check for numbers being with 1% of each other (or, whether they are both very close to zero).
  3. Use == to check if two objects are identically the same object.
    (More precisely: use == to see if two object-references are both referring to the same Object.)
  4. Use equals(·) to check if two objects are conceptually equal, whether or not they are identically equal. For example, two Date objects should be compared using .equals(·), since there might be multiple instances which really do refer to the same date.
    class Date {  /* Gregorian calendar */
      int year;     /* AD */
      String month; /* 3-letter abbrev: e.g. "Mar", "Oct".
      int day;      /* The day-of-month, always in 1..31. */
    
      // ...other methods omitted...
    
      /** @param otherDate Another Date instance, to compare with this one.
       *  @return true if these two Dates look alike.
       */
      boolean equals(Date otherDate) {
        return (   (this.getYear() == otherDate.getYear())          // Same year?
                && (this.getMonth()).equals( otherDate.getMonth() ) // Same month?
                && (this.getDay() == otherDate.getDay()) // Same day-of-month?
                   );
    
        // WARNING: We'll learn later: if we name this method "equals", we should
        // declare it as "public" and its parameter should be of type "Object".
        }
    
      }
    
  5. If you don't write a public boolean equals(Dog) method for your own classes, then Java automatically provides one for you: however, what Java writes for you is a method which simply declares two things equal iff they are ==:
    class Dog {
      // ...other fields and methods omitted...
    
      /** @param otherDog Another Dog instance, to compare with this one.
       *  @return true if these two Dogs are identically the same
       *      (This is the default version of equals which Java provides,
       *       if we don't write our own version for Dogs.)
       */
      boolean equals(Dog otherDog) {
        return (this == otherDog);
    
        // WARNING: We'll learn later: if we name this method "equals", we should
        // declare it as "public" and its parameter should be of type "Object".
        }
      }
    
    In this case, that's the code we really do want. But for class Date, it's definitely not what we want!

Be aware: When comparing Strings, you almost always want to use equals rather than ==. We've already talked about equalsIgnoreCase (an equality-testing function that is unique to Strings).

Optional topic: Notes on defining your own equals:

While these are all obviously desirable traits, be aware that you may (wittingly or not) violate them, if you write your own equals(·). This danger is much more plausible once we've talked about polymorphism and inheritance.

Rule of thumb: if you decide a class warrants its own equals method, then the fields of that class should also be immutable (that is, don't provide any setter methods); once the object is created, its fields will never change. (This is the case with the Java classes String, Integer, Date, etc..)

(Optional) null: a dirty word

null is a dirty word in this class, and you should never use it. … However, you'll see it in other code and in books, so we'll talk about what it does, and also give some strategies on how to avoid it.

First, let's back up, to ints: What does the following code do?

  int zz;
Easy: It declares a variable, named zz, of type int. (If this line is inside a method, it's creating a local variable; if this line is at the top level of a class, then it's creating a field.) Either way, zz is not initialized at this point. If you try to use zz without initializing it, you might get a compiler error “zz may not have been initialized.”. We've already discussed:
The purpose of a constructor is to make sure all the fields are initialized.

Here's a dirty little secret: If you don't initialize a field, Java secretly initializes it for you. The int zz actually starts off with the value 0. But just because Java does that, don't rely on it: If you want a value initialized to zero, then do this yourself. That way, other people reading your code (like the grader!) don't have to stop and wonder whether there might be some bug involving an uninitialized field.

Okay, now that we've seen that talked about int, what happens with objects?

  Treasure tt;
As before, this code declares tt to be a reference-to-Treasure. And like before, Java secretly initializes tt for you. But it can't use 0, because that's not of type reference-to-Treasure. Instead, Java uses null: a reference-to-no-Treasure-at-all.

This is dangerous. Consider:

Treasure someFunction() {
  Treasure tt;
  //...
  tt.getWeight();
  //...
  }
If the first part of code doesn't assign a real reference-to-Treasure to tt, then tt.getWeight() will result in “Null pointer exception”. If you see that error, look for fields (or variables) which were never initialized.

Thorough testing is important:

Treasure someFunction() {
  Treasure tt;
  if ( /* some condition */ )
    tt = new Treasure( /*... */);

  tt.getWeight();

  //...
  }
If your test cases aren't careful, you might think they all pass without any error, but in truth your test cases all happened to match the condition of line 3. In the above example, when the error does occur (line 5), it's easy to figure out. But sometimes null-pointer-exceptions can occur long long after the faulty code is run. Consider:
Treasure someFunction() {
  Treasure tt;
  if ( /* some condition */ )
    tt = new Treasure( /*... */);

  return tt;
  }
Here, the person who called someFunction() could have taken the (possibly-null) result, tucked it away in some field/variable, and only 10hrs later does somebody actually take that result and call its getWeight method. The moral of the story:
Make sure you initialize your fields and variables to non-null values!

If you ever see one of your objects with a field containing null, you probably forgot to initialize that field in the constructor — tsk tsk!

Now, what if people want to represent a reference to nothing at all? For example: Consider Explorers who have two Treasure fields (one for each pocket). Initially, an explorer starts with no treasures. So, you might be inclined to say that those fields should be null. But a better solution — which is required no the homework — is to have an actual Treasure which somehow corresponds to “a nothingness treasure” — in our case, lint.

What difference does it make, if we use null or a nothingness-treasure? There are some pros and cons:


1You'll often see in documentation, comments like “this method must be passed a non-null argument…”.      

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs


©2008, Ian Barland, Radford University
Last modified 2008.Mar.03 (Mon)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme