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:
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.