Little PDF
Little PDF
Little PDF
Contents
1 Introduction 3
3 Installing Java 4
4 Java Documentation 4
5 Java Basics 4
5.1 A Simple Java Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.2 Names in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
5.3 Variables in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
5.4 Basic Java Datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
5.4.1 Numeric Datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
5.4.2 The Character Datatype . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.4.3 The Boolean Datatype . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.4.4 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.5 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.6 Expressions and Statements in Java . . . . . . . . . . . . . . . . . . . . . . . 9
5.6.1 Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.7 Basic Java Control Constructs . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.7.1 Looping Mechanisms in Java . . . . . . . . . . . . . . . . . . . . . . . 11
5.7.2 Conditional Statements . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5.8 Conclusion to the Basic Part . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6 Abstraction Mechanisms 13
6.1 Procedures, Parameters and Types . . . . . . . . . . . . . . . . . . . . . . . 15
1
7 Classes and Static Methods 16
7.1 Overview of Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
7.1.1 Syntax of Class Declarations . . . . . . . . . . . . . . . . . . . . . . . 16
7.1.2 Class Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
7.2 Static Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
7.2.1 The main Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
7.2.2 Method Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
7.3 The Dot Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
8 Visibility Issues 22
12 Inheritance 38
16 Conclusions 55
2
1 Introduction
These notes are a brief introduction to Java and to some basic object-oriented concepts. They
should not be viewed as a comprehensive textbook but they should be more than adequate
for learning Java in conjunction with the lectures and assignments. These notes were written
specifically to cover those aspects of Java that arose in CS250 during the Fall of 1998. The
emphasis in the course is algorithms and not programming languages. To properly appreciate
the subtleties of the object-oriented paradigm, one needs to take a course on programming
languages, such as CS302.
Java is a relatively new, but very popular object-oriented programming language. It
builds on the syntax of C and is closely related to C++, but there are key differences. I
assume that you know the elements of programming in some language such as Pascal or C
(it does not really matter what) so the basic aspects of programming are not talked about
at all; we will just give the basic Java syntax and semantics.
These notes will not tell you everything about Java. They will, however, tell you where
to find out more. In particular, we will tell you how to find out information about Java from
the official Java documentation available free on the internet.
There are three aspects of Java that one might cover:
• basic concepts,
It is our goal to cover the first topic. You will not be very knowledgeable about the other
two topics from this course alone. However, you will be able to learn those things easily in
the context of a job or project – if you need to – later on. These are pricisely the things that
you can teach yourself whereas learning the concepts is not easy without help.
3
3 Installing Java
The official Java website can be found at http://www.javasoft.com. Among its many
features, this site provides free downloads for Java and complete Java documentation. The
latest version of Java is called “Java 2 SDK v1.3.1”. The main homepage for this distribution
is:
http://java.sun.com/j2se/1.3/
http://java.sun.com/j2se/1.3/docs/index.html
In order to insure proper installation, do read the file that describes all steps that need
to be taken for successful installation. In particular, pay attention to the section pertaining
to setting classpaths. To be able to compile and run Java code from any directory, the
classpaths on your system must be set properly.
4 Java Documentation
Complete documentation for Java SDK v1.3.1 can be found at
java.sun.com/j2se/1.3/docs/
This page lists and provides a description of all packages supported by Java SDK v1.3.1.
These packages contain predefined Java classes (you will learn about classes later). The page
also lists all Java classes in alphabetical order and provides a link to the definition for each
class (i.e. the variables, constructors and methods provided by the class - do not worry if
you do not know as yet what these words mean). An appropriate time to browse through
the Java docs would be after you have learned about classes and objects.
You can look up all sorts of information including books at:
http://developer.java.sun.com/developer/infodocs/
For starters, get familiar with looking up information on Java on the web by finding the Java
Language specification. You can also practice by looking up information on Java operators
at
http://web2.java.sun.com/docs/books/tutorial/java/nutsandbolts/operators.html
5 Java Basics
In this section we review basic Java ideas. These are things you should know from a previous
course in another language, so the discussion will be brief.
4
5.1 A Simple Java Program
A Java program is stored in a .java file. Let us create a simple Java program that you can
compile and run. At this point, we do not expect you to understand the program’s syntax.
Type the following Java program using a text editor such as “NotePad”1 or preferably
“emacs” or “vi” - basically any editor that produces plain text output2 .
class First{
public static void main(String args[]){
System.out.println(‘‘This is my first Java program!!!’’);
}
}
Make sure that you typed the code exactly as above. Note that Java is case sensitive and
the code that you typed will contain errors if you did not preserve the case of the words.
Save the program in a file called First.java. Note that this program can be saved in a file
with any name as long as it has a .java extension.
5
Comments in Java
Comments can be written in two ways. Any text on the same line after two consecutive
backslashes (//) is a comment. Any text enclosed within /* and */ is a comment. Comments
are not nested, meaning that the first /* will end at the next */. The // is best used for
short comments that come on the same line and the other notation can be used for longer
comments.
This is a good place to say something about how to comment. Comments should be
there to explain what the program is doing. Every program should have comments at the
beginning that say briefly what the program does and what the general methods used are.
It might be appropriate to say what kinds of assumptions are made - for example the nature
or format of the expected input. Each method (procedure) should have comments specific
to that procedure. Anything tricky should be explained but obvious things should not be
belaboured. Here are examples of good comments and bad comments.
/* The following code allows one to swap two values without using a
temporary variable. */
x = y - x;
y = y - x;
x = x + y;
One very good use of comments is to indicate the end of methods or classes.
6
way of referring to a memory cell - this is what is usually called a variable - or be the name
of a method (procedure).
In Java, declarations can be introduced in the middle of the text. In other languages,
one has to make declarations at the beginning of the program or at the beginning of a block.
We will see examples of names and declarations below.
http://java.sun.com/docs/books/tutorial/java/nutsandbolts/vars.html
7
5.4.2 The Character Datatype
In Java, the char datatype denotes character values. Single quotes are used to specify
characters.
5.4.4 Strings
A string is a sequence of characters and is denoted by the String5 datatype. In Java, double
quotes are used to specify a string, thus distinguishing a string from a character.
5.5 Arrays
It is often useful to cluster data of the same type in a collection. An array is a set of memory
locations containing data of the same datatype. The most important aspect of arrays is that
the elements of the array are accedded by numerical indices. Furthermore these indices can
be computed.
An array can contain data of any Java datatype. Like strings, arrays are Java objects.
In Java, an array of size n has indices from 0 to n − 1. Recall that we expect you to be
already familiar with arrays in Pascal or C (or Fortran or Basic).
When declaring an array, one must specify the size of the array and the datatype of its
elements. The general declaration of an array is as follows:
5
Note that the first letter is capitalized.
8
datatype[] ArrayName = new datatype[ArraySize];
Note the use of the new keyword in the above declaration. We will discuss the importance
of this keyword when we discuss objects. Briefly, the new actually creates space for the array
while int[] numbers just says that the name “numbers” will be used for an array of integers
(of unspecified size).
After an array has been created, its elements must be initialized. So far we have named
the array and reserved space for it but we have not put any data in the array. Each element
of an array has an index, with the first element having the index 0 and the last element
having an index one less than the size of the array. Using an index, one can modify or obtain
the array element at that index. This is the key aspect of arrays. By using expressions for
the indices one can have names that are computed at run-time.
An error occurs if one tries to access an array index that does not exist, i.e. an index
that is less than zero, or greater than or equal to the size of the array. This error is a Java
exception called ArrayIndexOutOfBoundsException. We will discuss exceptions in a little
more detail in section 15.
x = 2 + 3;
9
1. Compute the expression 2 + 3.
2. Store the result of the expression evaluation in the memory cell named by x.
The statement of the previous paragraph is an example of the assignment statement.
You must have seen it many times before. However it is worth being absolutely clear about
several things now. A general assignment statement looks like this:
<name> = <expression>
The name on the left-hand side has to be the name of a memory cell. The expression on
the right hand side has to produce a value. The type declared for the cell and the type
of the expression must match. Later we will see that the “name” can be the result of a
computation. Thus the most general form is:
<expression-which-produces-a-name> = <expression-which-produces-a-value>
We will see examples of this more general form later on. For the first few weeks you will not
encounter it.
Things get more interesting when the same name occurs on both sides of the expression
as in:
x = x + 2;
Or as C hackers like to write it:
x += 2;
Now the interpretation of the name is different on the two sides of the assignment operator
(i.e. the = sign). In order to appreciate this properly, note that the name x is the name of a
memory cell (here a cell for storing integers). This cell contains a value. The name x is used
both to refer to the cell and to its contents. In the example at hand, on the left hand side
the name x names the cell and on the right hand side it names the value currently stored in
the cell.
Certain notations are both expressions and statements. A typical example is the construct
x + +. This means “increase the current value of x by 1”; but it is also a statement and
it returns the old value of x. The construct + + x means - as a statement - “increase the
current value of x by 1” (just the same as x + +) but as an expression it returns the new
value of x. Thus if x is 1729 and we write y = x++ we will get 1729 stored in y but x will
now be 1730. If we were to write y = ++x then both x and y would be 1730.
5.6.1 Blocks
In order to write interesting programs, we need mechanisms to put together complex state-
ments from simple ones. One of the basic forms is the block. A block contains zero or more
statements enclosed in parenthesis. A block is a compound statement and can always replace
a single statement. We will see examples of blocks below.
10
5.7 Basic Java Control Constructs
Java provides looping mechanisms and conditional statements that allow us to alter the
control flow of execution. In this section, we will discuss the syntax of some basic loops and
conditional statements. Recall that we expect you to be familiar with the concept of loops
and conditional statements. The notation may change from one language to another but the
ideas are the same.
while <boolean-expression>
statement
As long as the boolean expression specified in the header of the while loop evaluates to
true, the statement is executed. The while loop exits when the boolean expression is false.
If the boolean expression is false the first time it is evaluated, then the statement is never
executed. Note that the statement that the while loop repeatedly executes can also be a
block.
do
statement
while (boolean-expression);
The do-while loop is equivalent to the while loop except that the statement is executed
at least once. The boolean-expression is evaluated after the statement is executed. The loop
body is executed as long as the boolean expression remains true.
The for Loop The most general loop form is the for loop. The syntax of a for loop is
as follows:
11
initial-expression;
while (boolean-expression){
statement
increment-expression;
}
A for loop iterates over a range of values given to a variable. The variable must have an
initial value, which is specified by initial-expression. The value of the variable is modified
in some way; usually it is incremented as specified by increment-expression. One can also
decrement the value. The boolean-expression tests whether the value of the variable is within
the range that specifies the number of iterations for the for loop.
if (boolean-expression)
statement
if (boolean-expression)
statement
else if (boolean-expression)
statement
else
statement
12
Note that the if statement did not have an else part in this example.
Java provides a special statement called break which can be used to exit a block of code.
The syntax of the break statement is as follows:
break;
One can use the break statement to exit loops early. Here is an example of a loop that
searches an array for a given number and stops the search as soon as it finds the number.
int i = 0;
else i++;
}
6 Abstraction Mechanisms
A vital part of an advanced programming language like Java is abstraction. By abstraction we
mean packaging code and data into organized units in such a way that the details are hidden.
There is a tendency for beginning programmers (and writers of beginning textbooks) to
emphasize control constructs and data manipulation and spend less time on overall program
13
organization. This might be reasonable for small programs, but as programs get larger it
is imperative to think about the overall program structure. In this class we will be moving
towards medium sized programs and the principles of program organization will be more
important than mastery of all the control constructs.
Programs are the most complex mechanisms built by humans. There is no hope for any
one person to keep the details of a large program in mind. The only viable strategy is for
the program to be broken into small manageable pieces. These pieces will interact with
each other and this interaction has to be controlled in a structured way. Thus, when we
decompose a program into pieces we have to think at two levels. We have to understand a
piece of code ignoring all the interactions with the rest of the program and then, we have
to understand the interactions between the pieces without thinking about the details of the
code inside each piece. I emphasize this because this is the part where many beginners have
trouble. They tend to be obsessed with understanding each piece of code at the lowest level
of detail and cannot ignore details even when it is appropriate to do so.
The basic abstraction mechanisms are procedural abstraction and data abstraction. In
procedural abstraction, a piece of code - called a procedure - is packaged together and given
a name. Thereafter, this code can be run or invoked simply by using this name. This is done
not to save typing (as many students seem to think) but to enforce abstraction levels. When
the procedure is defined you do not worry about all the different places in the program where
it might be used, when the procedure is used, you do not think about how it was coded.
In data abstraction, some collection of data is organized into a structured form (a data
structure) and some code is provided to manipulate the data while maintaining the integrity
of the structure. The abstraction consists of hiding the details of the data structure. The
point of this is to force you to use the code provided rather than to mess with the structure
directly. This way you are less likely to violate the integrity of the structure. Just as in
procedural abstraction, hiding is the key. We will see examples of data abstraction and
procedural abstraction in Java in the next two sections.
The correct way to think of abstractions is as a contract. When you write a procedure
you have in mind that a solution to a certain well-defined task is to be implemented. You
do not think about what a user might do with the procedure. Abstractions like this occur
in everyday life. Perhaps one of the best examples of abstraction is in a car. The driver of
a car is usually not an expert engineer. In fact, relatively uneducated people can drive cars
well, despite the fact that there is a significant amount of sophisticated engineering involved.
Furthermore when one operates a car by using the controls, one has to be aware that some
basic contracts are fulfilled. Thus turning the wheel causes the car to turn and pressing the
brake slows down the car. One need not have the faintest idea of how this is accomplished. In
fact in an emergency, it is important to not waste your time thinking about auto mechanics,
you have to spend your mental effort dealing with the emergency.
The brake and the other controls are like procedures. They fulfill a certain contract but
you need now know how this is done. The designer of the car certainly has to know how this
is done. But she does not have to think about what the driver might do with the car. She
does not think about the fact that you are going to use the car to commute to work, or go on
14
a date or go to the store. The separation of concerns is complete, as the only link between
the two levels is the control panel of the car. This is the interface between the low-level
engineering and the higher-level human concerns of the person using the car.
We will be using all these concepts in programming. We will define procedures - called
methods in object-oriented programming - and data abstractions - using classes. We will
define interfaces and we will use these concepts to structure a program into manageable
pieces. A modern object-oriented programming language like Java supports these ideas
directly.
• its name,
The information in the latter two items is called the type of the procedure. In a typed
language like Java, these items have to be explicitly specified.
A simple example of these ideas is a procedure for performing multiplication. To use it
you have to know its name, say times. You have to know that it expects two integers and
that the result is an integer. Thus we would say that the type of times is
In programming languages we do not use this explicit notation with the arrow but we es-
sentially say the same thing. Notice that to use the procedure times, we need not have any
idea how this was coded. In fact, we often use mathematical libraries knowing the names
and types of procedures without knowing how they were coded.
You might think that we have left out something obvious, namely we need to know what
the procedure does even if we do not know how it does it. This is very true but the language
provides no way of ensuring that a procedure does what you think it does. Thus if you want
15
to perversely to call the multiplication procedure add or foobar, nothing in the language
stops you. This part of the contract is informal. This is one reason why we need comments
that document what a procedure does.
class Hello{
/* The body of this class contains nothing right now */
}
The name of a class must begin with a letter, but can otherwise contain both letters and
digits. The Java compiler raises an error at compile-time if the name of a class is one of
Java’s reserved words.
16
program according to some precise rules spelled out below in section 8. As far as the
internal structure of the class is concerned, static variables can be accessed and modified by
all methods contained in a class. Let us revisit class Hello and add a static variable to it.
class Hello{
//Static variable
static String s = "Hello?";
This is still not a very exciting class. It just contains a datum and provides nothing one
can do with it. A class needs to have code that can manipulate the data. This is what we
now turn to with our discussion of methods.
The return-type of a static method can be any Java datatype. A static method that
returns nothing has its return-type given as void. The name of a static method must begin
with a letter, but can otherwise contain letters and digits. The Java compiler raises an error
if the name of a method is one of Java’s reserved words.
The body of a static method is enclosed within curly brackets. Variables can be de-
clared within a method’s body: these variables are local to the method. Local variables
are undefined until they are initialized. Accessing the value of an undefined variable leads
to a compiler error. A method with a return-type other than void must contain a return
statement. The return statement must be placed such that all paths through the method
end with it.
Let us add a simple static method to class Hello.
17
class Hello{
//Static variable
static String s = "Hello?";
//Static methods
}//class Hello
Note the use of comments in the above code to indicate the end of a method and a class.
/* Body of main */
}
A class that is run must contain a method called main, as when a class is run, the code
in main is executed first. From the main method of a class, one calls other methods of the
class.
The header of the main method must be written as above. Do not worry about the
public keyword. We will discuss what this keyword means in section 8. The main method
is static. It takes as its parameter an array of type String called args. The size of this array
is not specified. The contents of this array can be specified at the command prompt when a
class is run. Suppose we want to provide parameters to the main method of a class named
foo. Then the parameters to the main method of class foo are specified after the command
java foo as follows:
java foo a b c
The parameters a, b and c are read as strings into array args. After these parameters
have been read into args, its contents are as follows:
args[0] = "a"
args[1] = "b"
args[2] = "c"
18
Let us add a main method to class Hello so that we can run it.
class Hello{
//Static variable
static String s = "Hello?";
//Static methods
}//class Hello
Save class Hello in a file called Hello.java. Compile the source code in Hello.java
through the command javac Hello.java. To run, type java Hello.
class Arith {
//Static variables
static int a,b;
19
public static void main(String[] args) {
int p = 0; //Declares and initializes variable local to main
This class has two static variables a and b, which are declared to be integers. It also has
a static method add which expects two integers and will return an integer as a result. We
see that from the type information given with the declaration for add.
There are two other kinds of names appearing in the program. First, there are local
variables - variables that are part of a method - such as r (local to add) and p (local to
main). Second, there are parameters namely the n and m which are parameters of the
method add.
Local variables are temporary storage, used when a method is active and removed when
the method finishes. Parameters are names used to refer to data that is passed to a method
when it is called. Let us see what happens when a method is called. A special area of
memory is reserved for the method. This is called the activation record. The following
space is reserved in the activation record. There is space for each local variable, space for
each parameter and space to remember where the control has to return when the call is
complete (the return address). The local variables get values like any other variables when
an assignment is made. The parameters get values when the call is made.
The precise rule for passing values to methods (procedures) varies from language to
language, and many languages support different mechanisms for passing parameters. In
Java, all parameters are passed by value. This means the following. When the call is made,
the arguments to the method are evaluated first. The resulting values are stored. In the
example above, the actual arguments are a and b. These are evaluated to yield 1729 and
4104 respectively. What is stored in the activation record are the values 1729 in the memory
cell named by n and 4104 in the memory cell named by m. Thus we get copies of the values.
We cannot affect the values of a and b from inside the add method, we can only affect the
copies. The copies of parameters are destroyed when a method exits. In fact, the whole
activation record is destroyed. The next time a method is called there is no memory of what
it had done before.
20
7.3 The Dot Operator
To access static variables and static methods inside the class to which they belong, one can
simply refer to the said variables and methods by their names. This is not the case when
one wants to access static variables and static methods belonging to other classes. One
can access static variables and static methods belonging to other classes using the dot(.)
operator. Suppose one wants to access method showFoo belonging to class foo from class
Bar. This is done as follows:
Foo.showFoo();
The dot(.) operator is preceded by the name of the class containing the method that
needs to be called. The method name follows the dot(.) operator. Thus in general, one calls
a static method belonging to another class as follows:
classname.methodname(parameters);
classname.variablename;
Note that one can only successfully access those methods and variables belonging to
another class that are not declared private. We will discuss what this keyword means in
greater detail in sectionr̃efvisibililty.
21
8 Visibility Issues
The variables and methods contained in a class can be accessed by all code in that class.
In order for classes to be an effective data abstraction mechanism, we will need to hide the
code and data in a class from other classes. One can control whether code in other classes
can access the variables and methods in a class through the use of the keywords: public,
private and protected. We will refer to these keywords as access modifiers. In this section,
we will only discuss the public and private keywords. For the purposes of this course, you
do not need to know what the protected keyword means.
Public data and code is visible to all classes, while private data and code is visible only
inside the class that contains it. To control the visibility of a method, place public or
private as the first word in the method header i.e. before the static keyword for static
methods and before the return-type for instance methods (you will learn about instance
methods in the next section). To control the visibility of a variable, place public or private
as the first word in the variable declaration: before the static keyword for static variables
and before the datatype for instance variables (again, you will learn what these are in the
next section).
One can also declare a class as public or private. We will not be going into a discussion
on the visibility of classes except that a public class must be contained in a .java file that
has the same name as the public class. One file can thus contain only one public class. To
declare a class public or private, place public or private before the class keyword in
the class declaration.
22
10 The Object Concept
One of the revolutionary ideas of computer science is that code is just another form of
data and can be treated as such. This idea was the basis of the fundamental conceptual
breakthroughs by Turing and von Neumann in the 30s and 40s which made computer science
possible.
In our discussion of abstraction mechanisms, we talked about procedural abstraction and
data abstraction. We saw how methods captured the idea of procedural abstraction and
classes captured the idea of data abstraction. This separation of abstractions makes an
artificial distinction. In fact, it makes sense to cluster code and data together into a single
entity called an object. In order for this to be possible, the distinction between code and
data has to be blurred. An object is treated like data; it is better to say an object is data.
It can be created and passed as an argument to a method. It can be the result of a method
and it has a type just like other data. However, part of an object is code as an object carries
methods in addition to other forms of data.
What is the type of an object? This is where classes come in. Classes have two roles with
respect to objects. They constitute types for objects and they are also generators of objects.
Let us ignore the second role for the moment. Objects of the same “family” are grouped
together by classes. Now, a class declaration becomes a description of an object. There
may be several objects of the same class present at the same time. Furthermore, individual
objects have their own names. We discuss the role of names below - be warned that names
for objects are a source of great confusion for beginners.
In summary, an object is a package of data and code. An object belongs to a class. Each
object is an instance of the class to which it belongs. There are two roles that a class plays
for its objects: it provides a type for its objects and it is a generator of its objects. The
name of a class is the type that a class gives to its objects. A class contains constructors
that allow one to generate or create objects of that class. Both roles will be discussed below.
class PointIn3D{
23
//Instance Variables
private double x;
private double y;
private double z;
}//class PointIn3D
10.2 Constructors
We need a way of manufacturing new instances of a class, i.e. new objects. A constructor is
a special method that creates an object of the class that contains it. A constructor has the
following syntax:
access-modifier NameOfConstructor(parameter-list){
/* body of constructor */
}
class PointIn3D{
//Instance Variables
24
private double x;
private double y;
private double z;
//Constructors
}//class PointIn3D
Any of the two constructors above can be used to create a PointIn3D object. The second
constructor differs from the first one as its parameters specify values for the fields of a newly
constructed object. Of course, we do not really need the first constructor, but perhaps it is
useful to have since it might be used so often.
25
Figure 1: A Reference as a Pointer
ReferenceType ReferenceName;
PointIn3D p;
Now that we have assigned p to an object, its value is no longer null. Instead, p contains
the address of the object to which it was assigned. p does not contain the actual object that
it was assigned, but the address of that object. This distinction is illustrated in figure 1.
A single object can have more than one reference. Let us create another reference for the
object referred to by p.
PointIn3D q = p;
q now contains the same value (i.e. the same address) as p as shown in figure 2.
26
10.5 Accessing the Fields of an Object
We can access a field of an object using the dot(·) operator on a reference to that object as
follows:
ReferenceName.FieldName;
The above statement returns the appropriate field of the object whose reference we used.
Attempting to access a private field from outside the object’s class with the above statement
will result in a compiler error. Note that all fields of a PointIn3D object are private. This
measure of privacy limits the access of these fields by code in other classes. It also limits the
ability of other classes to corrupt the fields of a PointIn3D object.
ObjectReference.InstanceMethodName(Parameter-List)
If a private method is invoked on an object from outside the object’s class, a compiler
error occurs. Let us add some instance methods to class PointIn3D.
class PointIn3D{
//Instance Variables
private double x;
private double y;
private double z;
//Constructors
27
x = 0;
y = 0;
z = 0;
}
28
public void setZ(double Z){
z = Z;
}
The instance methods defined in this class allow us to access and modify the fields (i.e.
the state) of a PointIn3D object. These methods are public and can be used by code in
any class.
class PointIn3D{
//Instance Variables
private double x;
private double y;
private double z;
//Constructors
29
//This constructor takes parameters
public PointIn3D(double X, double Y, double Z){
30
public void print(){
System.out.println("x = " + x + " y = " + y + " z = " + z);
}
/* Static Methods */
31
/* Do p and r refer to the same object? Check if both references
are equal */
if (p == r)
System.out.println("p and r are references to the same object!");
}//class PointIn3D
In the makeZero methods, we are passing references to PointIn3D objects. Using these
references, we can access and modify the fields of the actual objects. Note that because any
reference that is passed to a method is a copy of the original reference, we cannot modify
the original reference in the method. In the makeZero method, the reference p1 is assigned
to null. The original reference p is not modified.
Copy class PointIn3D into a .java file. Compile the program and run class PointIn3D.
32
to it. Let us rewrite a constructor and an instance method of class PointIn3D using the
this reference:
public PointIn3D(){
this.x = 0;
this.y = 0;
this.z = 0;
}
The above constructor and instance method are equivalent to those previously written
in class PointIn3D.
class Cell{
//Instance variables
private int item;
private Cell next;
//Constructors
public Cell(int n){item = n; next = null;};
33
public Cell(int n, Cell c){ item = n; next = c;};
//Instance methods
public int first(){ return item;};
public Cell rest(){ return next;};
public void SetItem(int n){ item = n;};
public void SetNext(Cell c){ next = c;};
};
Do not worry about all the methods just yet. Note that the basic data is an integer
and a reference to another cell. Note that the class definition is recursive! There are two
constructors one for making a brand new cell and one for making a new cell and linking it with
an existing chain of cells. The basic data is declared private but the instance methods allow
one to see and to modify the value and the next link. We have provided a class method to
display a sequence of cells. To recapitulate: each cell object has data representing a number
and a link to another cell. The code for the instance methods is copied in each object (at
least conceptually) and there is a class method for displaying the sequence of cells. Why did
we not make this an instance method? It was a design choice, it could very well have been
an instance method.
In order to work with linked lists and hide all the manipulation that goes on, we will
package up the sequences of cells inside another class called List with its own class definition.
The class list will contain the sequence of linked cells as private data. As before, we have
access methods that allow us to see the private data. The point is that the private data can
be manipulated but only through the methods that we have provided.
class List{
//Instance variables
private Cell list; //This is the actual list
34
//Constructors
public List(){
//Used to construct an empty list
list = null;
}
//Instance methods
public int car(){
//Accessing the first cell
return list.first();
}
35
//Concatenates a list to this list, nothing is destroyed
public List append(List l){
if (this.IsNull()) return l.copy();
else if (l.IsNull()) return this.copy();
else return new List(this.car(),this.cdr().append(l));
}
//Class methods
//Small test
l1 = new List(3,new List(4));
display(l1);
//Larger test
l = new List();
for (i=10;i > 0; i--) l = new List(i,l);
display(l);
36
}//class List
A natural question to ask is “why did we have this two-level structure?”. After all, the
linked structure already existed when we defined class Cell. Class List was only there as
packaging. One response is that packaging is important. A more precise answer appropriate
for the present situation can be seen by considering the following question. What would
happen if we wanted to insert a cell into a sequence? Suppose that we had no class List
and just worked with the class Cell. Suppose that we had a sequence of cells named s and
a number n and we wished to insert n (first making a new cell for it) into the sequence.
Perhaps s is sorted and we want to insert n in such a way as to keep it sorted. Now suppose
that n fits somewhere in the middle of s. This is not a problem at all. The following code
does this. We shall view it as a new static method added to the class Cell. Make sure that
you understand the piece of code below before reading further.
/* The following method makes a new cell for n and inserts it into
a sorted sequence of cells starting with c in such a way as
to preserve the sorting. */
In order to insert a number into s, we would call cellInsert with s as the second
argument. This code has a serious problem. It only works if the new value being inserted
is not right in front. Suppose that n is smaller than the smallest item in s (this is handled
37
by the case trailer == null) and we perform the assignment c = temp. What actually
happens? Well, remember that parameter passing is by value. So in the activation record
for cellInsert, there is a cell named c which has a reference to the actual cell s. When we
perform the assignment c = temp, we change this copy but the original cell s still points to
whatever it used to point to, it will not point to temp. We need a handle on the first cell.
One solution is to keep this handle in a special reference. But this is exactly what the list
class does. It has a private reference (called list) which is a reference to the first cell in its
list of data. By packaging this inside an object we keep it secure and accessible only through
our methods. Now we can keep the cell insertion code above, but in the class List we have
an insert method that first ensures that the new item should not be inserted in front before
calling cellInsert. If it has to be inserted in front the List class deals with it directly. This
is done in the method (of class List) shown below.
public void insert(int n){
if (list == null)
list = new Cell(n);
else if (n < list.first())
list = new Cell(n,list);
else
Cell.CellInsert(n,list);
}
The basic design is that class Cell is an auxiliary class which manages the lowest level
of data manipulation. Class List is a higher-level package which is what a user of this class
might actually work with.
12 Inheritance
One of the fundamental advances in the object-oriented paradigm is the ability to reuse
code. It often happens that you find yourself coding a small variation of something that you
had coded before. If your code is organized into classes, you might observe the following
patterns. The new code that you want to write is a new class, but it looks just like an old
class that you wrote a while ago except for a couple of methods. It is to handle situations
like this that we have the notion of inheritance.
The following code is a basic example.
class myInt {
//Instance variable
private int n;
//Constructor
public myInt(int n){
38
this.n = n;
}
//Instance methods
public int getval(){
return n;
}
This class just has an integer in each object together with some basic methods. It is not a
class we would really write. It is here for illustrative purposes only.
Now imagine that we decide to have “integers” made out of complex numbers. These
numbers are called gaussian integers used by the great mathematician Gauss for his work
in number theory. He discovered that as an algebraic system, these numbers behaved very
much like ordinary integers. We might want to extend our ordinary integers to deal with
these gaussian integers. Here is the code that does it. The keyword extends in the class
declaration tells the system that you want all the code that worked for class myInt to be
present in class gaussInt. We say that gaussInt inherits from myInt. We also say that
myInt is the superclass and that gaussInt is the subclass.
//Instance variable
private int m; //Represents the imaginary part
//Constructor
39
public gaussInt(int x, int y){
super(x); //Special keyword
this.m = y;
}
//Instance methods
40
to be modified. Obviously the show method of myInt is no use for gaussInt. In the subclass,
you can give a new definition for an old method name. Thus in the above class, we have
inherited getval and increment unchanged but we have modified show. This is called
overriding. From class gaussInt you cannot use the show method of the superclass as it is
well hidden.
There is also a more subtle phenomenon called overloading. Look at the add method in
the two classes. At first sight, this looks like overriding. However the types of the arguments
expected by the two definitions of the add method are different. The types of the arguements
are called the signature of the method. Now when we use the same name for a method but
with a different signature we get two different methods with the same name. Both mwthods
are available from the subclass. In this case, from the class gaussInt, we can use both add
methods. How does the system know which one to use? It looks at the types of the actual
arguments and decides which one to use. Thus if you want to add an ordinary myInt to a
gaussInt, then the add method of the superclass is used.
There are many more subtle aspects to inheritance that we will go into in more detail in
a later class. For now, it suffices to grasp the basic concept of inheritance and to recognize
when to use it.
41
you need a floating-point value an integer can be used but definitely not the other way. For
example, we may have a method for computing the prime factors of an integer, obviously
such a method would not even make sense of a floating-point number.
How do we set up the subtyping relation? There are some built in instances of subtyping
– such as intCf loat – but, clearly, this is not worth making such a fuss about. Where
subtyping really comes into its own is with user-defined types. In Java, subtyping occurs
automatically when you have inheritance; this does not mean that subtyping and inheritance
are the same thing. You can also have instances of subtyping without any inheritance as we
shall see.
Thus, if we declare a class B to be an extension of class A, we will have - in addition
to all the inheritance - that BCA. In other words, if at some point you have a method foo
which expects an argument of type A; foo will always accept an object of type B. If we
extend class B with class C then we have
CCBCA.
If we extend A with another class D then we will have DCA but there will be no subtyping
relation between B and D or C and D. A method expecting an object of type A will accept
objects of type B, C or D. This gives increased generality to the code.
It is often the case that the inheritance hierarchy is not very “wide”. In other words
it is unlikely that a class A can be sensibly extended in many incompatible ways. This is
because the code has to be inherited and usually only a few methods are modified. If we
are modifying all or almost all the methods then it is clear that we are not really using
inheritance. We are really trying to get the generality offered by subtype polymorphism.
Java supports subtype polymorphism independently of inheritance. This is done by
interfaces. An interface is a declaration of a collection of method names - without any
method bodies - and perhaps some constants. They describe a (sub)set of methods that
a class might have. There is no code in an interface, hence there is nothing to inherit.
A concrete class7 may be declared to implement an interface. This is really a subtyping
declaration. An interface names a new type (not a class) and when we say that a class P
implements interface I we have set up the subtyping relation P CI. Because there was no
code in the interface, P does not inherit any code; but it must have a method of the same
name and type as every method name in the interface I.
A class can implement many interfaces and thus one can have a complex type hierarchy
with multiple subtyping. One often hears the phrase “interfaces allow Java to fake multiple
inheritance”. This is a source of confusion. What interfaces allow is multiple subtyping. Java
definitely does not have multiple inheritance (C++ does have true multiple inheritance); wat
it has is multiple subtyping.
Here is an example of the use of inheritance. Imagine that you have written code to draw
the graph of a mathematical function. You want this to be abstracted on the function. You
7
We use the adjective “concrete” to mean that the class is completely defined, i.e. it has all the actual
code for the methods.
42
do not want to write code to plot a graph of the sine function and another different - but
almost identical - piece of code to plot a graph of the exp function. You want the function
- a piece of code - to be a parameter. We can do this with objects because objects can be
passed around like any other piece of data but yet they carry code. What kind of object is a
mathematical function? It expects a double argument and returns a double result. There
might be other methods associated with function objects but the plot method does not care.
It only cares that there is a method - called, say, y - such that f.y(3.14159) returns a double.
So instead of having to know all about the details of the class of mathematical functions we
just define an interface plottable with the one method y in it with the appropriate type.
When we define our mathematical function objects we can make them as complicated as we
please as long as we declare that they implement plottable and ensure that they really do
have the method y. If we have another type of object - say fin-data - for financial data
we would expect to define a totally different class with no relation to mathematical function
objects. However, fin-data could also have a method y and be declared to implement
plottable. Then our plot method works on totallly unrelated classes. We have achieves
generality for our plotting method through subtype polymorphism in a situation far more
general than could have been achieved by inheritance.
In between interfaces and classes are abstract classes that have some methods defined
and some that are left blank as in interfaces. You can extend them as you would any class.
There are many subtle issues about method lookup and how it interacts with inheritance
and type-checking. However, those issues are best discussed in a programming languages
class. A future edition of these notes will deal with these issues.
• The type checker has to say that a method call is OK at compile time.
• All type checking is done based on what the declared type of a reference to an object
is.
43
• Method lookup is based on actual type of the object and not the declared type of the
reference.
class myInt {
private int n;
public myInt(int n){ this.n = n;}
44
}
}
}
Here is what we can say about the variables (TBD means “to be determined”):
a: myInt myInt
z gaussInt gaussInt
w gaussInt TBD
b myInt gaussInt
45
d myInt myInt
c myInt TBD
b is declared to be of type myInt. There is a method called show in the myInt class. The
type checker sees that and because of that it passes the type checker, but the actual type
of b is gaussInt. Method lookup is based on actual types of objects and therefor b uses the
show method in the gaussInt class and displays what a gaussInt object would have shown.
myInt d = b.add(b)
> 6
b is declared to be of the type myInt, the type checker checks to see whether there is an add
method in the myInt class. Yes there is one; it takes a myInt object and returns a myInt
object as the result. At run time b’s actual type is gaussInt the run-time system checks to
see if there is an add method in the gaussInt class which matches the type that it was told
by the type-checker. There are two add methods - one that takes a myInt and returns a
myInt (This method has been inherited from the myInt class). The other takes a gaussInt
and returns a gaussInt; this is the method that is explicitly defined in the gaussInt class.
However the latter method does not match what the type-checker told the run-time system
to expect.
NOW WHICH ADD METHOD DO WE USE?
since “When there is overloading, it is resolved by typechecking” the method which takes an
object of the type myInt will be used. This is the method that has been inherited. It takes
46
in a myInt and returns a myInt. Hence b.add(b) returns a myInt object and therefor NOW
the actual type of d is myInt.
1. z is declared to be of the type gaussInt. There are two methods in the gaussInt
class, the one that takes in a myInt object and returns a myInt object is used. Why?
Once again overloading is resolved by typechecking. Since b is declared to be a myInt
object it will pick the add method that it inherited.
z is a gaussInt which is a subtype of myInt and hence is added to b and returns a
myInt. w is declared to be a guassInt. Since myInt is not a subtype of gaussInt
the assignment statement will not accept this for the right hand side, and hence would
cause an error.
2. b is declared to be of the type myInt. The type checker checks if there is an add method
in the myInt class there is one which expects a myInt object and returns a myIntobject
z is a gaussInt and since gaussInt is a subtype of myInt, b is added to z to produce
a myInt object
w is declared to be a gaussInt Since myInt is not a subtype of gaussInt it will not
accept it and hence would cause an error.
w = ( (gaussInt) b).add(z)
This does type check as it is just a little modification to case 2 above. Now since w is
a gaussInt, it better get a gaussInt on the right hand side. However, now, because of
the cast, the typechecker knows that b is really a gaussInt. Thus, it now has to choose
between two possible add methods. To resolve the overloading it uses the declared types;
z has declared type gaussInt. Thus when it resolves the overloading of the add method it
figures out to use the gaussInt to gaussInt version.
47
15.1 The Exception Object
In Java, runtime errors are called exceptions. An exception is a Java object. All exceptions
inherit from the Exception class and its subclasses. You can find the definition of class
Exception and a list of its direct subclasses at
http://java.sun.com/products/jdk/1.2/docs/api/java/lang/Exception.html
Java divides exceptions into two groups: unchecked exceptions and checked exceptions.
All unchecked exceptions inherit from the RunTimeException class, which is a subclass of the
Exception class. Note that the name RunTimeException is misleading since all exceptions
occur at runtime.
Runtime errors that occur because of programming errors correspond to unchecked ex-
ceptions. Unchecked exceptions happen because of the programmer’s carelessness and can
be prevented by the programmer through the introduction of checks in the code. Unchecked
exceptions thus do not require exception handling. Two common unchecked exceptions are
ArrayIndexOutofBoundsException and NullPointerException. Both can be prevented
by simple checks in the code: the former exception would not occur if the programmer
checks that an array index actually exists before accessing it, while the latter exception can
be prevented by checking whether a reference contains null before using it to access fields
or invoke methods.
All other runtime errors are categorized as checked exceptions. Checked exceptions are
caused by situations out of the programmer’s control. Checked exceptions require exception
handling. Two common checked exceptions are FileNotFoundException and IOException.
The former exception occurs when the user provides an input file that does not exist. The
latter exception indicates that an error occurred while writing to or reading from a file.
48
throw new FileNotFoundException();
A method must add a throws clause in its header to indicate the types of all the checked
exceptions that it may throw. This clause follows the parameter list in a method’s header.
Commas separate multiple exception types in the throws clause. Here is the header of a
main method that throws an IOException and a FileNotFoundException:
A method’s header advertises the checked exceptions that may occur when the method
executes. When creating a new exception class, it is important that its instances be checked
exceptions, so that others are aware of the existence of a new type of exception that may be
thrown. If a method’s header does not contain a throws clause, then the method throws no
checked exceptions.
Although exceptions can be thrown to indicate runtime errors such as a missing input
file, one can also throw exceptions to indicate special error conditions. Consider a method
called search that looks for an element in an array. The method either returns the first
array index at which the element is found or -1 to indicate that the search was unsuccessful.
One can alternatively throw an exception to indicate that an element was not found in the
array. Let us write search and create a new exception class:
//Constructor
//Static methods
public static int search(int[] A, int key) throws ElementNotFoundException{
49
found = true;
else
index ++;
}
//Initializing A
for (int i = 0; i < A.length; i++){
A[i] = k;
k--;
}
k = 23;
int result = search(A, k);
System.out.println(k + " found at " + result);
k = 88;
k = 35;
result = search(A, k);
System.out.println(k + " found at " + result);
}//main
}//class bar
Compile the code above and run class bar. The source code must be saved in a file called
bar.java as class bar is a public class.
50
We defined a new exception class called ElementNotFoundException, which extends
the Exception class. An instance of the ElementNotFoundException class is a checked
exception and must be declared in the header of a method that throws it. Since we explicitly
throw an ElementNotFoundException in the search method of class bar, we declare that
exception in the throws clause of the method’s header.
An exception can occur in two ways: explicitly through the use of a throw statement
or implicitly by calling a method that can throw an exception. We explicitly throw an
exception in the search method of class bar. The main method of class bar implicitly
throws an exception by calling search, a method that can throw an exception. Thus, the
main method of class bar must also declare an ElementNotFoundException in its throws
clause.
In general, it is not a good idea to indicate an error condition that will occur often by
throwing an exception. This is so because catching exceptions takes a lot of time.
try{
code that could cause exceptions
}
catch (Exception e1){
code that does something about exception e1
}
catch (Exception e2){
code that does something about exception e2
}
Code that can throw exceptions is placed in a try block as above. When an exception
occurs, the normal flow of execution stops as the rest of the code in the try block is skipped.
The Java runtime system starts looking for an appropriate catch clause for the exception
that occurred.
The catch clause for an exception need not be in the method in which the exception is
thrown. It can be in any of the methods that were called before the current method. Indeed,
if a catch clause is not found in the method that throws the exception, then each of the
calling methods is examined in turn for a catch clause. If no catch clause is found in any
of the methods on the calling stack, then a default exception handler catches the exception.
The default exception handler handles all exceptions in the same way: execution is stopped
and the name of the exception and a stack trace are printed on the screen. This is the
scenario that takes place when class bar is run.
51
If a catch clause for the exception is found, then the code in that catch clause is executed.
After the execution of the catch clause, the first line of code not inside a catch clause is
executed. A method that handles an exception need not declare that exception in its throws
clause. If no exception is thrown by the code in a try block, then all the code in the try
block is executed and the all the catch clauses associated with that try block are skipped.
Let us modify the main method of class bar to catch the exception thrown by the search
method.
class ElementNotFoundException extends Exception{
//Constructor
public ElementNotFoundException(){
super();
}
//Static methods
public static int search(int[] A, int key) throws ElementNotFoundException{
//Initializing A
52
for (int i = 0; i < A.length; i++){
A[i] = k;
k--;
}
try{
k = 23;
int result = s2(A, k);
System.out.println(k + " found at " + result);
k = 88;
//This will cause an exception
result = search(A, 88);
System.out.println(k + " found at " + result);
k = 35;
result = search(A, k);
System.out.println(k + " found at" + result);
}
catch(ElementNotFoundException e1){
System.out.println(k + " not found in array A");
}
We enclosed all the calls to search in a try block, since any of these calls could result in an
ElementNotFoundException. The second call to search causes an ElementnotFoundException.
The remaining code in the try block is skipped. The code in the catch clause correspond-
ing to an ElementNotFoundException is executed. After the execution of the catch clause,
normal execution resumes as the print statement after the catch clause is executed. Note
that because the second call to search throws an exception, the third call to search is never
reached. Indeed, if the first call to search caused an exception, then both the second and
third calls to search would be skipped. The above try-catch block is poorly designed. It
is much better to wrap each call to search in a try block. Thus, for each call to search,
one can handle the possibility of an unsuccessful search.
//Constructor
public ElementNotFoundException(){
super();
53
}
//Static methods
public static int search(int[] A, int key) throws ElementNotFoundException{
//Initializing A
for (int i = 0; i < A.length; i++){
A[i] = k;
k--;
}
try{
k = 23;
int result = s2(A, k);
System.out.println(k + " found at " + result);
}
catch(ElementNotFoundException e1){
System.out.println(k + " not found in array A");
}
54
try{
k = 88;
//This will cause an exception
result = search(A, k);
System.out.println(k + " found at " + result);
}
catch(ElementNotFoundException e1){
System.out.println(k + " not found in array A");
}
try{
k = 35;
result = search(A, k);
System.out.println(k + " found at" + result);
}
catch(ElementNotFoundException e1){
System.out.println(k + " not found in array A");
}
16 Conclusions
This is the end of our brief survey of Java. To learn more about the language you should take
a course on programming languages. There are many subtle issues that we have not looked
at most notably subtyping and related issues. There are many details of various predefined
Java classes, libraries and packages that we have not even mentioned. What we have said
about the language is hopefully stable.
Acknowledgements
We would like to thank Haroon Ali Agha, Jacob Eliosoff, Etienne Gagnon, Patrick Lam,
Moses Mathur and Maria Olaguera for helpful comments. We are very grateful to Alan
55
Shaver, Dean of the Faculty of Science for providing the funding that made these notes
possible. The second author would like to thank his wife for reminding him to shave from
time to time.
References
[AG98] Ken Arnold and James Gosling. The Java Programming Language, Second Edition.
Addison-Wesley, 1998.
[HC99] Cay S. Horstmann and Gary Cornell. Core Java 1.2. Sun Microsystems, 1999.
[LL98] John Lewis and William Loftus. Java Software Solutions. Addison-Wesley, 1998.
[vdL99] Peter van der Linden. Just Java 1.2, Fourth Edition. Sun Microsystems Press, 1999.
56