You are on page 1of 84

OOP Using Java

A java program is a sequence of statements that have to be formed in accordance


with the predefined syntax statement is the smallest executable unit in java. Each
statement ends with a semicolon. Compound statements, or blocks, are marked by
delimiting them with braces, {and}

Variables

A variable refers to the memory location that holds values like: numbers, texts etc.
in the memory. A variable is a name of location where the data is stored when a
program executes.
The type of value that a variable can hold is called data type. When we declare a
variable we need to specify the type of value it will hold along with the name of the
variable. This tells the compiler that the particular variable will hold certain amount
of memory to store values. Some common types of data types are used in the
programming languages called as the primitive types like characters,
integers, floating point numbers etc. These primitive data types are given below.

byte: The byte data type is an 8-bit signed two's complement integer. It has a
minimum value of -128 and a maximum value of 127 (inclusive). The byte data type
can be useful for saving memory in large arrays, where the memory savings actually
matters. They can also be used in place of int where their limits help to clarify your
code; the fact that a variable's range is limited can serve as a form of
documentation.

short: The short data type is a 16-bit signed two's complement integer. It has a
minimum value of -32,768 and a maximum value of 32,767 (inclusive). As with byte,
the same guidelines apply: you can use a short to save memory in large arrays, in
situations where the memory savings actually matters.

int: The int data type is a 32-bit signed two's complement integer. It has a minimum
value of -2,147,483,648 and a maximum value of 2,147,483,647 (inclusive). For
integral values, this data type is generally the default choice unless there is a reason
(like the above) to choose something else. This data type will most likely be large
enough for the numbers your program will use, but if you need a wider range of
values, use long instead.

long: The long data type is a 64-bit signed two's complement integer. It has a
minimum value of -9,223,372,036,854,775,808 and a maximum value of
9,223,372,036,854,775,807 (inclusive). Use this data type when you need a range
of values wider than those provided by int.

float: The float data type is a single-precision 32-bit IEEE 754 floating point. Its
range of values is beyond the scope of this discussion, but is specified in section
4.2.3 of the Java Language Specification. As with the recommendations for byte and
short, use a float (instead of double) if you need to save memory in large arrays of
floating point numbers. This data type should never be used for precise values, such
as currency. For that, you will need to use the java.math.BigDecimal class instead.
Numbers and Strings covers BigDecimal and other useful classes provided by the
Java platform.
double: The double data type is a double-precision 64-bit IEEE 754 floating point.
Its range of values is beyond the scope of this discussion, but is specified in section
4.2.3 of the Java Language Specification. For decimal values, this data type is
generally the default choice. As mentioned above, this data type should never be
used for precise values, such as currency.

boolean: The boolean data type has only two possible values: true and false. Use
this data type for simple flags that track true/false conditions. This data type
represents one bit of information, but its "size" isn't something that's precisely
defined.

char: The char data type is a single 16-bit Unicode character. It has a minimum
value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).

Data Default
Description Size
Type Value

boolean true or false 1-bit false

Unicode 16-
char \u0000
Character bit

byte Signed Integer 8-bit (byte) 0

16-
short Signed Integer (short) 0
bit

32-
int Signed Integer 0
bit

64-
long Signed Integer 0L
bit

32-
float Real number 0.0f
bit

64-
double Real number 0.0d
bit

The Java contains the following types of variables:

Instance Variables (Non-static fields): In object oriented programming, objects


store their individual states in the "non-static fields" that is declared without the
static keyword. Each object of the class has its own set of values for these non-static
variables so we can say that these are related to objects (instances of the
class).Hence these variables are also known as instance variables. These variables
take default values if not initialized.  
  
Class Variables (Static fields): These are collectively related to a class and none
of the object can claim them  its sole-proprietor . The variables defined with static
keyword are shared by all objects. Here Objects do not store an individual value but
they are forced to share it among themselves. These variables are declared as "static
fields" using the static keyword. Always the same set of values is shared among
different objects of the same class. So these variables are like global variables which
are same for all objects of the class. These variables take default values if not
initialized.    
 
Local Variables: The variables defined in a method or block of code is called local
variables. These variables can be accessed within a method or block of code only.
These variables don't take default values if not initialized. These values are required
to be initialized before using them.

Parameters: Parameters or arguments are variables used in method declarations. 

Declaring and defining variables:

Each variable must be declared before it can be used in the program. It is declared
by specifying its type and its name. Variable names are strings of any length of
letters, digits, underscores, and dollar signs that with a letter, underscore, or dollar
sign. However, a letter is any Unicode letter (a character above 192), not only 1 of
the 26 letters inn the English alphabet. Local variables must be initialized. Java is
case sensitive so that variable n is deferent from variable N.

Examples:

int num;        //represents that num is a variable that can store value of int type.
String name; //represents that name is a variable that can store string value.
boolean bol;    //represents that bol is a variable that can take boolean value
(true/false);
You can assign a value to a variable at the declaration time by using an assignment
operator ( = ).
int num = 1000;      // This line declares num as an int variable which holds value
"1000".
boolean bol = true; // This line declares bol as boolean variable which is set to the
value "true".

Literals

A literal is the source code representation of a fixed value; literals are represented
directly in your code without requiring computation. As shown below, it's possible to
assign a literal to a variable of a primitive type:

boolean result = true;


char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;

The integral types (byte, short, int, and long) can be expressed using decimal, octal,
or hexadecimal number systems. Decimal is the number system you already use
every day; it's based on 10 digits, numbered 0 through 9. The octal number system
is base 8, consisting of the digits 0 through 7. The hexadecimal system is base 16,
whose digits are the numbers 0 through 9 and the letters A through F. For general-
purpose programming, the decimal system is likely to be the only number system
you'll ever use. However, if you need octal or hexadecimal, the following example
shows the correct syntax. The prefix 0 indicates octal, whereas 0x indicates
hexadecimal.

int decVal = 26; // The number 26, in decimal


int octVal = 032; // The number 26, in octal
int hexVal = 0x1a; // The number 26, in hexadecimal

The floating point types (float and double) can also be expressed using E or e (for
scientific notation), F or f (32-bit float literal) and D or d (64-bit double literal; this is
the default and by convention is omitted).

double d1 = 123.4;
double d2 = 1.234e2; // same value as d1, but in scientific notation
float f1 = 123.4f;

Literals of types char and String may contain any Unicode (UTF-16) characters. If
your editor and file system allow it, you can use such characters directly in your
code. If not, you can use a "Unicode escape" such as '\u0108' (capital C with
circumflex), or "S\u00ED se\u00F1or" (Sí Señor in Spanish). Always use 'single
quotes' for char literals and "double quotes" for String literals. Unicode escape
sequences may be used elsewhere in a program (such as in field names, for
example), not just in char or String literals.
The Java programming language also supports a few special escape sequences for
char and String literals: \b (backspace), \t (tab), \n (line feed), \f (form feed), \r
(carriage return), \" (double quote), \' (single quote), and \\ (backslash).
There's also a special null literal that can be used as a value for any reference type.
null may be assigned to any variable, except variables of primitive types. There's
little you can do with a null value beyond testing for its presence. Therefore, null is
often used in programs as a marker to indicate that some object is unavailable.
Finally, there's also a special kind of literal called a class literal, formed by taking a
type name and appending ".class"; for example, String.class. This refers to the
object (of type Class) that represents the type itself.

Operators

Operators are special symbols that perform specific operations on one, two, or three
operands, and then return a result. As we explore the operators of the Java
programming language, it may be helpful for you to know ahead of time which
operators have the highest precedence. The operators in the following table are
listed according to precedence order

Operators Precedence

postfix expr++ expr--

unary ++expr --expr +expr -expr ~ !


multiplicative */%

additive +-

shift << >> >>>

relational < > <= >= instanceof

equality == !=

bitwise AND &

bitwise exclusive OR ^

bitwise inclusive OR |

logical AND &&

logical OR ||

ternary ?:

assignment = += -= *= /= %= &= ^= |= <<= >>= >>>=

The Simple Assignment Operator

One of the most common operators that you'll encounter is the simple assignment
operator "=". You saw this operator in the Bicycle class; it assigns the value on its
right to the operand on its left:
int cadence = 0;
int speed = 0;
int gear = 1;
This operator can also be used on objects to assign object references, as discussed in
Creating Objects.

The Arithmetic Operators

The Java programming language provides operators that perform addition,


subtraction, multiplication, and division. There's a good chance you'll recognize them
by their counterparts in basic mathematics. The only symbol that might look new to
you is "%", which divides one operand by another and returns the remainder as its
result.

+ additive operator (also used for String concatenation)


- subtraction operator
* multiplication operator
/ division operator
% remainder operator

The following program, ArithmeticDemo, tests the arithmetic operators.

class ArithmeticDemo {
public static void main (String[] args){
int result = 1 + 2; // result is now 3
System.out.println(result);
result = result - 1; // result is now 2
System.out.println(result);
result = result * 2; // result is now 4
System.out.println(result);
result = result / 2; // result is now 2
System.out.println(result);
result = result + 8; // result is now 10
result = result % 7; // result is now 3
System.out.println(result);

}
}

You can also combine the arithmetic operators with the simple assignment operator
to create compound assignments. For example, x+=1; and x=x+1; both increment
the value of x by 1.

The + operator can also be used for concatenating (joining) two strings together, as
shown in the following ConcatDemo program:

class ConcatDemo {
public static void main(String[] args){
String firstString = "This is";
String secondString = " a concatenated string.";
String thirdString = firstString+secondString;
System.out.println(thirdString);
}
}

By the end of this program, the variable thirdString contains "This is a concatenated
string.", which gets printed to standard output.

The Unary Operators

The unary operators require only one operand; they perform various operations such
as incrementing/decrementing a value by one, negating an expression, or inverting
the value of a boolean.

+ Unary plus operator; indicates positive value (numbers are positive


without this, however)
- Unary minus operator; negates an expression
++ Increment operator; increments a value by 1
-- Decrement operator; decrements a value by 1
! Logical complement operator; inverts the value of a boolean

The following program, UnaryDemo, tests the unary operators:

class UnaryDemo {
public static void main(String[] args){
int result = +1; // result is now 1
System.out.println(result);
result--; // result is now 0
System.out.println(result);
result++; // result is now 1
System.out.println(result);
result = -result; // result is now -1
System.out.println(result);
boolean success = false;
System.out.println(success); // false
System.out.println(!success); // true
}
}

The increment/decrement operators can be applied before (prefix) or after (postfix)


the operand. The code result++; and ++result; will both end in result being
incremented by one. The only difference is that the prefix version (++result)
evaluates to the incremented value, whereas the postfix version (result++)
evaluates to the original value. If you are just performing a simple
increment/decrement, it doesn't really matter which version you choose. But if you
use this operator in part of a larger expression, the one that you choose may make a
significant difference.

The following program, PrePostDemo, illustrates the prefix/postfix unary increment


operator:

class PrePostDemo {
public static void main(String[] args){
int i = 3;
i++;
System.out.println(i); // "4"
++i;
System.out.println(i); // "5"
System.out.println(++i); // "6"
System.out.println(i++); // "6"
System.out.println(i); // "7"
}
}

The Equality and Relational Operators

The equality and relational operators determine if one operand is greater than, less
than, equal to, or not equal to another operand. The majority of these operators will
probably look familiar to you as well. Keep in mind that you must use "==", not "=",
when testing if two primitive values are equal.

== equal to
!= not equal to
> greater than
>= greater than or equal to
< less than
<= less than or equal to

The following program, ComparisonDemo, tests the comparison operators:

class ComparisonDemo {
public static void main(String[] args){
int value1 = 1;
int value2 = 2;
if(value1 == value2) System.out.println("value1 == value2");
if(value1 != value2) System.out.println("value1 != value2");
if(value1 > value2) System.out.println("value1 > value2");
if(value1 < value2) System.out.println("value1 < value2");
if(value1 <= value2) System.out.println("value1 <= value2");
}
}
Output:
value1 != value2
value1 < value2
value1 <= value2

The Conditional Operators

The && and || operators perform Conditional-AND and Conditional-OR operations on


two boolean expressions. These operators exhibit "short-circuiting" behavior, which
means that the second operand is evaluated only if needed.

&& Conditional-AND
|| Conditional-OR

The following program, ConditionalDemo1, tests these operators:

class ConditionalDemo1 {
public static void main(String[] args){
int value1 = 1;
int value2 = 2;
if((value1 == 1) && (value2 == 2)) System.out.println("value1
is 1 AND value2 is 2");
if((value1 == 1) || (value2 == 1)) System.out.println("value1
is 1 OR value2 is 1");

}
}

Another conditional operator is ?:, which can be thought of as shorthand for an if-
then-else statement (discussed in the Control Flow Statements section of this
lesson). This operator is also known as the ternary operator because it uses three
operands. In the following example, this operator should be read as: "If some
Condition is true, assign the value of value1 to result. Otherwise, assign the value of
value2 to result."
The following program, ConditionalDemo2, tests the ?: operator:

class ConditionalDemo2 {
public static void main(String[] args){
int value1 = 1;
int value2 = 2;
int result;
boolean someCondition = true;
result = someCondition ? value1 : value2;
System.out.println(result);
}
}

Because some Condition is true, this program prints "1" to the screen. Use the ?:
operator instead of an if-then-else statement if it makes your code more readable;
for example, when the expressions are compact and without side-effects (such as
assignments).

The Type Comparison Operator instanceof

The instanceof operator compares an object to a specified type. You can use it to test
if an object is an instance of a class, an instance of a subclass, or an instance of a
class that implements a particular interface.

The following program, InstanceofDemo, defines a parent class (named Parent), a


simple interface (named MyInterface), and a child class (named Child) that inherits
from the parent and implements the interface.

class InstanceofDemo {
public static void main(String[] args) {
Parent obj1 = new Parent();
Parent obj2 = new Child();
System.out.println("obj1 instanceof Parent: " + (obj1 instanceof
Parent));
System.out.println("obj1 instanceof Child: " + (obj1 instanceof
Child));
System.out.println("obj1 instanceof MyInterface: " + (obj1
instanceof MyInterface));
System.out.println("obj2 instanceof Parent: " + (obj2 instanceof
Parent));
System.out.println("obj2 instanceof Child: " + (obj2 instanceof
Child));
System.out.println("obj2 instanceof MyInterface: " + (obj2
instanceof MyInterface));
}
}
class Parent{}
class Child extends Parent implements MyInterface{}
interface MyInterface{}

Output:
obj1 instanceof Parent: true
obj1 instanceof Child: false
obj1 instanceof MyInterface: false
obj2 instanceof Parent: true
obj2 instanceof Child: true
obj2 instanceof MyInterface: true
When using the instanceof operator, keep in mind that null is not an
instance of anything.

Bitwise and Bit Shift Operators

The Java programming language also provides operators that perform bitwise and bit
shift operations on integral types. The operators discussed in this section are less
commonly used. Therefore, their coverage is brief; the intent is to simply make you
aware that these operators exist.
The unary bitwise complement operator "~" inverts a bit pattern; it can be applied to
any of the integral types, making every "0" a "1" and every "1" a "0". For example, a
byte contains 8 bits; applying this operator to a value whose bit pattern is
"00000000" would change its pattern to "11111111".
The signed left shift operator "<<" shifts a bit pattern to the left, and the signed
right shift operator ">>" shifts a bit pattern to the right. The bit pattern is given by
the left-hand operand, and the number of positions to shift by the right-hand
operand. The unsigned right shift operator ">>>" shifts a zero into the leftmost
position, while the leftmost position after ">>" depends on sign extension.
The bitwise & operator performs a bitwise AND operation.
The bitwise ^ operator performs a bitwise exclusive OR operation.
The bitwise | operator performs a bitwise inclusive OR operation.
The following program, BitDemo, uses the bitwise AND operator to print the number
"2" to standard output.

class BitDemo {
public static void main(String[] args) {
int bitmask = 0x000F;
int val = 0x2222;
System.out.println(val & bitmask); // prints "2"
}
}

Expressions, Statements, Blocks and Methods

Expressions

An expression is a construct made up of variables, operators, and method


invocations, which are constructed according to the syntax of the language, that
evaluates to a single value. You've already seen examples of expressions, illustrated
in bold below:

int cadence = 0;
anArray[0] = 100;
System.out.println("Element 1 at index 0: " + anArray[0]);
int result = 1 + 2; // result is now 3
if(value1 == value2) System.out.println("value1 == value2");

The data type of the value returned by an expression depends on the elements used
in the expression. The expression cadence = 0 returns an int because the
assignment operator returns a value of the same data type as its left-hand operand;
in this case, cadence is an int. As you can see from the other expressions, an
expression can return other types of values as well, such as boolean or String.
The Java programming language allows you to construct compound expressions from
various smaller expressions as long as the data type required by one part of the
expression matches the data type of the other. Here's an example of a compound
expression:

1*2*3

In this particular example, the order in which the expression is evaluated is


unimportant because the result of multiplication is independent of order; the
outcome is always the same, no matter in which order you apply the multiplications.
However, this is not true of all expressions. For example, the following expression
gives different results, depending on whether you perform the addition or the
division operation first:

x + y / 100 // ambiguous

You can specify exactly how an expression will be evaluated using balanced
parenthesis: ( and ). For example, to make the previous expression unambiguous,
you could write the following:

(x + y) / 100 // unambiguous, recommended

If you don't explicitly indicate the order for the operations to be performed, the order
is determined by the precedence assigned to the operators in use within the
expression. Operators that have a higher precedence get evaluated first. For
example, the division operator has a higher precedence than does the addition
operator. Therefore, the following two statements are equivalent:

x + y / 100
x + (y / 100) // unambiguous, recommended

When writing compound expressions, be explicit and indicate with parentheses which
operators should be evaluated first. This practice makes code easier to read and to
maintain.

Statements

Statements are roughly equivalent to sentences in natural languages. A statement


forms a complete unit of execution. The following types of expressions can be made
into a statement by terminating the expression with a semicolon (;).

 Assignment expressions
 Any use of ++ or --
 Method invocations
 Object creation expressions

Such statements are called expression statements. Here are some examples of
expression statements.

aValue = 8933.234; // assignment statement


aValue++; // increment statement
System.out.println("Hello World!"); // method invocation statement
Bicycle myBike = new Bicycle(); // object creation statement

In addition to expression statements, there are two other kinds of statements:


declaration statements and control flow statements. A declaration statement declares
a variable

double aValue = 8933.234; //declaration statement

Blocks

A block is a group of zero or more statements between balanced braces and can be
used anywhere a single statement is allowed. The following example, BlockDemo,
illustrates the use of blocks:

class BlockDemo {
public static void main(String[] args) {
boolean condition = true;
if (condition) { // begin block 1
System.out.println("Condition is true.");
} // end block one
else { // begin block 2
System.out.println("Condition is false.");
} // end block 2
}
}

Methods

Methods in Java are conceptually similar to functions and procedures in other


highlevel languages. Methods can accept parameters as arguments, and their
behavior depends on the object they belong to and the values of any parameters
that are passed. Every method in Java is specified in the body of some class. A
method definition has two parts: the signature, which defines the and parameters for
a method, and the body, which defines what the method does.

A method allows a programmer to send a message to an object. The method


signature specifies how such a message should look and the method body specifies
what the object will do when it receives such a message.
Declaring Methods

The syntax for defining a method is as follows:

modifiers type name(type parameter , …, type parameter ){


0 0 n−1 n−1
// method body …
}

Each of the pieces of this declaration have important uses. The modifiers part
includes the same kinds of scope modifiers that can be used for variables, such as
public, protected, and static, with similar meanings. The type part of the declaration
defines the return type of the method. The name is the name of the method, which
can be any valid Java identifier. The list of parameters and their types declares the
local variables that correspond to the values that are to be passed as arguments to
this method. Each type declaration type can be any Java type name and each
parameter can be any Java identifier. This list of parameters and their types can be
empty, which signifies that there are no values to be passed to this method when it
is invoked. These parameter variables, as well as the instance variables of the class,
can be used inside the body of the method. Likewise, other methods of this class can
be called from inside the body of a method.

When a method of a class is called, it is invoked on a specific instance of that class


and can change the state of that object (except for a static method, which is
associated with the class itself). For example, invoking the following method on
particular gnome changes its name.

public void renameGnome (String s) {


name = s; // Reassign the name instance variable of this gnome.
}

Method Modifiers

As with instance variables, method modifiers can restrict the scope of a method:

public: Anyone can call public methods.

protected: Only methods of the same package or of subclasses can call a protected
method.

private: Only methods of the same class (not methods of a subclass) can call a
private method.

If none of the modifiers above are used, then the method is friendly. Friendly
methods can only be called by objects of classes in the same package.
The above modifiers may be preceded by additional modifiers:

abstract: A method declared as abstract has no code. The signature of such a


method is followed by a semicolon with no method body. For example:

public abstract void setHeight (double newHeight): Abstract methods may only
appear within an abstract class.
final: This is a method that cannot be overridden by a subclass.

static: This is a method that is associated with the class itself, and not with a
particular instance of the class. Static methods can also be used to change the state
of static variables associated with a class (provided these variables are not declared
to be final).

Return Types

A method definition must specify the type of value the method will return. If the
method does not return a value, then the keyword void must be used. If the return
type is void, the method is called a procedure; otherwise, it is called a function. To
return a value in Java, a method must use the return keyword (and the type
returned must match the return type of the method). Here is an example of a
method (from inside the Gnome class) that is a function:

public booleanisMagical () {
returnmagical;
}

As soon as a return is performed in a Java function, the method ends.

Java functions can return only one value. To return multiple values in Java, we
should instead combine all the values we wish to return in a compound object, whose
instance variables include all the values we want to return, and then return a
reference to that compound object. In addition, we can change the internal state of
an object that is passed to a method as another way of "returning" multiple results.

Parameters

A method's parameters are defined in a comma-separated list enclosed in


parentheses after the name of the method. A parameter consists of two parts, the
parameter type and the parameter name. If a method has no parameters, then only
an empty pair of parentheses is used.

All parameters in Java are passed by value, that is, any time we pass a parameter to
a method, a copy of that parameter is made for use within the method body. So if
we pass an int variable to a method, then that variable's integer value is copied. The
method can change the copy but not the original. If we pass an object reference as a
parameter to a method, then the reference is copied as well. Remember that we can
have many different variables that all refer to the same object. Changing the internal
reference inside a method will not change the reference that was passed in. For
example, if we pass a Gnome reference g to a method that calls this parameter h,
then this method can change the reference h to point to a different object, but g will
still refer to the same object as before. Of course, the method can use the reference
h to change the internal state of the object, and this will change g's object as well
(since g and h are currently referring to the same object).

Constructors

A constructor is a special kind of method that is used to initialize newly created


objects. Java has a special way to declare the constructor and a special way to
invoke the constructor. First, let's look at the syntax for declaring a constructor:
modifiers name(type parameter , …, type parameter ){
0 0 n−1 n−1
// constructor body …
}

Thus, its syntax is essentially the same as that of any other method, but there are
some important differences. The name of the constructor, name, must be the same
as the name of the class it constructs. So, if the class is called Fish, the constructor
must be called Fish as well. In addition, we don't specify a return type for a
constructor—its return type is implicitly the same as its name (which is also the
name of the class). Constructor modifiers, shown above as modifiers, follow the
same rules as normal methods, except that an abstract, static, or final constructor is
not allowed.

Here is an example:

publicFish (intw, String n) {


weight = w;
name = n,;
}

The main Method

When we wish to execute a stand-alone Java program, we reference the name of the
class that defines this program by issuing the following command (in a Windows,
Linux, or UNIX shell):

java Aquarium

In this case, the Java run-time system looks for a compiled version of the Aquarium
class, and then invokes the special main method in that class. This method must be
declared as follows:

public static voidmain(String[] args){


// main method body …
}

The arguments passed as the parameter args to the main method are when the
program is called. The args variable is an array of String objects, that is, a collection
of indexed strings, with the first string being args[0], the second being args[1], and
so on.

Decision statements

Control Flow Statements

The statements inside your source files are generally executed from top to bottom, in
the order that they appear. Control flow statements, however, break up the flow of
execution by employing decision making, looping, and branching, enabling your
program to conditionally execute particular blocks of code. This section describes the
decision-making statements (if-then, if-then-else, switch), the looping statements
(for, while, do-while), and the branching statements (break, continue, return)
supported by the Java programming language.
The if-then Statement

The if-then statement is the most basic of all the control flow statements. It tells
your program to execute a certain section of code only if a particular test evaluates
to true. For example, the Bicycle class could allow the brakes to decrease the
bicycle's speed only if the bicycle is already in motion. One possible implementation
of the applyBrakes method could be as follows:

void applyBrakes(){
if (isMoving){ // the "if" clause: bicycle must be moving
currentSpeed--; // the "then" clause: decrease current speed
}
}

If this test evaluates to false (meaning that the bicycle is not in motion), control
jumps to the end of the if-then statement.

In addition, the opening and closing braces are optional, provided that the "then"
clause contains only one statement:

void applyBrakes(){
if (isMoving) currentSpeed--; // same as above, but without braces
}

Deciding when to omit the braces is a matter of personal taste. Omitting them can
make the code more brittle. If a second statement is later added to the "then"
clause, a common mistake would be forgetting to add the newly required braces. The
compiler cannot catch this sort of error; you'll just get the wrong results.

The if-then-else Statement

The if-then-else statement provides a secondary path of execution when an "if"


clause evaluates to false. You could use an if-then-else statement in the applyBrakes
method to take some action if the brakes are applied when the bicycle is not in
motion. In this case, the action is to simply print an error message stating that the
bicycle has already stopped.

void applyBrakes(){
if (isMoving) {
currentSpeed--;
} else {
System.err.println("The bicycle has already stopped!");
}
}

The following program, IfElseDemo, assigns a grade based on the value of a test
score: an A for a score of 90% or above, a B for a score of 80% or above, and so on.

class IfElseDemo {
public static void main(String[] args) {

int testscore = 76;


char grade;

if (testscore >= 90) {


grade = 'A';
} else if (testscore >= 80) {
grade = 'B';
} else if (testscore >= 70) {
grade = 'C';
} else if (testscore >= 60) {
grade = 'D';
} else {
grade = 'F';
}
System.out.println("Grade = " + grade);
}
}

The output from the program is:

Grade = C

You may have noticed that the value of testscore can satisfy more than one
expression in the compound statement: 76 >= 70 and 76 >= 60. However, once a
condition is satisfied, the appropriate statements are executed (grade = 'C';) and the
remaining conditions are not evaluated.

The switch Statement

A switch works with the byte, short, char, and int primitive data types. It also works
with enumerated types and a few special classes that "wrap" certain primitive types:
Character, Byte, Short, and Integer .

The following program, SwitchDemo, declares an int named month whose value
represents a month out of the year. The program displays the name of the month,
based on the value of month, using the switch statement.

class SwitchDemo {
public static void main(String[] args) {

int month = 8;
switch (month) {
case 1: System.out.println("January"); break;
case 2: System.out.println("February"); break;
case 3: System.out.println("March"); break;
case 4: System.out.println("April"); break;
case 5: System.out.println("May"); break;
case 6: System.out.println("June"); break;
case 7: System.out.println("July"); break;
case 8: System.out.println("August"); break;
case 9: System.out.println("September"); break;
case 10: System.out.println("October"); break;
case 11: System.out.println("November"); break;
case 12: System.out.println("December"); break;
default: System.out.println("Invalid month.");break;
}
}
}

In this case, "August" is printed to standard output.

The body of a switch statement is known as a switch block. Any statement


immediately contained by the switch block may be labeled with one or more case or
default labels. The switch statement evaluates its expression and executes the
appropriate case.

Of course, you could also implement the same thing with if-then-else statements:

int month = 8;
if (month == 1) {
System.out.println("January");
} else if (month == 2) {
System.out.println("February");
}
. . . // and so on

Deciding whether to use if-then-else statements or a switch statement is sometimes


a judgment call. You can decide which one to use based on readability and other
factors. An if-then-else statement can be used to make decisions based on ranges of
values or conditions, whereas a switch statement can make decisions based only on
a single integer or enumerated value.

Another point of interest is the break statement after each case. Each break
statement terminates the enclosing switch statement. Control flow continues with the
first statement following the switch block. The break statements are necessary
because without them, case statements fall through; that is, without an explicit
break, control will flow sequentially through subsequent case statements. The
following program, SwitchDemo2, illustrates why it might be useful to have case
statements fall through:

class SwitchDemo2 {
public static void main(String[] args) {

int month = 2;
int year = 2000;
int numDays = 0;

switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
numDays = 31;
break;
case 4:
case 6:
case 9:
case 11:
numDays = 30;
break;
case 2:
if ( ((year % 4 == 0) && !(year % 100 == 0))
|| (year % 400 == 0) )
numDays = 29;
else
numDays = 28;
break;
default:
System.out.println("Invalid month.");
break;
}
System.out.println("Number of Days = " + numDays);
}
}

This is the output from the program.

Number of Days = 29

Technically, the final break is not required because flow would fall out of the switch
statement anyway. However, we recommend using a break so that modifying the
code is easier and less error-prone. The default section handles all values that aren't
explicitly handled by one of the case sections.

Loops

The while and do-while Statements

The while statement continually executes a block of statements while a particular


condition is true. Its syntax can be expressed as:

while (expression) {
statement(s)
}
The while statement evaluates expression, which must return a boolean value. If the
expression evaluates to true, the while statement executes the statement(s) in the
while block. The while statement continues testing the expression and executing its
block until the expression evaluates to false. Using the while statement to print the
values from 1 through 10 can be accomplished as in the following WhileDemo
program:

class WhileDemo {
public static void main(String[] args){
int count = 1;
while (count < 11) {
System.out.println("Count is: " + count);
count++;
}
}
}

You can implement an infinite loop using the while statement as follows:

while (true){
// your code goes here
}

The Java programming language also provides a do-while statement, which can be
expressed as follows:

do {
statement(s)
} while (expression);

The difference between do-while and while is that do-while evaluates its expression
at the bottom of the loop instead of the top. Therefore, the statements within the do
block are always executed at least once, as shown in the following DoWhileDemo
program:

class DoWhileDemo {
public static void main(String[] args){
int count = 1;
do {
System.out.println("Count is: " + count);
count++;
} while (count <= 11);
}
}

The for Statement

The for statement provides a compact way to iterate over a range of values.
Programmers often refer to it as the "for loop" because of the way in which it
repeatedly loops until a particular condition is satisfied. The general form of the for
statement can be expressed as follows:

for (initialization; termination; increment) {


statement(s)
}

When using this version of the for statement, keep in mind that:

The following program, ForDemo, uses the general form of the for statement to print
the numbers 1 through 10 to standard output:

class ForDemo {
public static void main(String[] args){
for(int i=1; i<11; i++){
System.out.println("Count is: " + i);
}
}
}

The output of this program is:

Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Count is: 10

Notice how the code declares a variable within the initialization expression. The
scope of this variable extends from its declaration to the end of the block governed
by the for statement, so it can be used in the termination and increment expressions
as well. If the variable that controls a for statement is not needed outside of the
loop, it's best to declare the variable in the initialization expression. The names i, j,
and k are often used to control for loops; declaring them within the initialization
expression limits their life span and reduces errors.

The three expressions of the for loop are optional; an infinite loop can be created as
follows:

for ( ; ; ) { // infinite loop


// your code goes here
}

The for statement also has another form designed for iteration through Collections
and arrays This form is sometimes referred to as the enhanced for statement, and
can be used to make your loops more compact and easy to read. To demonstrate,
consider the following array, which holds the numbers 1 through 10:
int[] numbers = {1,2,3,4,5,6,7,8,9,10};

The following program, EnhancedForDemo, uses the enhanced for to loop through
the array:
class EnhancedForDemo {
public static void main(String[] args){
int[] numbers = {1,2,3,4,5,6,7,8,9,10};
for (int item : numbers) {
System.out.println("Count is: " + item);
}
}
}

In this example, the variable item holds the current value from the numbers array.
The output from this program is the same as before:
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Count is: 10

We recommend using this form of the for statement instead of the general form
whenever possible.

Branching Statements

The break Statement

The break statement has two forms: labeled and unlabeled. You saw the unlabeled
form in the previous discussion of the switch statement. You can also use an
unlabeled break to terminate a for, while, or do-while loop, as shown in the following
BreakDemo program:

class BreakDemo {
public static void main(String[] args) {
int[] arrayOfInts = { 32, 87, 3, 589, 12, 1076,
2000, 8, 622, 127 };
int searchfor = 12;
int i;
boolean foundIt = false;
for (i = 0; i < arrayOfInts.length; i++) {
if (arrayOfInts[i] == searchfor) {
foundIt = true;
break;
}
}
if (foundIt) {
System.out.println("Found " + searchfor
+ " at index " + i);
} else {
System.out.println(searchfor
+ " not in the array");
}
}
}
This program searches for the number 12 in an array. The break statement, shown
in boldface, terminates the for loop when that value is found. Control flow then
transfers to the print statement at the end of the program. This program's output is:

Found 12 at index 4

An unlabeled break statement terminates the innermost switch, for, while, or do-
while statement, but a labeled break terminates an outer statement. The following
program, BreakWithLabelDemo, is similar to the previous program, but uses nested
for loops to search for a value in a two-dimensional array. When the value is found, a
labeled break terminates the outer for loop (labeled "search"):

class BreakWithLabelDemo {
public static void main(String[] args) {
int[][] arrayOfInts = { { 32, 87, 3, 589 },
{ 12, 1076, 2000, 8 },
{ 622, 127, 77, 955 }
};
int searchfor = 12;

int i;
int j = 0;
boolean foundIt = false;
search:
for (i = 0; i < arrayOfInts.length; i++) {
for (j = 0; j < arrayOfInts[i].length; j++) {
if (arrayOfInts[i][j] == searchfor) {
foundIt = true;
break search;
}
}
}
if (foundIt) {
System.out.println("Found " + searchfor +
" at " + i + ", " + j);
} else {
System.out.println(searchfor
+ " not in the array");
}
}
}

This is the output of the program.

Found 12 at 1, 0

The break statement terminates the labeled statement; it does not transfer the flow
of control to the label. Control flow is transferred to the statement immediately
following the labeled (terminated) statement.

The continue Statement


The continue statement skips the current iteration of a for, while , or do-while loop.
The unlabeled form skips to the end of the innermost loop's body and evaluates the
boolean expression that controls the loop. The following program, ContinueDemo ,
steps through a String, counting the occurences of the letter "p". If the current
character is not a p, the continue statement skips the rest of the loop and proceeds
to the next character. If it is a "p", the program increments the letter count.

class ContinueDemo {
public static void main(String[] args) {
String searchMe = "peter piper picked a peck of pickled peppers";
int max = searchMe.length();
int numPs = 0;
for (int i = 0; i < max; i++) {
//interested only in p's
if (searchMe.charAt(i) != 'p')
continue;
//process p's
numPs++;
}
System.out.println("Found " + numPs + " p's in the string.");
}
}

Here is the output of this program:

Found 9 p's in the string.

To see this effect more clearly, try removing the continue statement and
recompiling. When you run the program again, the count will be wrong, saying that it
found 35 p's instead of 9.

A labeled continue statement skips the current iteration of an outer loop marked with
the given label. The following example program, ContinueWithLabelDemo, uses
nested loops to search for a substring within another string. Two nested loops are
required: one to iterate over the substring and one to iterate over the string being
searched. The following program, ContinueWithLabelDemo, uses the labeled form of
continue to skip an iteration in the outer loop.

class ContinueWithLabelDemo {
public static void main(String[] args) {
String searchMe = "Look for a substring in me";
String substring = "sub";
boolean foundIt = false;
int max = searchMe.length() - substring.length();
test:
for (int i = 0; i <= max; i++) {
int n = substring.length();
int j = i;
int k = 0;
while (n-- != 0) {
if (searchMe.charAt(j++)
!= substring.charAt(k++)) {
continue test;
}
}
foundIt = true;
break test;
}
System.out.println(foundIt ? "Found it" :
"Didn't find it");
}
}

Here is the output from this program.

Found it

The return Statement

The last of the branching statements is the return statement. The return statement
exits from the current method, and control flow returns to where the method was
invoked. The return statement has two forms: one that returns a value, and one that
doesn't. To return a value, simply put the value (or an expression that calculates the
value) after the return keyword.

