Michael Bolin's 6.170 Recitation

This web site is for members of the recitation that I am teaching for 6.170 Spring 2004. The recitation meets Thursday mornings from 10-11am in 26-210. Here you will find recitation notes as well as links to software development tools that I consider important.
Recitation Notes
2/5 Recitation 1
2/12 Recitation 2
2/19 Recitation 3
2/26 Recitation 4
3/4 Recitation 5
Comments on Problem Sets
Problem Set 0
Problem Set 1
Problem Set 3
Things I Recommend
Software You Should Install
Books You Should Own
Web Sites You Should Use

2/5 Recitation 1

Today's goal was to introduce you to Java. As it was difficult to cover everything that you need to know to get started with Java in 50 minutes, I recommed reading Sun's Java Tutorial if you want a more comprehensive introduction to Java. Also, if you have questions, please feel free to ask them over email, zephyr, or at my office hours.

These notes contain a summary of what we covered in class, as well as a few things that we didn't get to cover.

  1. Primitives vs. Objects
  2. Casting
  3. equals() and hashCode(): Best Friends Forever
  4. Dangers of Mutable Objects
  5. Comments on Commenting
  6. Tips for Problem Set 1

1. Primitives vs. Objects

Java is not a purely object-oriented (OO) language because it has primitives in addition to objects. The motivation behind having primitives is to improve performance, but the tradeoff is simplicity/consistency. For example, to get the representation of an object as a String, you can invoke the object's toString() method. However, to get the string representation of a primitive, you have to call the static method of the String class called valueOf. If you look at the String API, you'll notice that the valueOf method is overloaded with 9 different parameters! It is tedious for developers to have to make special cases for each type of primitive instead of using one method that applies to all objects in a hierarchy.

Java has 8 primitives:
Type
Contains
Default
Size
Range

boolean

true or false false At least 1 bit? either true or false

char

Unicode character
unsigned
\u0000 16 bits or
2 bytes
0 to 216-1 or
\u0000 to \uFFFF

byte

Signed integer 0 8 bit or
1 byte
-27 to 27-1 or
-128 to 127

short

Signed integer 0 16 bit or
2 bytes
-215 to 215-1 or
-32768 to 32767

int

Signed integer 0 32 bit or
4 bytes
-231 to 231-1 or
-2147483648 to 2147483647

long

Signed integer 0 64 bit or
8 bytes
-263 to 263-1 or
-9223372036854775808 to
9223372036854775807

float

IEEE 754 floating point
single-precision
0.0f 32 bit or
4 bytes
11.4E-45 to
13.4028235E+38

double

IEEE 754 floating point
double-precision
0.0 64 bit or
8 bytes
1439E-324 to
11.7976931348623157E+308

Since even a null object in Java takes up 20 bytes, primitives take up a relatively small amount of memory and are more convenient for doing quick mathematical operations. You'll notice that primitive types start with a lowercase letter (demonstrating their diminutive status) and that classes start with an uppercase letter. Classes are more interesting because they have methods (functions) that can be invoked (called) to do complex procedures.

As primitives do not have their own methods, they must be compared using ==. The equals method can only be used, and applied by, objects. Hence, the following snippet of code will NOT compile because equals can only be invoked by an object and must take an object as a parameter:

    int a = 5;
    int b = 5;
    System.out.println(a.equals(b)); // compilation ERROR!
If you really wanted; however, you could do the following:
    int a = 5;
    int b = 5;
    System.out.println((new Integer(a)).equals((new Integer(b))));
But that would be ridiculous as you could have just done:
    int a = 5;
    int b = 5;
    System.out.println( (a == b) );
The point is that because primitives are a fixed number of bytes, Java determines their equality by doing a bitwise comparision of the two values. Thus, == is basically reserved for bitwise comparison which is what is going on when == is used to compare the equality of two objects. For example, when you have a class Simple defined as follows:
public class Simple {
  private String a, b; // private fields
  public Simple() { // constructor
    a = "Hello, World";
    b = "Hello, World";
  }
}
then Java knows that whenever an instance of Simple is created by invoking the new keyword, eight bytes of space will have to be allocated for the two 32-bit pointers to a and b. Each pointer will be an address in memory where the value of the characters of a and b will be stored (when these eight bytes of space are created, they will be initialized to zero, representing a null reference, until the addresses for a and b are assigned to them). Thus, when a == b is called here, the two 32-bit pointers are compared for equality. In this case, the equality of the pointers, or the references, is being compared as opposed to the values to which a and b point. As this is a simple integer comparision, the == test is much faster than the equals() test for equality; however, it is rarely the type of test for String equality that you, the programmer, want. You are most likely interested in seeing if the two Strings have the same characters, in which case you must use equals to compare the two.

Forgetting to use equals instead of == for object comparision is a subtle bug, so watch out for it! What equals means depends on the type of the object. For two Strings to be equal, they must have the same sequence of characters. For two Points to be equal, they must have the same x- and y-coordinates. Though what equality means for two objects may vary, the specification for the equals method is explained in detail in the API for Object. You should become familiar with this specification, as failing to implement equals correctly may also result in subtle bugs. See the section on equals and hashCode below for a more detailed explanation.

