CSE143 Notes 3/29/06 - Objects and Classes

Structuring large systems - idea: organize the system as a collection of objects that encapsulate some information and provide a coherent set of operations. Then create objects and ask them to do things.

For each object:

Example:  iPod:    operations (what can it do?)                   data/state (what does it know?)           

 

 

More geeky example: list of words (strings) (e.g., "hello", "wednesday", "green")

operations (what should it be able to do?)                          data/state (what does it know?)

 

 

 

 

 

Two views of an object:

Example: Automobile: What is the client view?

 

 

 

 

How is it implemented?

 

 

Objects in Java

Key point: in Java, we don't define objects by themselves. Instead we define classes that describe the behavior and implementation of a whole category of objects. Then we create individual objects as instances of classes. Each object has its own state (data) and can work independently of other objects.

Example: Using a couple of string lists (pseudocode)

  create an instance of StringList, call it list1
  create another instance of StringList, call it list2
  add "hello" to list1
  add "howdy" to list1
  add "whazzup?" to list2

Turning this into Java code

Create a class:

public class StringList {

  ... operations and state (data)

}

Then create instances of the class and use operations on those instances

public class UseStringList {
  public static void main(String[] args) {
    StringList list1 = new StringList();
    StringList list2 = new StringList();
    list1.add("hello");
    list1.add("howdy");
    list2.add("whazzup?");
  }
}

Defining classes (prototypes for objects)

Both methods and instance variables can be declared to be either public or private

Rules of thumb: Only methods that are part of the client interface should be public. Normally all instance variables and other methods should be private. (Why? Gives us flexibility if we want to make changes to the implementation later; avoids tying client code to details of the implementation when this is not needed.)

Technical detail: In Java, privacy is a property of classes, not specific objects. A method in a class can reference private data or methods in any objects (instances) of that class. This still provides the desired protection - client code cannot access or mess up private data or methods.

Where do you start? Creating a class is like any project - we want to break it down into smaller steps that can be implemented (and tested!) one at a time. Goal: figure out what we can do first without having to do everything, get that far and check it out, then add to it. Here's a useful strategy:

  1. Start by writing down the part of the class that provides the client view of the objects (e.g., public methods). Include comments for concepts and ideas that are part of the design but are not part of the raw code. At this point you should have a properly documented class that does nothing, but compiles cleanly.
  2. Define the basic instance variables needed to represent the state of the object. These should be private
  3. Define a constructor (or constructors). These are special methods that are executed when instances of the class are created. They should ensure that all of the instance variables have sensible initial values and do anything else that needs to be done to get the object properly set up.
  4. Start filling in method bodies to implement the public operations. Add additional private methods and additional (private) state as you need it. Test as you go.

Constructors: These are public methods that have the same name as the class and no return value. They are automatically executed when instances of the class (i.e., objects) are created. They often have parameters to provide initialization values, and there may be more than one constructor if there is more than one way to sensibly create instances of the class.

Comments: Got to have these? Why?

 

 

What to include in the comments:

What not to include in the comments: things that are obvious (e.g., i=i+1; // add 1 to i); comments that just paraphrase the code; irrelevant remarks.

Comments styles. Simple example

  // return the location of s in this list, or -1 if s is not present
  public int indexOf(String s) { ... }

JavaDoc comment for the same public method

  /**
   * Return the location of an item in this list
   * @param s the string to locate
   * @return the index of s in the list, or -1 if not found
   */
  public int indexOf(String s) { ... }

Why would anyone do this? It's definitely more verbose, more typing, etc. The advantage is that Java provides tools that can turn these comments into html pages documenting the class. Rule of thumb: Use JavaDoc comments to define the external (client) view of classes you create; use more succinct comments for everything else.

Some Java details

toString: There is a convention in Java that every class can (and probably should) include a toString method. Specification:

  public String toString() { ... }

The idea is that whenever we need a String representation of an object (like, say, when we print it), then toString is automatically executed to produce the string value that should be printed. Typically this string should describe the kind of the object and the current values in it.

throwing exceptions: what do you do if a precondition is not met? For instance, trying to add data to a list that is already full, trying to retrieve data at an index that is out of bounds. Good answer in many cases: throw an exception. For instance, if an index is out of bounds, you can do this

  if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}

This generates an error and, if not otherwise handled, terminates the program. Some of the standard exceptions are IndexOutOfBoundsException, IllegalArgumentException, NullPointerException, and NoSuchElementException. Use the one that most appropriately describes the problem.

this: In methods we can reference both parameters and local variables of the method, and also instance variables that are declared in the class and belong to the object. Usually this is unambiguous. However, we can specifically indicate that we are referring to an instance variable by prefixing it with the keyword this, meaning "the current object", e.g., this.size. Some people prefer to do this all the time for stylistic reasons; but whether or not you do this consistently, it is useful at times to eliminate ambiguity about whether an instance variable or a local variable/parameter is being referenced.

constants: It is good practice to give names to significant constants, rather than just typing "magic numbers" in the code. These are normally variables that are initialized in their declaration and declared final so they cannot change afterwards. Example:

  public static final int DEFAULT_CAPACITY = 20;

static: The modifier static means something that has only one occurrence in the system, i.e., it belongs to some class itself, not to individual instances of the class. Most things like instance variables are not static, because we want a separate copy for each different object. Generally static is used only for things like named constants and main methods, which are not associated with any particular object. Style note: The Java/C/C++ convention for symbolic constant names is to use all capital letters with underscores to separate parts of words.

Testing objects for equality: Java contains operators == and != that test two things to see if they are "the same". For simple types like integers and characters, these test whether the values of the data are the same. But for objects like strings, the result of these tests are whether they are the "same object", not whether they contain the "same" value. So, for example, if we have two strings defined by

  String s1 = "hello";
String s2 = "hel" + "lo";

it may well be that s1 == s2 is false, because the variables could refer to different string objects, even though those objects contain the same string values. To test whether two string objects contain the same string data, use the equals method: s1.equals(s2). This will return true if the two strings contain the same number of characters in the same order, even if they are actually stored in different objects.

Technicality: In some contexts, Java guarantees that the result of a string operation involving constants, like "hel"+"lo", will produce a result that is the exact same object as the constant "hello", and in this case == will return true when these objects are compared. However, in general you shouldn't count on this and should use equals() to compare two strings for equality.