return ++count;

The data type of the returned value must match the type of the method's declared
return value. When a method is declared void, use the form of return that doesn't
return a value.

return;

Arrays

An array is a container object that holds a fixed number of values of a single type.
The length of an array is established when the array is created. After creation, its
length is fixed. You've seen an example of arrays already, in the main method of the
"Hello World!" application. This section discusses arrays in greater detail.

Each item in an array is called an element, and each element is accessed by its
numerical index. As shown in the above illustration, numbering begins with 0. The
9th element, for example, would therefore be accessed at index 8.
The following program, ArrayDemo, creates an array of integers, puts some values in
it, and prints each value to standard output.

class ArrayDemo {
public static void main(String[] args) {
int[] anArray; // declares an array of integers
anArray = new int[10]; // allocates memory for 10 integers
anArray[0] = 100; // initialize first element
anArray[1] = 200; // initialize second element
anArray[2] = 300; // etc.
anArray[3] = 400;
anArray[4] = 500;
anArray[5] = 600;
anArray[6] = 700;
anArray[7] = 800;
anArray[8] = 900;
anArray[9] = 1000;
System.out.println("Element at index 0: " + anArray[0]);
System.out.println("Element at index 1: " + anArray[1]);
System.out.println("Element at index 2: " + anArray[2]);
System.out.println("Element at index 3: " + anArray[3]);
System.out.println("Element at index 4: " + anArray[4]);
System.out.println("Element at index 5: " + anArray[5]);
System.out.println("Element at index 6: " + anArray[6]);
System.out.println("Element at index 7: " + anArray[7]);
System.out.println("Element at index 8: " + anArray[8]);
System.out.println("Element at index 9: " + anArray[9]);
}
}

The output from this program is:

Element at index 0: 100


Element at index 1: 200
Element at index 2: 300
Element at index 3: 400
Element at index 4: 500
Element at index 5: 600
Element at index 6: 700
Element at index 7: 800
Element at index 8: 900
Element at index 9: 1000

In a real-world programming situation, you'd probably use one of the supported


looping constructs to iterate through each element of the array, rather than write
each line individually as shown above. However, this example clearly illustrates the
array syntax. You'll learn about the various looping constructs (for, while, and do-
while) in the Control Flow section.

Declaring a Variable to Refer to an Array

The above program declares anArray with the following line of code:
int[] anArray; // declares an array of integers

Like declarations for variables of other types, an array declaration has two
components: the array's type and the array's name. An array's type is written as
type[], where type is the data type of the contained elements; the square brackets
are special symbols indicating that this variable holds an array. The size of the array
is not part of its type (which is why the brackets are empty). An array's name can be
anything you want, provided that it follows the rules and conventions as previously
discussed in the naming section. As with variables of other types, the declaration
does not actually create an array — it simply tells the compiler that this variable will
hold an array of the specified type.

Similarly, you can declare arrays of other types:

byte[] anArrayOfBytes;
short[] anArrayOfShorts;
long[] anArrayOfLongs;
float[] anArrayOfFloats;
double[] anArrayOfDoubles;
boolean[] anArrayOfBooleans;
char[] anArrayOfChars;
String[] anArrayOfStrings;

You can also place the square brackets after the array's name:

float anArrayOfFloats[]; // this form is discouraged

However, convention discourages this form; the brackets identify the array type and
should appear with the type designation.

Creating, Initializing, and Accessing an Array

One way to create an array is with the new operator. The next statement in the
ArrayDemo program allocates an array with enough memory for ten integer
elements and assigns the array to the anArray variable.

anArray = new int[10]; // create an array of integers

If this statement were missing, the compiler would print an error like the following,
and compilation would fail:

ArrayDemo.java:4: Variable anArray may not have been initialized.


The next few lines assign values to each element of the array:

anArray[0] = 100; // initialize first element


anArray[1] = 200; // initialize second element
anArray[2] = 300; // etc.
Each array element is accessed by its numerical index:
System.out.println("Element 1 at index 0: " + anArray[0]);
System.out.println("Element 2 at index 1: " + anArray[1]);
System.out.println("Element 3 at index 2: " + anArray[2]);

Alternatively, you can use the shortcut syntax to create and initialize an array:
int[] anArray = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};

Here the length of the array is determined by the number of values provided
between { and }.

You can also declare an array of arrays (also known as a multidimensional array) by
using two or more sets of square brackets, such as String[][] names. Each element,
therefore, must be accessed by a corresponding number of index values.

In the Java programming language, a multidimensional array is simply an array


whose components are themselves arrays. This is unlike arrays in C or Fortran. A
consequence of this is that the rows are allowed to vary in length, as shown in the
following MultiDimArrayDemo program:

class MultiDimArrayDemo {
public static void main(String[] args) {
String[][] names = {{"Mr. ", "Mrs. ", "Ms. "},
{"Smith", "Jones"}};
System.out.println(names[0][0] + names[1][0]); //Mr. Smith
System.out.println(names[0][2] + names[1][1]); //Ms. Jones
}
}

The output from this program is:


Mr. Smith
Ms. Jones

Finally, you can use the built-in length property to determine the size of any array.
The code

System.out.println(anArray.length);

will print the array's size to standard output.

Copying Arrays

The System class has an arraycopy method that you can use to efficiently copy data
from one array into another:

public static void arraycopy(Object src,


int srcPos,
Object dest,
int destPos,
int length)

The two Object arguments specify the array to copy from and the array to copy to.
The three int arguments specify the starting position in the source array, the starting
position in the destination array, and the number of array elements to copy.

The following program, ArrayCopyDemo, declares an array of char elements, spelling


the word "decaffeinated". It uses arraycopy to copy a subsequence of array
components into a second array:
class ArrayCopyDemo {
public static void main(String[] args) {
char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e',
'i', 'n', 'a', 't', 'e', 'd' };
char[] copyTo = new char[7];
System.arraycopy(copyFrom, 2, copyTo, 0, 7);
System.out.println(new String(copyTo));
}
}

The output from this program is:


Caffeine

Class and Objects

Java is a strongly typed language: Every variable must be declared to have a data
type.

Data type categorized as either primitive types or reference types. The eight built-in
primitive types are for integers, characters, decimal numbers, and boolean values.
Reference types are user-defined, and their variables must be instantiated to hold
data. interfaces are types that cannot be instantiated; enum types are defined by
listing all the values that a variable of that type may have.
Classes are concrete data types that specify how their state is stored (the class
fields) and how their instances behave (the instance methods). A class is defined in a
declaration statement with this syntax:

modifers class class-name associations {


declarations
}

where modifiers are keywords such as public and abstract, class-name is an identifier
such as Person that names the class, associations are clauses such as extends
Object, and declarations are declarations of the class’s members.

A class can have six kinds of members:

1. Fields that specify the kind of data that the objects hold.
2. Constructors that specify how the objects are to be created.
3. Methods that specify the operations that the objects can perform.
4. Nested classes.
5. Interfaces.
6. Enum type definitions.

Each member of a class must be specified in its own declaration statement. The
purpose of a declaration is to introduce an identifier to the compiler. It provides all
the information that the compiler needs in order to compile statements that use that
identifier.

A field is a variable that holds data for the object. The simplest kind of field
declaration hasthis syntax:
modifers type name = initializer;

where modifers and the initializer are optional. For example, the Point class in
Example declares two fields at lines 2–3. Each has the modifier protected, which
means that they are accessible only from within the class itself and from its
extensions.

A constructor is a subprogram that creates an object. It’s like a method with these
distinctions:

 Its name is the same as its class name.


 It has no return type.
 It is invoked by the new operator.

The simplest kind of constructor declaration has this syntax:

modifers name(param-decls) {
statements
}

Note that a class need not have a main() method. If it does, it is then an executable
program. Otherwise, it merely defines a new type that can be used elsewhere.

EXAMPLE Ratio Class

public class Ratio {


protected int num;
protected int den;
public static final Ratio ZERO = new Ratio();
private Ratio() {
this(0, 1);
}
public Ratio(int num, int den) {
this.num = num;
this.den = den;
}
public boolean equals(Object object) {
if (object==this) {
return true;
} else if (!(object instanceof Ratio)) {
return false;
}
Ratio that = (Ratio)object;
return (this.num*that.den == that.num*this.den);
}

public int getNum() {


return num;
}
public int getDen() {
return den;
}
public String toString() {
return String.format("%d/%d", num, den);
}
public double value() {
return (double)num/den;
}
}

Instances of this class represent fractions, with numerator (num) and denominator
(den). The static final field ZERO represents the fraction 0/1. It is defined to be static
because it is unique to the class itself — there shouldn’t be more than one ZERO
object.

In addition to its three fields, this class has two constructors and four methods. The
no-arg constructor (it has no arguments) defined at line 6 is declared private so that
it cannot be invoked from outside of its class. It is invoked at line 4 to initialize the
ZERO object. This constructor uses the this keyword at line 7 to invoke the two-arg
constructor, passing 0 to num and 1 to den.

The two-arg constructor at line 10 is provided to the public for constructing Ratio
objects with specific num and den values. Note that, to prevent the ZERO object
from being duplicated, we could have included this at line11:

if (num == 0) {
throw new IllegalArgumentException("Use Ratio.ZERO");
}
But then we would have to replace line 7 with explicit initializations:
num = 0;
den = 1;

instead of invoking the two-arg constructor there.

The equals() method at line 15 overrides the default equals() method that is defined
in the Object class (which all other classes extend). Its purpose is to return true if
and only if its explicit argument (object) represents the same thing as its implicit
argument (this). It returns true immediately (at line 17) if the two objects are
merely different names for the same object. On the other hand, it returns false (at
line 19) if the explicit argument is not even the right type. These tests for the two
extremes are canonical and should be done first in every equals() method. If they
both are passed, then we can recast the explicit argument as an object of the same
type as the implicit argument (Ratio) so we can access its fields (num and den). The
test for equality of two ratios a/b = c/d is whether a*d = b*c, which is done at line
22.

The methods defined at lines 25 and 29 are accessor methods (also called “getter
methods”) providing public access to the class’s private fields.
The toString() method at line 33 also overrides the corresponding method that is
defined in the Object class. Its purpose is to return a String representation of its
implicit argument. It is invoked automatically whenever a reference to an instance of
the class appears in an expression where a String object is expected. For example,
at line 6 in the test program below, the expression "x = " + x concatenates the
string "x = " with the reference x. That reference is replaced by the string "22/7"
that is returned by an implicit invocation of the toString() method.
Finally, the value() method at line 37 returns a decimal approximation of the
numerical value of the ratio. For example, at line 7 in the test program below,
x.value() returns the double value 3.142857142857143.

The program tests the Ratio class:


public class TestRatio {
public static void main(String[] args) {
System.out.println("Ratio.ZERO = " + Ratio.ZERO);
System.out.println("Ratio.ZERO.value() = " + Ratio.ZERO.value());
Ratio x = new Ratio(22, 7);
System.out.println("x = " + x);
System.out.println("x.value() = " + x.value());
System.out.println("x.equals(Ratio.ZERO): " + x.equals(Ratio.ZERO));
Ratio xx = new Ratio(44, 14);
System.out.println("xx = " + xx);
System.out.println("xx.value() = " + xx.value());
System.out.println("x.equals(xx): " + x.equals(xx));
}
}
The output is:
Ratio.ZERO = 0/1
Ratio.ZERO.value() = 0.0
x = 22/7
x.value() = 3.142857142857143
x.equals(Ratio.ZERO): false
xx = 44/14
xx.value() = 3.142857142857143
x.equals(xx): true