2. Casting

Objects have a hierarchy in Java where java.lang.Object is at the top of the hierarchy. To see what classes extend a particular class, look up the class in the Java API and see what's listed (if anything) under All Known Implementing Classes. From this documentation page, you can also see what classes the class extends. For example, if you look at the API for java.util.ArrayList, you can see that it extends AbstractList which extends AbstractCollection which extends Object. An object in the hierarchy may always be referred to as an object higher than it in the hierarchy. For example, a method that takes an AbstractList may also take an ArrayList because ArrayList extends AbstractList and is therefore may be referred to as an ArrayList.

However, sometimes you may want to refer to an object as something deeper in the hierarchy. This is called casting. For example, the equals method of Integer takes an Object as a parameter, but the Integer class really wants to compare itself to other Integer objects by seeing if it has the same value as another Integer. This may be done through a cast as shown in the following example:

  Object a = new Integer(5);
  Integer b = new Integer(5);
  Integer c = (Integer) a; // a is cast as an Integer
  if (b.intValue() == c.intValue()) {
    System.out.println("they are equal!");
  } else {
    System.out.println("Bah! they are NOT equal");
  }
The syntax for doing a cast is to put the name of the class to which you would like to cast your object in parenthesis in front of the reference to the object that you would like to cast, as shown in the example above. You could not call a.intValue() even though a has an intValue method buried in it somewhere because the compiler only "sees" a as an Object.

But what if we tried to cast an object into something that it isn't? If you try to do this, your code will compile; however, when you run it, it will be met with a ClassCastException. To protect against such an Exception, you can do a test using instanceof before you cast.

  Object obj = new Object();
  Integer i = new Integer();
  if (!(obj instanceof Integer)) {
    System.out.println("that's not an Integer!");
  } else {
    Integer j = (Integer) obj;
    String message = null;
    if (i.intValue() == j.intValue()) {
      message = "same";
    } else {
      message = "different";
    }
    System.out.println(msg);
  }
Now this code will only perform the cast once it's sure that the object is an Integer. This seems silly in this case since we can see from the code that obj is not an Integer, but in other cases, like when the object is passed into the equals method (see next section), then such a test may be more appropriate.

Although primitives do not fit into the object hierarchy, they can also take advantage of casting, but in a slightly different sense, as casting a primitive may change its value. For example, a 64-bit long may be cast into an int. When this happens, the upper 32 bits of the long are dropped which may result in a loss of precision.

Also, as an ArrayList may be used as an AbstractList, an int may be used as a long without any special syntax. There is no loss of precision in this case because a long has more bits of precision than an int. This is often called upcasting whereas the opposite is called downcasting.

3. equals() and hashCode(): Best Friends Forever

