15-100 Lecture 5 (Monday, January 25, 2008)

Literal Values and Strings

I'd like to observe that all literal values are known before a program ever runs -- they are embedded within the code, itself. As a result, the compiler simply creates them when compiling -- they are born with the program, they are not created at runtime.

So, consider the following:

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

The code above initializes each of s1 and s2 to reference the literal String "Hello World". The way this works is that, before the program runs, Java creates a "Hello World" String object. At runtime, the literal "Hello World" embedded within the program basically acts as a reference to this pre-existing object. So, both s1 and s2 reference exactly the same object.

The == Operator

Some sections tell their students that, "You can't use == to compare Objects." And, this is true -- but as they present it, it makes no sense. You can use the ==-operator to compare string references. The ==-operator works exactly as it does for every other primitive type: It looks into the box and it sees if the two values, in this case references, match.

So, let's take a look at a classic example and see what happens:

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

  if (s1 == s2) 
    System.out.println ("The two references identify the same object.");
  else
    System.out.println ("The two references identify different objects.");
  

To the surprise of some folks, "The two references identify the same object." is printed. The want to believe that "s1" and "s2" are Strings and that the ==-operator is comparing them. But, we debunked that myth last class. They are not String objects -- they are reference variables. And, in this case, they both point to the single "Hello World" object.

Now consider the example below. It forces the creation of a new String object by calling the "new" operator. You can see that a reference to the "Hello World" object is being passed into the constructor. It does exactly what you'd expect -- it creates another object that also models "Hello World". In effect, what we now have, are two "Red, 2008, Honda Accords". We have two different, but equivalent objects.

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

  if (s1 == s2) 
    System.out.println ("The two references identify the same object.");
  else
    System.out.println ("The two references identify different objects.");
  

This time the code prints, "The two references identify different objects.". And, this is, of course, correct -- we created a second object. Remember, we are comparing the refernces, not the objects. Each reference identifies a different object -- it doesn't matter that the two objects happen to be equivalent.

The equals() method

Well, what do we do if we want to know if the objects, themselves, are equivalent? To solve this problem, we can't use the ==-operator. We know that the ==-operator does the same thing for references as it does any other type of primitive variable -- it looks at the value and makes a comparison. It looks at the references, themselves, not the objects that they identify.

As it turns out, in Java, when we write a class specification, we don't start with a blank slate. Instead, we inherit features from a generic class specification called Object. What this means is that, without us having to write them, we get some methods that are part of the Object class's specification.

This includes one method as below:

  public boolean equals (Object o) {
    return (this == o);
  }
  

Notice "this" in the code above. In any method, "this" is a reference to the object that is running the method. So, for example, if we consider "x.equals(y)", within the equals() method, "this" is equal to "x" and "y" is passed in as "o".

So, we see that the equals() method, by default, does exactly the same thing as the ==-operator. And, this isn't surprising. What else can it do in a "one size fits all" way? Comparing objects is difficult stuff. And, it is done differntly for each type of object.

For example, in comapring strings, they are equivalent if each corresponding letter matches. But, in comparing people, they might be considered equal if they have the same number of years of experience and the same degree, or the same height, weight, and game-day stats.

So, by default, the equals() method isn't particularly useful -- it is the same as the more convenient operator. But, the good news is that, in writing our own class specifications, we can override it. If we write a method with exactly the same name and argument list, it will replace the inherited version -- allowing us to compare the objects, themselves, however we'd like. We'll do some of this next class.

But, for now, here's what I'd like you to remember: Use the equals() operator if you want to compare Strings, since they are objects -- only use the ==-operator if you want to compare the references, themselves.

If we rewrite our example form above, it looks as below:

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

  if (s1.equals(s2)) 
    System.out.println ("The two Strings are equivalent.");
  else
    System.out.println ("The two Strings are equivalent.);
  

Hidden Instance Variables

Consider the following example:

  class Scorecard {
    private String playerName;

    public Scorecard (String playerName) {
      playerName = playerName;
    }

    ...
  }
  

Do you see the problem? We are trying to initialize the instance variable "playerName" with the argument "playerName" -- and what we've got makes no sense. Both identifiers are in scope. They both are usable. Does the identifier "playerName" represent the instance variable? Or the argument?

Well, the argument hides the instance variable. "playerName" refers to the argument. So, how do we get to the instance variable by the same name?

You've got the tool in your toolbox. Can you think of it? We just discussed it. Remember the "this" reference? Check out how it can solve the problem by giving us a way to get to the instance variable:

  class Scorecard {
    private String playerName;

    public Scorecard (String playerName) {
      this.playerName = playerName;
    }

    ...
  }