The Ratio class in Example 1.5 is immutable: its fields cannot be changed.

MODIFIERS

Modifiers are used in the declaration of class members and local variables. These are
summarized in the following tables.

Modifiers for classes, interfaces, and enums

Modifier Meaning

abstract The class cannot be instantiated.

final The class cannot be extended.

public Its members can be accessed from any other class.

strictfp Floating-point results will be platform-independent.

Constructor modifiers
Modifier Meaning

private It is accessible only from within its own class.

protecte
d It is accessible only from within its own class and its extensions.

public It is accessible from all classes.

Field modifiers

Modifier Meaning

final It must be initialized and cannot be changed.

private It is accessible only from within its own class.

protecte
d It is accessible only from within its own class and its extensions.

public It is accessible from all classes.

static The same storage is used for all instances of the class.

transient It is not part of the persistent state of an object.

volatile It may be modified by asynchronous threads.

Method modifiers

Modifier Meaning

abstract Its body is absent; to be defined in a subclass.

final It cannot be overridden in class extensions.

native Its body is implemented in another programming language.

private It is accessible only from within its own class.

protecte
d It is accessible only from within its own class and its extensions.

public It is accessible from all classes.

static It has no implicit argument.

Local variable modifier


Modifier Meaning

It must be initialized and cannot be changed.


final

The three access modifiers, public, protected, and private, are used to specify where
the declared member (class, field, constructor, or method) can be used. If none of
these is specified, then the entity has package access, which means that it can be
accessed from any class in the same package.

The modifier final has three different meanings, depending upon which kind of entity
it modifies. If it modifies a class, final means that the class cannot be extended to a
subclass. (See Chapter 9.) If it modifies a field or a local variable, it means that the
variable must be initialized and cannot be changed, that is, it is a constant. If it
modifies a method, it means that the method cannot be overridden in any subclass.
The modifier static means that the member can be accessed only as an agent of the
class itself, as opposed to being bound to a specific object instantiated from the
class. For example, the format() method, invoked at line 34 in the Line class in
Example 1.5 on page 6 is a static method:

return String.format("%d/%d", num, den);

It is bound to the String class itself, accessed as String.format(). On the other hand,
the value() method, invoked at line 7 in the test program is a nonstatic method. It is
bound to the object x, an instance of the Ratio class, and is accessed x.value().

A static method is also called a class method; a nonstatic method is also called an
instance method. The object to which an instance method is bound in an invocation
is called its implicit argument for that invocation. For example, the implicit argument
in the invocation

x.equals(xx) is the object x. (xx is the explicit argument.) Note that every program’s
main() method is a static method.

Encapsulation

The least understood of the three concepts is encapsulation. Sometimes,


encapsulation is also called protection or information hiding. In fact, encapsulation,
protection and information hiding are three overlapping concepts.

Definition: Encapsulation

Encapsulation is the inclusion within a program object of all the resources need for
the object to function - basically, the method and the data. The object is said to
"publish its interfaces." Other objects adhere to these interfaces to use the object
without having to be concerned with how the object accomplishes it. The idea is
"don't tell me how you do it; just do it." An object can be thought of as a
selfcontained atom. The object interface consists of public methods and instantiate
data.

Protection and information hiding are techniques used to accomplish encapsulation of


an object. Protection is when you limit the use of class data or methods. Information
hiding is when you remove data, methods or code from a class's public interface in
order to refine the scope of an object. So how are these three concepts implemented
in C++? You'll remember that C++ classes have a public, protected and private
interface. Moving methods or data from public to protected or to private, you are
hiding the information from the public or protected interface. If you have a class A
with one public integer data member d, then the C++ definition would be...

class A
{
public:
integer d;
};

If you moved that data member from the public scope of the private scope, then you
would be hiding the member. Better said, you are hiding the member from the public
interface.
class A
{
private:
integer d;
};

It is important to note that information hiding are not the same as encapsulation.
Just because you protect or hide methods or data, does not mean you are
encapsulating an object. But the ability to protect or hide methods or data, provide
the ability to encapsulate an object. You might say that encapsulating is the proper
use of protection and information hiding. As an example, if I used information hiding
to hide members that should clearly be in the public interface, then I am using
information hiding techniques, but I am not encapsulating the class. In fact, I am
doing the exact opposite (unencapsulating the class). Do not get the idea that
encapsulation is only information hiding. Encapsulation is a lot more. Protection is
another way of encapsulating a class. Protection is about adding methods and data
to a class. When you add methods or data to a class, then you are protecting the
methods or data from use without first having an object of the class. In the previous
example, the data member d cannot be used except as a data member of an object
of class A. It is being protected from use outside of this scenario. I have also heard
many computer scientist use information hiding and protection interchangeably. In
this case, the scientist takes the meaning of protection and assign it to information
hiding. This is quite acceptable. Although I'm no historian, I believe the definition of
information hiding has taken some turns over the years. But I do believe it is
stabilizing on the definition I presented here.

Inheritance

Definition: Inheritance

Inheritance is the concept that when a class of object is defined, any subclass that is
defined can inherit the definitions of one or more general classes. This means for the
Programmer that an object in a subclass need not carry its own definition of data and
methods that are generic to the class (or classes) of which it is a part. This not only
speeds up program development; it also ensures an inherent validity to the defined
subclass object (what works and is consistent about the class will also work for the
subclass).

The simple example in C++ is having a class that inherits a data member from its
parent

class.
class A
{
public:
integer d;
};
class B : public A
{
public:
};

The class B in the example does not have any direct data member does it? Yes, it
does. It inherits the data member d from class A. When one class inherits from
another, it acquires all of its methods and data. We can then instantiate an object of
class B and call into that data member.

void func()
{
B b;
b.d = 10;
};

Polymorphism

Inheritance is a very easy concept to understand. Polymorphism on the other hand is


much harder. Polymorphism is about an objects ability to provide context when
methods or operators are called on the object.

Definition: Polymorphism

In object-oriented programming, polymorphism (from the Greek meaning "having


multiple forms") is the characteristic of being able to assign a different meaning to a
particular symbol or "operator" in different contexts.

The simple example is two classes that inherit from a common parent and implement
the same virtual method.

class A
{
public:
virtual void f()=0;
};
class B
{
public:
virtual void f()
{std::cout << "Hello from B" << std::endl;};
};
class C
{
public:
virtual void f()
{std::cout << "Hello from C" << std::endl;};
};

If I have an object A, then calling the method f() will produce different results
depending on the context, the real type of the object A.

func(A & a)
{
A.f();
};

Abstraction

Another OOP concept related to encapsulation that is less widely used but gaining
ground is abstration.

Definition: Abstraction

Through the process of abstraction, a programmer hides all but the relevant data
about an object in order to reduce complexity and increase efficiency. In the same
way that abstraction sometimes works in art, the object that remains is a
representation of the original, with unwanted detail omitted. The resulting object
itself can be referred to as an abstraction, meaning a named entity made up of
selected attributes and behavior specific to a particular usage of the originating
entity.

The example presented is quite simple. Human's are a type of land animal and all
land animals have a number of legs. The C++ definition of this concept would be...

class LandAnimal
{
public:
virtual int NumberOfLegs()=0;
};
class Human : public LandAnimal
{
public:
virtual int NumberOfLegs()
{return 2;};
};

The method NumberOfLegs in LangAnimal is said to be a pure virtual function. An


abstract class is said to be any class with at least one pure virtual function. Here I
have created a class LandAnimal that is abstract. It can be said that the LandAnimal
class was abstracted from the commonality between all types of land animals, or at
least those that I care about. Other land animals can derive there implementation
from the same class.

class Elephant : public LandAnimal


{
public:
virtual int NumberOfLegs()
{return 4;};
};

Although I cannot create an instance of the class LandAnimal, I can pass derived
instances of the class to a common function without having to implement this
function for each type of LandAnimal.

bool HasTwoLegs(LandAnimal & x)


{
return (x.NumberOfLegs()==2);
};

There is also a less rigid definition of abstraction that would include classes that
without pure virtual functions, but that should not be directly instantiated. A more
rigid definition of abstraction is called purely abstract classes. A C++ class is said to
be purely abstract, if the class only contains pure virtual functions. The LandAnimal
class was such a class. Purely abstract classes are often called interfaces, protocol
classes and abstract base classes.

Modularity

What is modularity?

Modularity is a general concept which applies to the development of software in a


fashion which allows individual modules to be developed, often with a standardized
interface to allow modules to communicate. In fact, the kind of separation of
concerns between objects in an OO language is much the same concept as for
modules, except on a larger scale. Typically, partitioning a system into modules
helps minimize coupling, which should lead to easier to maintain code.

The Java language was not designed with modules in mind (other than packages,
which are likened to Modula-3 modules in the introduction) but none the less there
are many de-facto modules in the Java community.

Libraries are modules too

Libraries are implicitly modules. They may not all have a single interface to
communicate with, but often will have 'public' APIs (which should be used) and
'private' packages which have documented use cases. Furthermore, they have
dependencies themselves (such as JMX or JMS). This can result in automatic
dependency managers bringing in a lot more than is strictly necessary;

In some cases, a module's dependencies can be optional; that is, the module can
provide a subset of functionality with missing dependencies. In the above example, if
JMS isn't present on the runtime classpath, then logging via JMS will not be available
but other mechanisms will be. (Java achieves this through the use of deferred
linking; by not requiring a class to be present until it is accessed, a missing
dependency can be handled by an appropriate ClassNotFoundException. Other
platforms have the concept of weak linking which does much the same runtime
checks.)

When is modularity useful?

Modularity is useful as a general concept to break down an application into different


parts, which can then be tested (and evolved) separately. As noted above, most
libraries are modules anyway so for those producing libraries for others to consume,
modularity is an important concept to understand. Usually, the dependency
information is encoded in the build tool and explicitly documented in the library's
usage notes. It's not uncommon for an upstream library to develop workarounds for
bugs in a lower level library, even when the latest version of the lower level library
has been fixed since, to provide a seamless experience in the higher level library.
(Sometimes these can cause subtle problems however.)

If a library is being built for consumption by others, then it is already implicitly a


module. But in the same way that there are few “Hello World” libraries, there are
also few real “Hello World” modules either. It's only once an application becomes
sufficiently large (or it's being built with a sufficiently modular build system) that the
concept of logically breaking down an application into different parts comes into play.

One aspect that is a benefit to modularization is that of testing. A smaller module


(with a well-defined API) can typically be tested better than a monolithic application.
This is especially true of GUI applications, where the GUI itself might not be easily
testable but the code which it calls may be.

Another aspect is that of evolution. Although the system as a whole will have a
version number, in reality, it is a product of multiple modules and versions under the
covers (whether closed source or open source, there will always be some kind of
library – even the Java version – that is a dependency of the system). As a result,
each module is free to go about evolving in a way suitable for that module. Some
modules may evolve faster than others, whilst some may be stable enough to remain
fixed for long periods.

Project management can also benefit from modularization. Given that a module will
end up having a published API to which others can subscribe, it's possible for
separate modules to be implemented by separate teams. This inevitably happens on
large-scale projects anyway, but sub-teams can be made responsible for the delivery
of different modules.

Finally, modularizing an application can help to concretely identify which versions of


dependent libraries are being used in order to harmonies library dependencies across
a large project.
Runtime versus compile time

Java typically has a flat classpath, whether at compile time or at runtime. In other
words, applications normally have full visibility to any class that's found on the
classpath, regardless of the order of entries in the classpath (assuming that there
are no overlaps, at least; otherwise, first one wins). This enables the functionality of
dynamic linking in Java; a class loaded from the front of the classpath need not have
resolved all references to the classes that may be towards the rear of the classpath
until they're actually required.

This is frequently used when working against a set of interfaces to which the
implementation isn't known about until runtime. For example, an SQL utility can be
compiled against the generic JDBC package, but at runtime (and with an additional
piece of configuration information) can instantiate the correct JDBC driver. This is
typically achieved through the name of a class (which implements a pre-defined
factory interface or abstract class) being supplied to a Class.forName lookup at
runtime. If the specified class doesn't exist (or can't be loaded for any other reason)
an error is generated.

It's therefore quite likely that the compile time classpath is (subtly) different from
the runtime classpath for a module. Further, each module is often compiled in
isolation (module A may be compiled against module C 1.1, and module B may be
compiled against module C 1.2) but then combined at runtime in a single path (and
in this case, either arbitrarily choosing version 1.1 or 1.2 of module C). This leads
quickly to Dependency Hell, especially when it is the transitive closure of these
dependencies which forms the runtime classpath. Build systems like Maven and Ivy
make modularity visible to developers, if not end users.

Java has an underappreciated feature called ClassLoaders which allow the runtime
path to be more segmented. Typically, all classes are loaded from the system
ClassLoader; however, some systems partition their runtime space with different
ClassLoaders. A good example is Tomcat (or other Servlet engines) which typically
have a one ClassLoader-per-WebApp. This allows a WebApp to function normally but
not see (accidentally or otherwise) classes defined by other WebApps in the same
JVM.

The way this works is that each WebApp loads classes from its own ClassLoader, so
that a (local) WebApp's implementation doesn't load classes which conflict with
another WebApp's implementation. The requirement is, for any ClassLoader chain,
that the class spaces be consistent; this means you can have two Util.classes loaded
from two separate ClassLoaders in your VM at one time, provided that these
ClassLoaders aren't visible to one another. (It's also what gives the Servlet engine its
ability to redeploy changes without a restart; by throwing a ClassLoader away, you
throw away references to its classes as well, making the old version eligible for
garbage collection – this then allows the Servlet engine to create a new ClassLoader
and re-load the new versions of the classes in at runtime.)

Modules all the way down

Building a modular system is really a way of partitioning an application into


(potentially) reusable modules and to minimise the coupling between them. It's also
a way of de-coupling a module's requirements; for example, the Eclipse IDE typically
has plugins that have separate dependencies on GUI and non-GUI components (e.g.
jdt.ui and jdt.core). This permits other uses of the non-GUI module (headless builds,
parsing and error checking, etc.) outside of the IDE environment.

Other than the monolithic rt.jar, any system can typically be decomposed into
various modules. The question becomes; is it worth it? After all, it's much easier to
start with a modular system and build your way up than to take a monolithic system
apart and break it into modules.
One of the reasons why this is usually the case is to do with class leakage across
module boundaries. For example, the java.beans package logically shouldn't have
any dependencies on any GUI code; however, java.beans.AppletInitializer, used by
Beans.instantiate(), has a reference to Applet which of course has knock-on
dependencies to the whole AWT chain. So java.beans technically has an optional
dependency on AWT, when common sense dictates that it should not. Had a more
modular approach been taken to building the core Java libraries initially, this error
would have been caught long before the API was ever made public.

At some point, a module cannot be broken further down into sub-modules. However,
sometimes related functions are kept within the same module for ease of
organization, and only decomposed further when necessary. For example, the
refactoring support, originally part of Eclipse's JDT, was pulled out into its own
module in order to allow other languages (like CDT) to take advantage of the generic
refactoring capability.

Plugins

Many systems are extensible through the concept of plugins. In these cases, the host
system has a defined API to which the plugin must conform, and a way of injecting
that plugin in. Many applications (such as web browsers, IDEs and build tools) offer a
way to customize the application by providing a plugin that offers the correct API.

Sometimes these plugins are limited or perform generic operations (decoding audio
or video) but equally well can be complex in their own right (e.g. plugins for IDEs).
Sometimes, these plugins can provide their own plugin to customize the behavior
further, which can make systems highly customizable. (Increasing the number of
levels of indirection can make a system increasingly hard to understand, however.)