Both the equals(Object obj) and the hashCode() method are defined by Object, so every Java object inherits these two methods. According to the specification for hashCode():

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java 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(java.lang.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 hashtables.
Therefore, the rule of thumb is:

If you override equals, then you must override hashCode.

Why is this so important? Because failure to meet this specification can introduce insidious bugs. For example, when you do a put into a Hashtable, the Hashtable takes the hashCode() of the key and puts the value into a "bucket" with all keys in the same range. Thus, when you use get(Object key) to extract a value from a Hashtable, the table takes the hashCode of the key and then looks through the bucket to which the key maps for another key with (i) an identical hashCode and (ii) whose equals method returns true when applied to the provided key. Thus, if the hashCode for the key changes over the course of execution of the program, a call to get will not be able to find the desired Object because the Hashtable will look in the wrong bucket. Similarly, if the hashCode method is not updated so that it returns the same value for two objects that are the same according to the equals method, then a call to get() will fail to return the desired value because it will not recognize the key as it fails condition (i) stated above. For a concrete example with fewer run-on sentences, see Item 8 in Effective Java.

So how should equals() and hashCode() be written? Suppose you were writing the equals method for class Foo. The following would be a good skeletal implementation:

  public boolean equals(Object obj) {
    if (obj == null || (!(obj instanceof Foo))) return false;
    // the code if (!(obj instanceof Foo))
    // will also return false if obj is null,
    // so the (obj == null) test may be omitted
    // it is shown here for clarity

    Foo otherFoo = (Foo)obj;

    // return true or false depending on how the fields
    // of this compare to those of otherFoo
    //
    // example of determining equality of two Foo objects based on their names:
    //
    // return this.getName().equals(otherFoo.getName());
  }
Notice how this correctly returns false if the object that is passed in is null or is not of type Foo. Then casting immediately lets you call the methods of otherFoo so that it can easily be compared to the values in this. Writing a good hashCode method is not as straightforward. Once again, refer to Item 8 of Effective Java for information on how to write a good hashCode method.

4. Dangers of Mutable Objects

There is nothing bad about mutable objects, but you should be aware of the types of dirty tricks people can play (or the types of bugs you can encounter) by allowing objects to be mutated. For example, if you have a Transaction class and you want its constructor to take a java.util.Calendar to represent the time at which the transaction occurred and an int to represent the number of dollars transferred, you could do the following:
import java.util.Calendar;

public class Transaction {

  /** time when transaction was made */
  private Calendar time;

  /** amount of the transaction in dollars (may be positive or negative) */
  private int amount;

  /** @requires time != null */
  public Transaction(Calendar time, int amount) {
    if (time == null) throw new IllegalArgumentException("time is null");
    this.time = time;
    this.amount = amount;
  }

}
But suppose you had a BankAccount class that other people could access through a method called submitTransaction(Transaction trans). Then someone could change the date after the Transaction was submitted as follows:
  BankAccount bank = new BankAccount();
  Calendar rightNow = Calendar.getInstance();
  int amount = 100;
  Transaction trans = new Transaction(rightNow, amount);
  bank.submitTransaction(trans);
  rightNow.set(Calendar.YEAR, 1985);
This works because the person who created trans still has a reference to the Calendar stored inside of it. So how could Transaction be fixed to prevent this from happening? It could be made immutable by doing the following:
import java.util.Calendar;
import java.util.GregorianCalendar;

public class Transaction {

  /** time when transaction was made */
  private long timeInMillis;

  /** amount of the transaction in dollars (may be positive or negative) */
  private int amount;

  /** @requires time != null */
  public Transaction(Calendar time, int amount) {
    if (time == null) throw new IllegalArgumentException("time is null");
    this.time = time.getTimeInMillis();
    this.amount = amount;
  }

  /**
   * @return a Calendar representing the time when
   * this transaction was made
   */
  public Calendar getTime() {
    return (new GregorianCalendar()).setTimeInMillis(timeInMillis);
  } 
}
Because the time is being stored as an immutable long, this class is now immutable and is safe from the "attack" from the above example. In fact, this version of Transaction also shows how to have a "safe" accessor for the time field that does not expose that the time is internally being stored as a long.

5. Comments on Commenting

In Java, /* and */ can be used to block off large chunks of code whereas // is used to comment out everything from the // to the end of the line. In practice, /* and */ should be reserved for Javadoc comments, in which case the opening tag should have an additional asterisk: /**. If you need to highlight a large block of code, highlight the region and use C-/ in Eclipse. (M-x comment-region works in Emacs.) This is better because block comments do not nest. For example, if you already did:
  String a = "Athena ";
  String b = "bites";
  /* String b = "brings me happiness"; */
  String c = "closes? Nope. Never.";
  String d = "doesn't have anywhere to sleep comfortably.";
But then you wanted to comment out the creation of variables a and c using a block comment:
  String a = "Athena ";
  /* String b = "bites";
  /* String b = "brings me happiness"; */
  String c = "closes? Nope. Never.";
  */
  String d = "doesn't have anywhere to sleep comfortably.";
The two block comment characters that have been added are in red. Notice that this failed to comment out the statement where c is created. Also, this code will no longer compile because there is a */ dangling by itself after the definition of c. This may seem easy to fix now, but if you have commented a large block of code, it may be a pain to find the nested block comment that is causing the compilation error. You can avoid this mess entirely by using the // comment:
  String a = "Athena ";
  // String b = "bites";
  // // String b = "brings me happiness";
  // String c = "closes? Nope. Never.";
  String d = "doesn't have anywhere to sleep comfortably.";
This also makes it easier to uncomment smaller blocks of commented regions. Use C-\ to uncomment code in Eclipse (or M-x uncomment-region in Emacs). Granted, this may be inconvenient for those who are using a plain text editor such as Notepad to edit the code, but those people really aren't serious about writing code then, now are they?

Eclipse Tip: If you want to leave yourself a note about a piece of code that you need to fix, preface the comment with TODO. You will notice that TODO will appear in bold and that if you do Window > Show View > Tasks, then a a "Tasks" pane will come up with all of the things you have left yourself TODO. You can jump to these points in your code quickly by double-clicking on them in the "Tasks" pane.

6. Tips for Problem Set 1

  1. If JUnit is new to you, then check out the JUnit resources listed on the 6.170 web site. You will be using JUnit all semester (as well as in industry, if you develop Java software), so you should start to become comfortable with using it.
  2. For the methods that you have to implement, read all of the comments carefully. Not just those around the method that you are trying to implement, but throughout the whole class. When I tested this problem set, I implemented a method (or two ;-) incorrectly because I assumed that I knew what the method was supposed to do just by reading its name. I could have saved a lot of time if I had read more thoroughly.
  3. If the Java Collections Framework (basically everything in the java.util package) is new to you, then you should peruse (which means read carefully) Sun's Overview of the Collections Framework, especially the Tutorial by Joshua Bloch (who is also the author of one of the recommended texts for this class, Effective Java). Every Java programmer relies heavily on the Collections Framework, so it is extremely important that you learn how to take advantage of this library. If you are already familiar with this library, then Problem Set 1 should not take you very long (or at least it should not require you to write much code).


©2004 Michael Bolin