The plugin API forms part of a contract which the individual plugins must obey.
These plugins are themselves modules, which go through the normal dependency
chain and versioning issues that the enclosing system is providing. As the complexity
of the (specific) plugin API evolves, so too must the plugin itself (or backward
compatible behavior must be maintained).

One of the reasons for the success of the Netscape plugin API for browsers has been
its simplicity; only a handful of functions are needed, and providing that the host
browser redirects input with the appropriate MIME type, the plugin can handle
processing the rest. However, more complex applications like IDEs typically need far
more tightly integrated modules, and therefore, a more complex API to drive them.

Current state of Java modularity


Many module systems and plugin infrastructures exist in Java at the moment. IDEs
typically are the well known ones, with IntelliJ, NetBeans and Eclipse all offering their
own plugin systems as ways to customize the experience. However, build systems
(Ant, Maven) and even end-user applications (Lotus Notes, Mac AppleScript-able
applications) have the concept of being able to extend the core functionality of the
application or system in question.

Exception Handling

If an error is detected during execution of a lava program, lava throws an exception


after which the program is terminated and an error message is displayed informing
the user which exception was raised (that is, what type of error occurred and where
in the program it happened. However, the user may handle the error in the program
should one occur, at least by making the program ignore it so that execution of the
program can continue. But if an exception is raised, a special course of actions can
be undertaken and then flee program can continue. Catching an error is possible by
a try-catch mechanism

A general format of the try-catch statement is

try {
do something;
} catch (eexception-type exception-name) {
do something;
}

The number of catch clauses is not limited to one. There can be as many as needed,
each one for a particular exception.

In this statement, execution of the body of the try clause is tried, and if an exception
occurs, control is transferred to the catch clause to execute its body. Then execution
of the program continua with a statement following the try-catch statement, unless it
contains the throw Cause which causes an exit from the method

Consider the following method:

public int f1(int[] a, int a) throws ArrayIndexOutOfBoundsException {


return n* a[n+2];
}

The throws clause in the heading of the method is a warning to the user of the
method that a particular exception can occur, and if not handled properly, the pro
-gram crashes. To prevent that from happening, the user may include the try-catch
statement in the caller of f1();

Public void f2 () {
Int[] a = {1,2,3,4,5};
try {
for (int i=0;i<a.length;i++)
Sysytem.out.print(f1(a,i)+ “ “);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(“Exception caught in f2()”);
}
}

The catch clause prints a message, but it does not perform any fixing operation on
the array a, although it could. In this example, the catch clause also includes the
throw statement although this is not very common. In this way, the exception is not
only caught and handled in f 2 ( ). but a caller of f 2() is forced to handle it as well,
as in the method f3():

public void f3() {


try {
f2();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println (“Exception caught in f3()”);
}
}

If the caller of f2() does not handle the exception, the program crashes, although the
exception was caught in f2(). The same fate meets a program if a caller of f 1 ( )
does not catch the exception:

Public void f4 () {
int[] a = {1,2,3,4,5};

for (int i=0;i<a.length;i++)


Sysytem.out.print(f1(a,i)+ “ “);

Note that the behavior of the program in all these cases is the same That is, handling
an exception, passing it to another method, or crashing the program) if t throws
pause is not included in the heading of f1() so that f1() could simply be

public int f1(int[] a, int n) {


return n = a[n+2];
}

The throws Cause is thus a very important signal for the user to a possible problem
that may occur when calling a particular method.

Not all types of exception can be ignored as Me exception raised by f1() is ignored by
f4 (). Most of the time, exceptions have to be handled in the program; otherwise, the
program does Dot compile This, for instance, is the case with IOException thrown by
I/0 methods; therefore. These methods are usually calla inside try-catch clauses.

INPUT AND OUTPUT

The java.lo package provides several classes for reading and writing dam use the
classes, the package has to be explicitly included with the statement
Import Java. lo. *;

In this section, we briefly introduce classes for reading from standard device
(keyboard), writing to standard device (monitor), and processing ItO on files. There
are also a number of other classes that are particularly important for interacting with
the network, such as buffered, filtered, and piped streams.

To print anything on the screen, two statements are sufficient;

Syatem.oue.print( message);
Systenm.out.prlntln( messaged);

The two statements differ in that the second version outputs the end-of-line charade
tar after printing a message. The message printed by the statement is a stnng. The
string can be composed of literal strings, string variables, and string generated by
the method toString ( ) for a particular object; all components of the print statement
are concatenated with the operator +. For example, having declared the class c:

class C {
int I =10;
char a = ‘A’;
public String toString() {
return “(”+I+” “+a+”)”;
}
And an object
C obj = new C();
A printing statement :
System.out.println(“the object : “ +obj);
Outputs
The object : (10 A)

Note that if toString() were not defiened in C, the output would be the
object:C@1cc789

The reason is that class C is by default an extension of the class object whose
method toString( ) prints the address of a particular object. Therefore, it is almost
always critical that toString ( ) is redefined in a user class to have a more meaningful
output than an address.
Reading input is markedly more cumbersome. Data are read from standard input
with the input stream System.in. To that end, the method read () can be used, which
returns an integer. To read a line. the user must define a new method, for example

Public static String readLine() {


int ch;
String s = “”;
while(true){
try {
ch = System.in.read();
if(ch == -1 || (char)ch == ‘\n’ )//end of file or end of line;
break;
else if((char)ch !=’\r’) //ignore carriage return;
s=s+(char)ch;
}catch(IOException e) {
}
}
return s;
}

Because read () is defined as a method that throws an IOException, the exception


has to be handled with the try-catch Cause.
Note that Oh must be an integer to detect the end of file (Which is Ctrl-z entered
Mom the PC keyboard). The end-of-field marker is the number -1, and characters are
really unsigned integers. If Oh were declared as a character, then the assignment
statement would have to be

ch = (char) System.in.read();

with which the end-of-line marker -I would be stored as number 65535 in ch


,whereby the sub condition ch == -1 would be evaluated to false and the system
would wait for further input.

Fortunately, the task can be accomplished differently by first declaring an input


stream with the declarations:

static InputStreamReader cin = new InpputStreamReader(Sytem.in);


static BufferedReader br = new BufferedReader(cin);
or with one declaration
static BufferedReader br = new BufferedReader(new
InpputStreamReader(Sytem.in));

And then a built-in method read.line() can be called assign a value to string s:
try {
s = br.readLine();
}catch(IOException e) {
}

To read a number, an input must be read as a string and then converted into a
number. For example, if input is in string a, then the conversion can be performed
with

try {
i= Integer.valueOf(s.trim()).intValue();
}catch(NumberFormatException e) {
System.out.println(“Not a Number”) ;
}

Note that the assignment


i = Integer.valueOf(s.trim());

is insufficient if i is an integer because Integer.valueOf() returns an object of type


Integer, not an integer of type int,so that the integer value must be extracted from
the object with the method intvalue () .

To perform input and output on a file, the RandomAcceeeFile class should be used. A
file is created with the constructor RandomAcceeeFile(name,made);
The constructor opens a file with the specified name either for reading only or for
reading and writing. The mode is specified by either the letter w or letters rw, for
instance,
RandomAcceeeFile = raf new RandomAcceeeFile(“myFile”,”rw”);

We can move anywhere in the file, but to know that we are within the file, we can
use the method length() that returns the size of the file measured in bytes. The
method getFilePointer() returns the current position in the file. The method seek
( pos ) moves the file pointer to the position specified by an integer pos.
Reading is done by the method read() that returns a byte as an integer, by read( b )
that fills entirely a byte array b, by read( b,off ,len) that fills len cells of the byte
array b starting from cell off, and by readLine() to read one line of input. Other
reading methods return a value specified by their narnes: readBoolean(), readByte(),
readShort( ), readChar(), readInt (), readLong(). All these reading methods have
corresponding writing methods, for example, write(), write (b), read( b, off, len),
etc. plus the method writeBytes(s) to write a string a as a sequence of bytes.

After file processing is finished, the file Should be closed with the close() method:
raf.close();

JAVA AND POINTERS

In this section, a problem of implementing Java objects is analyzed

Although Java does not use explicit pointers and does not allow the programmer to
use them, object access is implemented in terms of pointers. An object occupies
some memory space starting from a certain memory location. A pointer to this object
is a variable that holds the address of the object, and this address is the starting
position of the object in memory. In many languages, pointer is a technical term for
a type of variable; therefore, the term is avoided in discussing lava programs and
usually the term reference is used instead.

Consider the following declarations:

Class node {
String name;
Int age;
}
With declarations
Node p = null, q= new Node(“Bill”,20);
two reference variables are created, p and g. The variable p is initialized to null. The pointer null
does not point anywhere. It is not able to point to any object of any type; therefore, null is
compatible with and can be assigned to a reference variable of any type. After execution of the
assignment
p= Null;
Object reference variables p and q: (a) logic of reference of q to an object; (b) implementation of
this reference.
we may not say that p refers to null or points to null but that p becomes null or p is
null. The variable p is created to be used in the future as a reference to an object,
but currently, it does not refer to any. The variable q is a reference to an object that
is an instance of class Node. Forced by the built-in method new, the operating
system through its memory manager allocates enough space for one unnamed object
that can be accessed, for now, only through the reference variable q. This reference
is a pointer to the address of the memory chunk allocated for the object just created,
as shown in Figure l.la. Figure l.la represents the logic of object reference, whose
implementation can vary from one system to another and is usually much more
intricate than the simple logic presented in this figure. For example, in Sun's
implementation of lava, q refers to a handle which is a pair of pointers: one to the
method table of the object and its type (which is a pointer to the class whose
instance the object isle and the other to the object's data (figure l.lb). ID Microsoft's
implementation, q refers to the object's data with the type and method table pointed
to by a hidden field of the object q. For simplicity, the subsequent illustrations use
the form reflecting the logic of object reference, as in figure 1. Ia.

Keeping in mind how object access is implemented in lava helps explain the to suits
of reference comparisons in lava Consider the following code:

P = new Node(“Bill”,20);
System.out.print(p==q);

The printing statement outputs false because we compare references to two different
objects; that is, we compare two different references (addresses), not the objects.
To compare the objects' contents, their data fields have to be compared one by one
using a method, defined just for this reason. If Node includes the method

illustrating the necessity of using the method clone () :


public Boolean equals (Node n) {
return name.equals (n.name) && age == n.age ;
}

Then prining statement

System.out.print(p.equals(q));

outputs true. ( This can be accomplished much more elegantly in C++ by


overloading the equality operator ==, that is, by defining a method which allows for
application of this operator to instances of class Node.)

The realization that object variables are really references to objects helps explain the
need for caution with the use of the assignment operator. The intention of the
declarations

Node node1 = new Node(“Roger”,20),node2=node1;

is to create objects nodel, assign values to the two fields in nodal, and then create
object node2 and initialize its fields to the same values as in node 1. These objects
are to be independent entities so that assigning values to one of them should not
affect values in the other. However, after the assignments

Node2.name = “Wendy”;
Node2.age = 30;

The printing statement

System.out.println(node.name+” “+node1.age+” ”+node.name+” ”+node2.age);

Generates the output

Wedy 30 wenday 30

Both the ages and names in the two objects are the same. What happened Because
nodal and node2 are pointers, the declarations of node1 and node2 result lo the
situation illustrated lo Figure 1.2a After the assignments to the two fields of node 1,
the situation is as in figure 1.2b. To prevent this from happening, we have to create
a new copy of the object referenced by node 1 and then make node2 to become a
reference to this copy. This can be done by defining the method clone() marked in
the interface Cloneable. A new definition of Node is now:

class Node implements Cloneable {

String name;
Int age;
Node (String n,int a) {
Name = n; age = a ;
}
Node() {
This(“”,0);
}
Public Object clone() {
Return new Node(name,age);
}
Public Boolean equals(Node n) {
Return name.equals(n.name) && age == n.age;
}
}

With this definition, the declarations should be

Node node1 = new Node(“Roger”,20), node2 = (Node) node1.clone();

With results in the situation shown in figure 1,2c so that the two assignments

Node2.name = “Wendy”;
Node.age = 30;

Affected only the second object (Figure 1.2d).

The Java pointers are screened from the programmer. There is no pointer type in
lava. The lack of an explicit pointer type is motivated by the desire to eliminate
harmful behavior of programs. First, it is not possible in Java to have a nonnull
reference to a nonexisting object. If a reference variable is not nun, it always points
to an object because the programmer cannot destroy an object referenced by a
variable. An object can be destroyed in Pascal through the use of the function
dispose() and in C++ through delete. The reason for using dispose() or delete is the
need to return to the memory manager memory space occupied by an unneeded
object Directly after execution of dispose() or delete, pointer variables hold
addresses of objects already returned to the memory manager. If these variables are
not set to null or to the address of an object accessible from the program, the so-
called dangling reference probolem arises, which can lead to a program crash in
Java, the dangling reference problem does not arise. If a reference variable p
changes its reference from one object to another, ant the old object is not referenced
by any other variable q, Men the space occupied by the object is reclaimed
automatically by the operating system through garbage collection See Chapter 12).
There is no equivalent in lava of dispose() or delete. Unneeded objects are simply
abandoned and included in the pool of free memory cells automatically by the
garbage collector during Allocution of the user programs
Another reason for not having explicit pointers in Java is a constant danger ~ having
a reference to a memory location that has nothing to do with toe logic of the
program. This would be possible Trough the pointer arithmetic that is not allowed in
java, where such statement as

(p+q).ch = ‘b’;
(++p).n = 6;
are illiegal.

Interestingly, although explicit pointers are absent in Java, lava relies on Pointers
more heavily than C/C~+. An object declaration is always a declaration of reference
to an object; therefore, an object declaration

Node P;

should be followed by initializing the Variable p either by explicitly using a


constructor, as In

p = new Node();

or by assigning a value from an already initialized variable, as in

p=q;

Because an arry is also an object, the declaration

int a[10];

is illegal; this declaration is considered an attempt to define a variable whose name


is a a[10]. A declaration has to be followed with initialization, which is often
combined

int[] a = new int[10];

In this way, Java does not allow for variables that name objects directly. Thus, the
dot notation used to access fields of the object, as in p. name, is really an indirect
reference to the field name because p is not the name of the object with field Came,
but a reference to (address of) this object. Fortunately, the programmer does not
have to be very concerned about this distinction because it is all the matter of
language implementation. But as mentioned, ~ understanding of these
implementation details helps explain the results of some operations, as illustrated
earlier by the operator =.

Interface

As you've already learned, objects define their interaction with the outside world
through the methods that they expose. Methods form the object's interface with the
outside world; the buttons on the front of your television set, for example, are the
interface between you and the electrical wiring on the other side of its plastic casing.
You press the "power" button to turn the television on and off.
In its most common form, an interface is a group of related methods with empty
bodies. A bicycle's behavior, if specified as an interface, might appear as follows:

interface Bicycle {

void changeCadence(int newValue); // wheel revolutions per minute

void changeGear(int newValue);

void speedUp(int increment);

void applyBrakes(int decrement);


}

To implement this interface, the name of your class would change (to a particular
brand of bicycle, for example, such as ACMEBicycle), and you'd use the implements
keyword in the class declaration:

class ACMEBicycle implements Bicycle {

// remainder of this class implemented as before

Implementing an interface allows a class to become more formal about the behavior
it promises to provide. Interfaces form a contract between the class and the outside
world, and this contract is enforced at build time by the compiler. If your class claims
to implement an interface, all methods defined by that interface must appear in its
source code before the class will successfully compile.

Package

A package is a namespace that organizes a set of related classes and interfaces.


Conceptually you can think of packages as being similar to different folders on your
computer. You might keep HTML pages in one folder, images in another, and scripts
or applications in yet another. Because software written in the Java programming
language can be composed of hundreds or thousands of individual classes, it makes
sense to keep things organized by placing related classes and interfaces into
packages.

The Java platform provides an enormous class library (a set of packages) suitable for
use in your own applications. This library is known as the "Application Programming
Interface", or "API" for short. Its packages represent the tasks most commonly
associated with general-purpose programming. For example, a String object contains
state and behavior for character strings; a File object allows a programmer to easily
create, delete, inspect, compare, or modify a file on the filesystem; a Socket object
allows for the creation and use of network sockets; various GUI objects control
buttons and checkboxes and anything else related to graphical user interfaces. There
are literally thousands of classes to choose from. This allows you, the programmer,
to focus on the design of your particular application, rather than the infrastructure
required to make it work.
Abstract Classes

Abstract Methods and Classes

An abstract class is a class that is declared abstract—it may or may not include
abstract methods. Abstract classes cannot be instantiated, but they can be
subclassed.

An abstract method is a method that is declared without an implementation (without


braces, and followed by a semicolon), like this:

abstract void moveTo(double deltaX, double deltaY);

If a class includes abstract methods, the class itself must be declared abstract, as in:

public abstract class GraphicObject {


// declare fields
// declare non-abstract methods
abstract void draw();
}

When an abstract class is subclassed, the subclass usually provides implementations


for all of the abstract methods in its parent class. However, if it does not, the
subclass must also be declared abstract.

Abstract Classes versus Interfaces

Unlike interfaces, abstract classes can contain fields that are not static and final, and
they can contain implemented methods. Such abstract classes are similar to
interfaces, except that they provide a partial implementation, leaving it to subclasses
to complete the implementation. If an abstract class contains only abstract method
declarations, it should be declared as an interface instead.

Multiple interfaces can be implemented by classes anywhere in the class hierarchy,


whether or not they are related to one another in any way. Think of Comparable or
Cloneable, for example.

By comparison, abstract classes are most commonly subclassed to share pieces of


implementation. A single abstract class is subclassed by similar classes that have a
lot in common (the implemented parts of the abstract class), but also have some
differences (the abstract methods).

An Abstract Class Example

In an object-oriented drawing application, you can draw circles, rectangles, lines,


Bezier curves, and many other graphic objects. These objects all have certain states
(for example: position, orientation, line color, fill color) and behaviors (for example:
moveTo, rotate, resize, draw) in common. Some of these states and behaviors are
the same for all graphic objects—for example: position, fill color, and moveTo.
Others require different implementations—for example, resize or draw. All
GraphicObjects must know how to draw or resize themselves; they just differ in how
they do it. This is a perfect situation for an abstract superclass. You can take
advantage of the similarities and declare all the graphic objects to inherit from the
same abstract parent object—for example, GraphicObject, as shown in the following
figure.

First, you declare an abstract class, GraphicObject, to provide member variables and
methods that are wholly shared by all subclasses, such as the current position and
the moveTo method. GraphicObject also declares abstract methods for methods,
such as draw or resize, that need to be implemented by all subclasses but must be
implemented in different ways. The GraphicObject class can look something like this:

abstract class GraphicObject {


int x, y;
...
void moveTo(int newX, int newY) {
...
}
abstract void draw();
abstract void resize();
}

Each non-abstract subclass of GraphicObject, such as Circle and Rectangle, must


provide implementations for the draw and resize methods:

class Circle extends GraphicObject {


void draw() {
...
}
void resize() {
...
}
}
class Rectangle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}

When an Abstract Class Implements an Interface

In the section on Interfaces , it was noted that a class that implements an interface
must implement all of the interface's methods. It is possible, however, to define a
class that does not implement all of the interface methods, provided that the class is
declared to be abstract. For example,

abstract class X implements Y {


// implements all but one method of Y
}

class XX extends X {
// implements the remaining method in Y
}

In this case, class X must be abstract because it does not fully implement Y, but
class XX does, in fact, implement Y.

Class Members

An abstract class may have static fields and static methods. You can use these static
members with a class reference—for example, AbstractClass.staticMethod()—as you
would with any other class.

Casting in Inheritance hierarchy

The Java Platform Class Hierarchy

The Object class, defined in the java.lang package, defines and implements behavior
common to all classes—including the ones that you write. In the Java platform, many
classes derive directly from Object; other classes derive from some of those classes,
and so on, forming a hierarchy of classes.

The most important aspect of inheritance is not that it provides methods for the new
class. It’s the relationship expressed between the new class and the base class. This
relationship can be summarized by saying “The new class is a type of the existing
class.”

At the top of the hierarchy, Object is the most general of all classes. Classes near the
bottom of the hierarchy provide more specialized behavior.

An Example of Inheritance

Here is the sample code for a possible implementation of a Bicycle class that was
presented in the Classes and Objects lesson:
public class Bicycle {

// the Bicycle class has three fields


public int cadence;
public int gear;
public int speed;
// the Bicycle class has one constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}

// the Bicycle class has four methods


public void setCadence(int newValue) {
cadence = newValue;
}

public void setGear(int newValue) {


gear = newValue;
}

public void applyBrake(int decrement) {


speed -= decrement;
}

public void speedUp(int increment) {


speed += increment;
}

A class declaration for a MountainBike class that is a subclass of Bicycle might look
like this:

public class MountainBike extends Bicycle {


// the MountainBike subclass adds one field
public int seatHeight;

// the MountainBike subclass has one constructor


public MountainBike(int startHeight, int startCadence, int startSpeed, int
startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}

// the MountainBike subclass adds one method


public void setHeight(int newValue) {
seatHeight = newValue;
}

}
MountainBike inherits all the fields and methods of Bicycle and adds the field
seatHeight and a method to set it. Except for the constructor, it is as if you had
written a new MountainBike class entirely from scratch, with four fields and five
methods. However, you didn't have to do all the work. This would be especially
valuable if the methods in the Bicycle class were complex and had taken substantial
time to debug.

What You Can Do in a Subclass

A subclass inherits all of the public and protected members of its parent, no matter
what package the subclass is in. If the subclass is in the same package as its parent,
it also inherits the package-private members of the parent. You can use the inherited
members as is, replace them, hide them, or supplement them with new members:

 The inherited fields can be used directly, just like any other fields.
 You can declare a field in the subclass with the same name as the one in the
superclass, thus hiding it (not recommended).
 You can declare new fields in the subclass that are not in the superclass.
 The inherited methods can be used directly as they are.
 You can write a new instance method in the subclass that has the same
signature as the one in the superclass, thus overriding it.
 You can write a new static method in the subclass that has the same
signature as the one in the superclass, thus hiding it.
 You can declare new methods in the subclass that are not in the superclass.
 You can write a subclass constructor that invokes the constructor of the
superclass, either implicitly or by using the keyword super.

Private Members in a Superclass

A subclass does not inherit the private members of its parent class. However, if the
superclass has public or protected methods for accessing its private fields, these can
also be used by the subclass.

A nested class has access to all the private members of its enclosing class—both
fields and methods. Therefore, a public or protected nested class inherited by a
subclass has indirect access to all of the private members of the superclass.

Casting Objects

We have seen that an object is of the data type of the class from which it was
instantiated. For example, if we write

public MountainBike myBike = new MountainBike();


then myBike is of type MountainBike.

MountainBike is descended from Bicycle and Object. Therefore, a MountainBike is a


Bicycle and is also an Object, and it can be used wherever Bicycle or Object objects
are called for.
The reverse is not necessarily true: a Bicycle may be a MountainBike, but it isn't
necessarily. Similarly, an Object may be a Bicycle or a MountainBike, but it isn't
necessarily.

Casting shows the use of an object of one type in place of another type, among the
objects permitted by inheritance and implementations. For example, if we write

Object obj = new MountainBike();

then obj is both an Object and a Mountainbike (until such time as obj is assigned
another object that is not a Mountainbike). This is called implicit casting.

If, on the other hand, we write

MountainBike myBike = obj;

we would get a compile-time error because obj is not known to the compiler to be a
MountainBike. However, we can tell the compiler that we promise to assign a
MountainBike to obj by explicit casting:

MountainBike myBike = (MountainBike)obj;

This cast inserts a runtime check that obj is assigned a MountainBike so that the
compiler can safely assume that obj is a MountainBike. If obj is not a Mountainbike
at runtime, an exception will be thrown.

Upcasting

This description is not just a fanciful way of explaining inheritance – it’s supported
directly by the language. As an example, consider a base class called Instrument
that represents musical instruments and a derived class called Wind. Because
inheritance means that all of the methods in the base class are also available in the
derived class, any message you can send to the base class can also be sent to the
derived class. If the Instrument class has a play( ) method, so will Wind instruments.
This means we can accurately say that a Wind object is also a type of Instrument.
The following example shows how the compiler supports this notion:

//: Wind.java
// Inheritance & upcasting
import java.util.*;

class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}

// Wind objects are instruments


// because they have the same interface:
class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); // Upcasting
}
} ///:~

What’s interesting in this example is the tune ( ) method, which accepts an
Instrument handle. However, in Wind.main ( ) the tune( ) method is called by giving
it a Wind handle. Given that Java is particular about type checking, it seems strange
that a method that accepts one type will readily accept another type, until you
realize that a Wind object is also an Instrument object, and there’s no method that
tune( ) could call for an Instrument that isn’t also in Wind. Inside tune( ), the code
works for Instrument and anything derived from Instrument, and the act of
converting a Wind handle into an Instrument handle is called upcasting.

Why “upcasting”?

The reason for the term is historical and is based on the way class inheritance
diagrams have traditionally been drawn with the root at the top of the page, growing
downward. (Of course, you can draw your diagrams any way you find helpful.) The
inheritance diagram for Wind.java is then:

Casting from derived to base moves up on the inheritance diagram, so it’s commonly
referred to as upcasting. Upcasting is always safe because you’re going from a more
specific type to a more general type. That is, the derived class is a superset of the
base class. It might contain more methods than the base class, but it must contain
at least the methods in the base class. The only thing that can occur to the class
interface during the upcast is that it can lose methods, not gain them. This is why
the compiler allows upcasting without any explicit casts or other special notation.

Composition vs. inheritance revisited

In object-oriented programming, the most likely way that you’ll create and use code
is by simply packaging data and methods together into a class, and using objects of
that class. Occasionally, you’ll use existing classes to build new classes with
composition. Even less frequently than that you’ll use inheritance. So although
inheritance gets a lot of emphasis while learning OOP, it doesn’t mean that you
should use it everywhere you possibly can. On the contrary, you should use it
sparingly, only when it’s clear that inheritance is useful. One of the clearest ways to
determine whether you should use composition or inheritance is to ask whether you’ll
ever need to upcast from your new class to the base class. If you must upcast, then
inheritance is necessary, but if you don’t need to upcast, then you should look
closely at whether you need inheritance.

Cast with interface

Using an interface where you might expect to see a class is not only allowed, but
generally good practice - so casting to an interface has to be allowed. As long as the
object that is being cast is of a class which implements the interface, there is no
problem - every method in the interface MUST be included in the object. If you try to
cast an object which is of a class which does not implement the interface, you will
get a ClassCastException.

For example, suppose you want to keep a List of Lists (using the interface List from
the Java 2 collections framework). When you pull an object out of a list, all you know
is that it is an Object. But you can cast it to a List, and use all the List methods,
without knowing or caring what sort of list it is:

Object myObject = masterList.get(0);


List mySublist;
if ( myObject instanceof List )
{
mySublist = (List)myObject;
}
else
{// oh dear. something in the master list wasn't a list.
}

VECTORS IN java.util

One of the most useful classes in the java.util package is the class vector. A vector is
the data structure with a contiguous block of memory just like an array. Because
memory locations are contiguous, they can be randomly accessed so that the access
time of any element of the vector is constant. Storage is managed automatically s
that on an attempt to insert an element into a full vector, a larger memory block is
a, located for the vector, the vector elements are copied to the new block, and the al
block is released. Vector is thus a flexible array, that is, an array whose size can be
do manically changed.

The class hierarchy I the packge java.util is as follows:

Object => AbstractCollection => AbstractList => Vector

Lists alphabetically the methods of class vector. Some of these methods are
inherited from AbetractList; others are from AbstractCollection. Following lists most
of the methods of class. Only methods iterator () and listIterator() inherited from
class Abetractlist and methods finalize(), getClass(),notify(), notifyAl(), and waits()
inherited from class Object are no included.
An application of these methods is illustrated in program The contents of affected
vectors are shown as comments on the line in which the methods are ailed contents
of vectors are output with an implicit call to the method tostring () in

Syatem.out.println("v1 = " +v1");

but in the program m Figure 1.4, only one such line is shown

To use the class Vector, the program has to include the import instruction

import java.util.Vector:

Vector v1 is declared empty, and then new elements are inserted with the method
addElement(). Adding a new element to a vector is usually fast unless the vector is
full and has to be copied to a new block But if the vector has some unused cells, it
can accommodate a new element immediately in constant tune.

The status of the vector can be tested with two methods: size() which returns the
number of elements currently in the vector. and capacity(), which returns the
number of cells in the vector. If the vector's capacity is greater than its size, then a
new element can be inserted at the end of the vector immediately. How frequently a
vector is filled and has to be copied depends on the interplay between these two
parameters, size and capacity, and the third parameter, capacity increment. By
default, a new empty vector has capacity 10, and its capacity is doubled every time
all its size readies the current capacity. For a large vector, this may lead to wasted
space. For example, a fill vector containing 50,000 elements has 100,000 cells after
a new element arrives, but the user may never include more elements than 50,001.
In such a situation, the method trimToSiz() should be used to reduce the waste

When the user is reasonably sure of the maximum number of elements inserted in a
vector, the method ensureCapacity() should be used to set capacity to the den sired
number so that all insertions are immediate. Otherwise, the user may set to capacity
increment to a certain value so that when the vector is Fill, it is not doubled but
increased by the capacity increment. Consider the declaration of vector v2 :

Vector v2 = new Vector ( 3, 4 );

Initially, capacity is set to 3, and because the capacity increment equals 4, the
capacity of the vector after inserting the fourth element equals 7. with this capacity
the statement

v2 . ensureCapacity( 9 );

raises the capacity to 11 because it uses the capacity increment. Because for vector
v3 capacity increment is not specified in its declaration, then when its capacity ells
11, the statement

v3.onsuroCapacity(9);

causes the capacity to be doubled to l6.

The method ensureCapacity() affects only the capacity of the vector, not its content.
The method setsize () affects its comment and possibly the capacitor. For example,
the empty vector v2 of capacity 2 changes to v2 = [null, null, null, null] after
execution of

v2.setSize(4);

and it capacity equals 4

The contents of v2 are potentially dangerous if a method is executed that expects


nonnull objects. For example, v2.toString() used in a printing statement raises
NullPointerExceptioo. (To print a vector safely, a loop should be used in whid
v.elementAt( i) is printed.)

The method addElement() adds an element at the end of the vector. The insertion of
an element in any other position can be performed with insertElernentAt(). This
reflects the fact that adding a new element inside the vector is a complex operation
because it requires that all the elements are moved by one position to make room
for the new element.

The method elements() puts vector elements in an object of Enumeration type. The
loop shown in the program works the same for any data structure that re turns an
Enumeration object. In this way, data contained in different data structural become
comparable by, as it were, equalizing the data structures themselves by using the
common ground, toe type Enumeration.

The method c lone() should be used carefully. The method clones the array
implementing the vector, but not the objects in the array. After the method is
finished, the cloned vector includes references to the same objects as the vector
from whim it was cloned. In Figure 1.4, vector v4 contains one object of type Node
(as defined in Section 1.4), and then vector vs. a clone of v4. References the very
same object from position 0. This is evident after the object is updated through
reference v5; both v4 And v5 now reference the same updated object.

DATA STRUCTURES AND OBJECT- ORIENTED PROGRAMMING

Although the computer operates on bits, we do not usually think in these tends; in
fact, we would not like to. Although an integer is a sequence of 32 bits, we prefer
seeing an integer as an entity with its own individuality which is reflected in
operations that can be performed on integers but not on variables of other types. As
an integer uses bits as its building blocks, so other objects can use integers as their
atomic elements. Some data types are already built into a particular language, but
some data types can be, and need to be, defined by the user. New data types have a
distinctive structure, a new configuration of their elements, and this structure
determines the behavior of objects of these new types. The task given to the data
structures domain is to explore such new structures and investigate their behavior in
terms of time and space requirements. Unlike the object-oriented approach, where
we start with behavior and then try to find the most suitable data type which allows
for an efficient performance of desirable operations, we now start with a data type
specification with some data structure and then look at what it can do, how it does it,
and how efficiently. The data structures field is designed for building tools to be
incorporated in and used by application programs and for finding data structures that
can perform certain operations speedily and without imposing too much burden on
computer memory. This field is interested in building closes by concentrating on the
mechanics of these classes, on their gears and cogs, which in most cases are not
visible to the user of the Classes. The data structures field investigates the
operability of these classes and its improvement by modifying the data structures to
be found inside the classes, since it has direct access to them. It sharpens tools and
advises the user to what purposes they can be applied. Because of inheritance, the
user can add some more operations to these Masses and try to squeeze from them
more than the Mass designer did.

The data structures field performs best if done in the object-oriented fashion in this
way, it can build the tools it intends without the danger that these tools will be
inadvertently misused in the application. By encapsulating the data structures into a
Mass and making public only what is necessary for proper usage of the class, the
data structures field can develop tools whose functioning is not compromised by
unnecessary tampering.

Writing a java program-Design, coding, testing and debugging


Basic concepts (Review)
In this article, we get our first glimpse of Java code when we show you how to write
a program from an algorithm. As our example, we write a program to calculate the
area of a square. We introduce the technical terms class, syntax, declaration, input,
output and comments.

In Java: Data, Variable, and Algorithm we wrote the algorithm for finding the area of
a square. We now show how to write the program in Java.

Write the program for the algorithm

We have specified the example algorithm using English statements. (Click image to
enlarge)

However, these statements are sufficiently ‘computer-oriented’ for a computer


program to be written directly from them. Before we do this, let us see how we
expect the program to work from the user’s point of view.
First, the program will type the request for the length of a side; we say the program
prompts the user to supply data. The screen display might look like this:
Enter length of side:
The computer will then wait for the user to type the length. Suppose the user types
12. The display will look like this:
Enter length of side: 12
The program will then accept (we say read) the number typed, calculate the area
and print the result. The display may look like this:
Enter length of side: 12
Area of square is 144
Here we have specified what the output of the program should look like. For instance,
there is a blank line between the prompt line and the line that gives the answer; we
have also specified the exact form of the answer. This is a simple example of output
design. This is necessary since the programmer cannot write the program unless he
knows the precise output required.
In order to write the computer program from the algorithm, a suitable programming
language must be chosen. We can think of a program as a set of instructions, written
in a programming language, which, when executed, will produce a solution to a given
problem or perform some specified task.
The major difference between an algorithm and a program is that an algorithm can
be written using informal language without having to follow any special rules (though
some conventions are usually followed) whereas a program is written in a
programming language and must follow all the rules (the syntax rules) of the
language. (Similarly, if we wish to write correct English, we must follow the syntax
rules of the English language.)
In this series, we will be showing you how to write programs in Java, the
programming language developed by Sun Microsystems in the 1990s, and one of the
most popular and widely used today.
Pursuing our example, the Java program for the algorithm which requests the user to
enter the length of a side and prints the area of the square is shown here (click
image to enlarge):

It is not too important that you understand anything about this program at this time.
But you can observe that a Java program consists of something called a class
(named AreaOfSquare here) which contains something (a method) called main. After
the name of the class comes a left brace { which indicates the beginning of the
class; a matching right brace } (the last one) ends the class. Similarly, there is a left
brace to begin main and a matching right brace to end it.
The statement
int a, s;
is called a declaration. The parts after // are comments which help to explain the
program to a human being but have no effect when the program is run. And * is
used to denote multiplication.
Note also that Java uses System.in to refer to the standard input (usually the
keyboard) and System.out to refer to the standard output (usually the
monitor/screen). In this program, the user will be expected to type the length of the
side on the keyboard and the computer will print the result on the monitor (screen).
All of these terms will be explained in detail in due course.

In this article, we show you how to compile and run your Java program using the
Command Prompt window. For convenience, we tell you how to set the "Path"
variable in Windows.

Using the Command Prompt window

In this article, we show you how to compile and run your Java program. We assume
that the JDK has been installed in
C:\Program Files\java\jdk1.6.0_07
and your program is stored in a file AreaOfSquare.java in the folder C:\MyJava.
We will run our programs using the “Command Prompt” window. This is also called
the MS-DOS window.
Open the window by clicking on Start > All Programs > Accessories > Command
Prompt
At the command prompt >, type cd C:\MyJava
The window should now look as follows (your first lines may be slightly different):

This changes the directory to the folder containing your program,


AreaOfSquare.java.
To compile the program, we would like to type
javac AreaOfSquare.java
where javac.exe is the file containing the compiler.
However, if we did this, we would get a message to the effect that javac is
unknown.
The file javac.exe is found in the bin folder of the JDK.
C:\Program Files\java\jdk1.6.0_07\bin is called the “path” to the compiler. We
must tell Windows where to find the compiler by setting the Path “environment
variable.”
How to set the "Path"
To set Path, go to Start > Control Panel and open the System control panel.

Click on the Advanced tab and then on Environment Variables. In the section System
variables, click on Path (you may need to scroll) and then on Edit. In the pop-up
window, click in the Variable value field and use the right arrow key to go to the end
of the field.

Type a semi-colon ( ; ) if one is not present as the last character on the line. After
the semi-colon, type the path C:\Program Files\java\jdk1.6.0_07\bin\ or C:\
jdk1.6\bin\ or to wherever you installed it. Click OK all the way out to the Control
Panel.

Now you can type

javac xxxx.java

(where xxxx is the name of your program) from any command prompt and Windows
will know where to find the compiler.

Helpful hint: if you’ve set Path correctly and it doesn’t work, close the Command
Prompt window and then reopen it. (If it is open when you set Path, it will not
recognize the change.)

Compile and execute

Now that you’ve set Path, you can compile the program with

javac AreaOfSquare.java

If there are no errors in the program, the compiler will create a file called
AreaOfSquare.class in the folder C:\MyJava. This file contains the Java bytecode
(think of it as machine language) equivalent of the source program.

To execute the program, type

java AreaOfSquare

(Note that you do not type .class.) This invokes the Java interpreter, java.exe, also
stored in the bin folder of the JDK. The Java interpreter will execute the code in the
class file.

Here, the computer will type

Enter length of side:


and wait for you to enter a number. Suppose you type 12. The screen will then look
like this:

Enter length of side: 12


Area of square is 144
and you are returned to the command prompt. The following shows the Command
Prompt window at the end of the above activities:
In this article on Java Programming basics we discuss the last three stages of the
program development process - testing and debugging, documentation and
maintenance.

Test and debug the program

Having written the program, the next job is to test it to find out whether it is doing
its intended job. Testing a program involves the following steps:

1. compile the program: recall that a computer can execute a program written in
machine language only. Before the computer can run our Java program, the latter
must be converted to machine language. We say that the source code must be
converted to object code or machine code. (Note, however, that Java does not
produce machine code directly. It converts Java source code into something called
Java bytecode. This bytecode is executed (we say interpreted) by another program
called the Java interpreter. We will get to details later.)

The program which does this job is called a compiler. Among other things, a compiler
will check the source code for syntax errors—errors which arise from breaking the
rules for writing statements in the language. For example, a common syntax error in
writing Java programs is to omit a semicolon or to put one where it is not required.

If the program contains syntax errors, these must be corrected before compiling it
again. When the program is free from syntax errors, the compiler will convert it to
machine language (bytecode in Java) and we can go on to the next step.
2. run the program: here we request the computer to execute the program and we
supply data to the program for which we know the answer. Such data is called test
data. Some values we can use for the length of a side are 3, 12 and 20.

If the program does not give us the answers 9, 144 and 400, respectively, then we
know that the program contains at least one logic error. A logic error is one which
causes a program to give incorrect results for valid data. A logic error may also
cause a program to crash (come to an abrupt halt).

If a program contains logic errors, we must debug the program—we must find and
correct any errors that are causing the program to produce wrong answers.
To illustrate, suppose the statement which calculates the area was written
(incorrectly) as:
a = s + s;

and when the program is run, 10 is entered for the length. Assume we know that the
area should be 100. But when we run the program, we get

Enter length of side: 10

Area of square is 20

Since this is not the answer we expect, we know that there is an error (perhaps
more than one) in the program. Since the area is wrong, the logical place to start
looking for the error is in the statement which calculates the area. If we look closely,
we should discover that + was typed instead of *. When this correction is made, the
program works fine.

Document the program

The final job is to complete the documentation of the program. So far, our
documentation includes:

 the statement of the problem;


 the algorithm for solving the problem;
 the program listing;
 test data and the results produced by the program.

These are some of the items that make up the technical documentation of the
program. This is documentation that is useful to a programmer, perhaps for
modifying the program at a later stage.

The other kind of documentation which must be written is user documentation. This
enables a non-technical person to use the program without needing to know about
the internal workings of the program. Among other things, the user needs to know
how to load the program in the computer and how to use the various features of the
program. If appropriate, the user will also need to know how to handle unusual
situations which may arise while the program is being used.

Maintain the program

Except for things like class assignments, programs are normally meant to be used
over a long period of time. During this time, errors may be discovered which
previously went unnoticed. Errors may also surface because of conditions or data
that never arose before. Whatever the reason, such errors must be corrected.

But a program may need to be modified for other reasons. Perhaps the assumptions
made when the program was written have now changed due to changed company
policy or even due to a change in government regulations (e.g. changes in income
tax rates). Perhaps the company is changing its computer system and the program
needs to be migrated to the new system. We say the program must be maintained.
Whether or not this is easy to do depends a lot on how the original program was
written. If it was well-designed and properly documented, then the job of the
maintenance programmer would be made so much easier.

Abstract Data Type

Before a program is written, the programmer should have a fairly good idea how to
accomplish the task being implemented by the program. Hence, an outline of the
program containing its requirements should precede the coding process. The larger
and more complex the project, the more detailed the outline phase should be. The
implementation details should be delayed to the later stages of the project. In
particular, the details of the particular data structures to be used in the
implementation should not be specified at the beginning.

Prom the start, it is important to specific each task in terms of input antd output At
the beginning stages, we should be more concerned with what the program should
do, not how it should or could be done. Behavior of the program is more important
than the gears of the mechanism accomplishing it. For example, if an item is needed
to accomplish some tasks, the item is specified in terms of operations performed on
it rather than in terms of its inner structure. These operations may act upon this item
by modifying it, searching for some details in it, or storing something in it. After
these operations are precisely specified, the implementation of the program may
start. The implementation decides which data structure should be used to make
execution most efficient in terms of time and space An item specified in terms of
operations is called an abstract data type. In Java, an abreact data type can be part
of a program in die form of an interface.

Interfaces, successors of protocols in Object C, are similar to classes, but they can
contain only constants flame variables) and method prototypes or signatures,

class BaseClass {

public BaseClass() {
}

void f(String s) {
System.out.println(“Method f() in BaseClass called from “ + S);
h(“BaseClass”);
}
protected void g(String s) {
System.out.println(“method g() in BaseClass called from “ + s);
}

protected void h(String s) {


System.out.println(“method h() in BaseClass called from “ + s);
}
}

class Derived1Level1 extends BaseClass {


public void f(String s) {
System.out.println(“method f() in Derived1Level1 called from “ + s);
g(“Derived1Level1”);
h(“Derived1Level1”);
}
public void h(String s) {
System.out.println(“method h() in Derived1Level1 called from “ + s);
}
}

class Derived2Level1 extends BaseClass {


public void f(String s) {
System.out.println(“method f() in Derived2Level1 called from “ + s);
g(“Derived2Level1”);
// h(“Derived2Level1”);// No method matching h() found in class
BaseClass
}
}

class DerivedLevel12 extends Derived1level1 {


public void f(String s) {
System.out.println(“Method h() in DerivedLevel12 called fro “ + s);
g(“DerivedLevel12”);
h(“DerivedLevel12”);
super.f(“DerivedLevel12”);
}
}

class testInheritance {
void run() {
BaseClass bc = new BaseClass();
Derived1Level1 d1l1 = new Derived1Level1();
Derived2Level1 d2l1 = new Derived2Level1();
DerivedLevel12 d12 = new DerivedLevel12();
bc.f(“main(1)”);
// bc.g(“main(2)”);// No method matching g() found in class BaseClass
// bc.h(“main(3)”);// No method matching h() found in class BaseClass
d1l1.f(“main(4)”);
// d1l1.g(“main(5)”);// No method matching h() found in class
Derived1Level1
d1l1.h(“main(6)”);
d2l1.h(“main(7)”);
// d2l1.g(“main(8)”);// No method matching g() found in class
Derived2Level1
// d2l1.h(“main(9)”);// No method matching h() found in class
Derived2Level1
d12.f(“main(10)”);
// d12.g(“main(11)”);// No method matching g() found in class
DerivedLevel12
d12.h(“main(12)”);
}
}

The execution of
(new testInheritance()).run();
Produce the following output:
Method f(l) in BaseClass called from main ( 1 )
Method h() in BaseClass called from BaseClaas
Method f() in DerivedlLevell called from main(4)
Method g() in BaseClass called from DerivedlLevell
Method h() in DerivedlLevell called from DerivedlLevel
Method h() in DerivedlLevell called from main(6).
Method h() in Derived2Levell called from main(7)
Method g() in BaseClass called from Derived2Levell
Method h() in DerivedLevel2 called from main(10)
Method g() in BaseClass called from DerivedLevel2
Method h() in DerivedlLevell called from DerivedLevel2
Method f() in DerivedlLevell called from DerivedLevel2
Method g() in BaseClass called from DerivedlLevell
Method h() in DerivedlLevell called from DerivedlLevel
Method h() in DerivedlLevell called from main(12)

The class BaseClass is called a base class or a superclass, and other classes are
called subclasses or derived classes because they are derived from the superclass in
that they can use the data fields and methods specified in BaseClass as protected,
public, or have no access modifier. They inherit all these fields and methods from
their base class so that they do not have to repeat the same definitions. However, a
derived class can override the definition of a non-final method by introducing its own
definition. In this way, both the base class and the derived class have some measure
of control over their methods.

The base class can decide which methods and data fields can be revealed to derived
classes so that the principle of information hiding holds not only with respect to the
user of the base class but also to the derived classes. Moreover, the derived class
cab decide which parts of the public and protected methods and data fields to retain
and use and which to modify. For examples both DerivedlLevell and Derived2Levell
redefine method f () by givingtheir own versions of f(). However, the access to the
method with the same name in the parent class is still possible by preceding the
method name with the keyword super as shown in the call of super.f() from f() in
DerivedIevel2.

A derived class can add new methods ant fields of its own. Such a class can be come
a base class for other classes that can be derived from it so that the inheritance
hierarchy can be deliberately extended. For example, the class DerivedlLevell is
derived from BaseClass but at the same time, it is the base class for DerivedLevel2.

Protected methods or fields of the base class are accessible only to derived Masses
and not to nonderived classes. For this reason, both DerivedlLevell and
Derived2Levell can call BaseClase's protected method g( ), but a call to this method
from run () in testIInheritance is rendered illegal.

Unlike C++, which supports multiple inheritance, inheritance in Java has to be


limited to one class only so that it is Not possible to declare a new class with the
declaration

class Derived2Level2 extends Derived1Level1, Derived2Level1 {…..}


In addition, a class declared final cannot be extended (the wrapper classes are
examples of final classes).

Algorithm

 An algorithm is a set of rules for carrying out calculation either by hand or on


a machine.
 An algorithm is a finite step-by-step procedure to achieve a required result.
 An algorithm is a sequence of computational steps that transform the input
into the output.
 An algorithm is a sequence of operations performed on data that have to be
organized in data structures.
 An algorithm is an abstraction of a program to be executed on a physical
machine (model of Computation).

Informally, an algorithm is any well-defined computational procedure that takes


some value, or set of values, as input and produces some value, or set of values, as
output. An algorithm is thus a sequence of computational steps that transform the
input into the output.

Characteristics of an Algorithm

Finiteness: An algorithm must always terminate after a finite number of steps.

Definiteness: Each step of an algorithm must be precisely defined; the actions to be


carried out must be rigorously and unambiguously specified for each case.

Input: an algorithm has zero or more inputs.

Output: an algorithm has one or more outputs.

Effectiveness: an algorithm is also generally expected to be effective, in the sense


that its operations must all be significantly basic that they can in principle be done
exactly and in a finite length of time.

Example:

Problem: finding the largest value among n>=1 numbers.

Input: the value of n and n numbers

Output: the largest value

Steps:

1. Let the value of the first be the largest value denoted by BIG
2. Let R denote the number of remaining numbers. R=n-1
3. If R != 0 then it is implied that the list is still not exhausted. Therefore look the next
number called NEW.
4. Now R becomes R-1
5. If NEW is greater than BIG then replace BIG by the value of NEW
6. Repeat steps 3 to 5 until R becomes zero.
7. Print BIG
8. Stop

End of algorithm

Algorithmic Notations

In this section we present the pseudo code that we use throughout the book to
describe algorithms. The pseudo code used resembles PASCAL and C language
control structures. Hence, it is expected that the reader be aware of PASCAL/C. Even
otherwise at least now it is required that the reader should know preferably C to
practically test the algorithm in this course work.

However, for the sake of completion we present the commonly employed control
constructs present in the algorithms.

1. A conditional statement has the following form

If < condition> then

Block 1

Else

Block 2

If end.

This pseudo code executes block1 if the condition is true otherwise block2 is executed.

2. The two types of loop structures are counter based and conditional based and they
are as follows

 For variable = value1 to value2 do


Block

For end

Here the block is executed for all the values of the variable from value 1 to
value 2.

 There are two types of conditional looping, while type and repeat type.

While (condition) do

Block

While end.

Here block gets executed as long as the condition is true.

 Repeat
Block

Until<condition>

Here block is executed as long as condition is false. It may be


observed that the block is executed at least once in repeat type.

Performance analysis (Complexity Analysis)

An essential aspect to data structures is algorithms. Data structures are


implemented using algorithms. An algorithm is a procedure that you can write as a
java function or program, or any other language. An algorithm states explicitly how
the data will be manipulated.

Time complexity and Space complexity

Some algorithms are more efficient than others. We would prefer to chose an
efficient algorithm, so it would be nice to have metrics for comparing algorithm
efficiency.

The complexity of an algorithm is a function describing the efficiency of the algorithm


in terms of the amount of data the algorithm must process. Usually there are natural
units for the domain and range of this function. There are two main complexity
measures of the efficiency of an algorithm:

Time complexity is a function describing the amount of time an algorithm takes in


terms of the amount of input to the algorithm. "Time" can mean the number of
memory accesses performed, the number of comparisons between integers, the
number of times some inner loop is executed, or some other natural unit related to
the amount of real time the algorithm will take. We try to keep this idea of time
separate from "wall clock" time, since many factors unrelated to the algorithm itself
can affect the real time (like the language used, type of computing hardware,
proficiency of the programmer, optimization in the compiler, etc.). It turns out that,
if we chose the units wisely, all of the other stuff doesn't matter and we can get an
independent measure of the efficiency of the algorithm.

Space complexity is a function describing the amount of memory (space) an


algorithm takes in terms of the amount of input to the algorithm. We often speak of
"extra" memory needed, not counting the memory needed to store the input itself.
Again, we use natural (but fixed-length) units to measure this. We can use bytes,
but it's easier to use, say, number of integers used, number of fixed-sized
structures, etc. In the end, the function we come up with will be independent of the
actual number of bytes needed to represent the unit. Space complexity is sometimes
ignored because the space used is minimal and/or obvious, but sometimes it
becomes as important an issue as time.

For example, we might say "this algorithm takes n2 time," where n is the number of
items in the input. Or we might say "this algorithm takes constant extra space,"
because the amount of extra memory needed doesn't vary with the number of items
processed.

An example: Selection Sort


Suppose we want to put an array of n floating point numbers into ascending
numerical order. This task is called sorting and should be somewhat familiar. One
simple algorithm for sorting is selection sort. You let an index i go from 0 to n-1,
exchanging the ith element of the array with the minimum element from i up to n.
Here are the iterations of selection sort carried out on the sequence {4 3 9 6 1 7 0}:

index 0 1 2 3 4 5 6 comments
--------------------------------------------------------- --------
| 4 3 9 6 1 7 0 initial
i=0 | 0 3 9 6 1 7 4 swap 0,4
i=1 | 0 1 9 6 3 7 4 swap 1,3
i=2 | 0 1 3 6 9 7 4 swap 3, 9
i=3 | 0 1 3 4 9 7 6 swap 6, 4
i=4 | 0 1 3 4 6 7 9 swap 9, 6
i=5 | 0 1 3 4 6 7 9 (done)

Here is a simple implementation in java:

int find_min_index (float [], int, int);


void swap (float [], int, int);

/* selection sort on array v of n floats */

void selection_sort (float v[], int n) {


int i;

/* for i from 0 to n-1, swap v[i] with the minimum


* of the i'th to the n'th array elements
*/
for (i=0; i<n-1; i++)
swap (v, i, find_min_index (v, i, n));
}

/* find the index of the minimum element of float array v from


* indices start to end
*/
int find_min_index (float v[], int start, int end) {
int i, mini;

mini = start;
for (i=start+1; i<end; i++)
if (v[i] < v[mini]) mini = i;
return mini;
}

/* swap i'th with j'th elements of float array v */


void swap (float v[], int i, int j) {
float t;

t = v[i];
v[i] = v[j];
v[j] = t;
}

Now we want to quantify the performance of the algorithm, i.e., the amount of time
and space taken in terms of n. We are mainly interested in how the time and space
requirements change as n grows large; sorting 10 items is trivial for almost any
reasonable algorithm you can think of, but what about 1,000, 10,000, 1,000,000 or
more items?

For this example, the amount of space needed is clearly dominated by the memory
consumed by the array, so we don't have to worry about it; if we can store the
array, we can sort it. That is, it takes constant extra space.

So we are mainly interested in the amount of time the algorithm takes. One
approach is to count the number of array accesses made during the execution of the
algorithm; since each array access takes a certain (small) amount of time related to
the hardware, this count is proportional to the time the algorithm takes.

We will end up with a function in terms of n that gives us the number of array
accesses for the algorithm. We'll call this function T(n), for Time.

T(n) is the total number of accesses made from the beginning of selection_sort until
the end. selection_sort itself simply calls swap and find_min_index as i goes from 0
to n-1, so

T(n) = [ time for swap + time for find_min_index (v, i, n)] .

(n-2 because the for loop goes from 0 up to but not including n-1). (Note: for those
not familiar with Sigma notation, that nasty looking formula above just means "the
sum, as we let i go from 0 to n-2, of the time for swap plus the time for
find_min_index (v, i, n).) The swap function makes four accesses to the array, so
the function is now

T(n) = [ 4 + time for find_min_index (v, i, n)] .

If we look at find_min_index, we see it does two array accesses for each iteration
through the for loop, and it does the for loop n - i - 1 times:
T(n) = [ 4 + 2 (n - i - 1)] .

With some mathematical manipulation, we can break this up into:

T(n) = 4(n-1) + 2n(n-1) - 2(n-1) - 2 i.

(everything times n-1 because we go from 0 to n-2, i.e., n-1 times). Remembering
that the sum of i as i goes from 0 to n is (n(n+1))/2, then substituting in n-2 and
cancelling out the 2's:

T(n) = 4(n-1) + 2n(n-1) - 2(n-1) - ((n-2)(n-1)).

and to make a long story short,

T(n) = n2 + 3n - 4 .

instance the loop overhead, other processes running on the system, and the fact that
access time to memory is not really a constant. But this kind of analysis gives you a
good idea of the amount of time you'll spend waiting, and allows you to compare this
algorithms to other algorithms that have been analyzed in a similar way.

Another algorithm used for sorting is called merge sort. The details are somewhat
more complicated and will be covered later in the course, but for now it's sufficient to
state that a certain C implementation takes Tm(n) = 8n log n memory accesses to
sort n elements. Let's look at a table of T(n) vs. Tm(n):

n T(n) Tm(n)
--- ---- -----
2 6 11
3 14 26
4 24 44
5 36 64
6 50 86
7 66 108
8 84 133
9 104 158
10 126 184
11 150 211
12 176 238
13 204 266
14 234 295
15 266 324
16 300 354
17 336 385
18 374 416
19 414 447
20 456 479

T(n) seems to outperform Tm(n) here, so at first glance one might think selection
sort is better than merge sort. But if we extend the table:

n T(n) Tm(n)
--- ---- -----
20 456 479
21 500 511
22 546 544
23 594 576
24 644 610
25 696 643
26 750 677
27 806 711
28 864 746
29 924 781
30 986 816

we see that merge sort starts to take a little less time than selection sort for larger
values of n. If we extend the table to large values:

n T(n) Tm(n)
--- ---- -----
100 10,296 3,684
1,000 1,002,996 55,262
10,000 100,029,996 736,827
100,000 10,000,299,996 9,210,340
1,000,000 1,000,002,999,996 110,524,084
10,000,000 100,000,029,999,996 1,289,447,652

we see that merge sort does much better than selection sort. To put this in
perspective, recall that a typical memory access is done on the order of
nanoseconds, or billionths of a second. Selection sort on ten million items takes
roughly 100 trillion accesses; if each one takes ten nanoseconds (an optimistic
assumption based on 1998 hardware) it will take 1,000,000 seconds, or about 11
and a half days to complete. Merge sort, with a "mere" 1.2 billion accesses, will be
done in 12 seconds. For a billion elements, selection sort takes almost 32,000 years,
while merge sort takes about 37 minutes. And, assuming a large enough RAM size, a
trillion elements will take selection sort 300 million years, while merge sort will take
32 days. Since computer hardware is not resilient to the large asteroids that hit our
planet roughly once every 100 million years causing mass extinctions, selection sort
is not feasible for this task.
Asymptotic Analysis

This function we came up with, T(n) = n2 + 3n - 4, describes precisely the number


of array accesses made in the algorithm. In a sense, it is a little too precise; all we
really need to say is n2; the lower order terms contribute almost nothing to the sum
when n is large. We would like a way to justify ignoring those lower order terms and
to make comparisons between algorithms easy. So we use asymptotic notation.

Big O

The most common notation used is "big O" notation. In the above example, we
would say n2 + 3n - 4 = O(n2) (read "big oh of n squared"). This means, intuitively,
that the important part of n2 + 3n - 4 is the n2 part.

Definition: Let f(n) and g(n) be functions, where n is a positive integer. We write
f(n) = O(g(n)) if and only if there exists a real number c and positive integer n0
satisfying 0 <= f(n) <= cg(n) for all n >= n0. (And we say, "f of n is big oh of g of
n." We might also say or write f(n) is in O(g(n)), because we can think of O as a set
of functions all with the same property. But we won't often do that in Data
Structures.)

This means that, for example, that functions like n2 + n, 4n2 - n log n + 12, n2/5 -
100n, n log n, 50n, and so forth are all O(n2). Every function f(n) bounded above by
some constant multiple g(n) for all values of n greater than a certain value is
O(g(n)).

Examples:

 Show 3n2 + 4n - 2 = O(n2).


We need to find c and n0 such that:
3n2 + 4n - 2 <= cn2 for all n >= n0 .
Divide both sides by n2, getting:
3 + 4/n - 2/n2 <= c for all n >= n0 .
If we choose n0 equal to 1, then we need a value of c such that:
3 + 4 - 2 <= c
We can set c equal to 6. Now we have:
3n2 + 4n - 2 <= 6n2 for all n >= 1 .

 Show n3 != O(n2). Let's assume to the contrary that


n3 = O(n2)
Then there must exist constants c and n0 such that
n3 <= cn2 for all n >= n0.
Dividing by n2, we get:
n <= c for all n >= n0.
But this is not possible; we can never choose a constant c large enough that n will
never exceed it, since n can grow without bound. Thus, the original assumption, that
n3 = O(n2), must be wrong so n3 != O(n2).

Big O gives us a formal way of expressing asymptotic upper bounds, a way of


bounding from above the growth of a function. Knowing where a function falls within
the big-O hierarchy allows us to compare it quickly with other functions and gives us
an idea of which algorithm has the best time performance. And yes, there is also a
"little o" we'll see later.

Properties of Big O

The definition of big O is pretty ugly to have to work with all the time, kind of like the
"limit" definition of a derivative in Calculus. Here are some helpful theorems you can
use to simplify big O calculations:

 Any kth degree polynomial is O(nk).


 a nk = O(nk) for any a > 0.
 Big O is transitive. That is, if f(n) = O(g(n)) and g(n) is O(h(n)), then f(n) =
O(h(n)).
 logan = O(logb n) for any a, b > 1. This practically means that we don't care,
asymptotically, what base we take our logarithms to. (I said asymptotically.
In a few cases, it does matter.)
 Big O of a sum of functions is big O of the largest function. How do you know
which one is the largest? The one that all the others are big O of. One
consequence of this is, if f(n) = O(h(n)) and g(n) is O(h(n)), then f(n) + g(n)
= O(h(n)).
 f(n) = O(g(n)) is true if limn->infinityf(n)/g(n) is a constant.

(Lower Bounds and Tight Bounds)

Big O only gives you an upper bound on a function, i.e., if we ignore constant factors
and let n get big enough, we know some function will never exceed some other
function. But this can give us too much freedom. For instance, the time for selection
sort is easily O(n3), because n2 is O(n3). But we know that O(n2) is a more
meaningful upper bound. What we need is to be able to describe a lower bound, a
function that always grows more slowly than f(n), and a tight bound, a function that
grows at about the same rate as f(n). Your book give a good theoretical introduction
to these two concepts; let's look at a different (and probably easier to understand)
way to approach this.

Big Omega is for lower bounds what big O is for upper bounds:

Definition: Let f(n) and g(n) be functions, where n is a positive integer. We write
f(n) = (g(n)) if and only if g(n) = O(f(n)). We say "f of n is omega of g of n."

This means g is a lower bound for f; after a certain value of n, and without regard to
multiplicative constants, f will never go below g.

Finally, theta notation combines upper bounds with lower bounds to get tight
bounds:
Definition: Let f(n) and g(n) be functions, where n is a positive integer. We write
f(n) = (g(n)) if and only if g(n) = O(f(n)). and g(n) = (f(n)). We say "f of n is
theta of g of n."

More Properties
 The first four properties listed above for big O are also true for Omega and
Theta.
 Replace O with and "largest" with "smallest" in the fifth property for big O
and it remains true.
 f(n) = (g(n)) is true if limn->infinityg(n)/f(n) is a constant.
 f(n) = (g(n)) is true if limn->infinityf(n)/g(n) is a non-zero constant.
 nk = O((1+ ) n)) for any positive k and . That is, any polynomial is bound
from above by any exponential. So any algorithm that runs in polynomial time
is (eventually, for large enough value of n) preferable to any algorithm that
runs in exponential time.
 (log n) = O(n k) for any positive k and . That means a logarithm to any
power grows more slowly than a polynomial (even things like square root,
100th root, etc.) So an algorithm that runs in logarithmic time is (eventually)
preferable to an algorithm that runs in polynomial (or indeed exponential,
from above) time.

You might also like