0% found this document useful (0 votes)
34 views97 pages

JavaNew6 0

This document provides an overview of the Java programming language, including its history and key features. It discusses Java basics such as documentation, language elements, data types, operators, control statements, subroutines, objects and classes. It also covers object-oriented programming concepts, arrays, advanced input/output, networking, and exceptions handling in Java. The document serves as a comprehensive introduction to the Java programming language.

Uploaded by

miahi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views97 pages

JavaNew6 0

This document provides an overview of the Java programming language, including its history and key features. It discusses Java basics such as documentation, language elements, data types, operators, control statements, subroutines, objects and classes. It also covers object-oriented programming concepts, arrays, advanced input/output, networking, and exceptions handling in Java. The document serves as a comprehensive introduction to the Java programming language.

Uploaded by

miahi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 97

Java client-server

Java .............................................................................................................................................. 3

Java white paper .......................................................................................................................... 3

Java compared to another OO language (C, C++) by paradigm...................................................... 4

Basic Java terminology ................................................................................................................. 4

Java Basics.................................................................................................................................... 5

Documentation ........................................................................................................................ 6

Language elements ................................................................................................................... 6


Keywords ...................................................................................................................................... 6
Identifiers ................................................................................................................................... 10
Special symbols .......................................................................................................................... 11

Data types .............................................................................................................................. 11


Basic Java Types ......................................................................................................................... 12
Variables, Declarations, and Assignments .................................................................................. 12
Java Reference Types ................................................................................................................... 13

Operators and expressions ..................................................................................................... 13


Arithmetic Operators ................................................................................................................. 14
Increment and Decrement ......................................................................................................... 14
Relational Operators .................................................................................................................. 15
Boolean Operators ..................................................................................................................... 16
Conditional Operator ................................................................................................................. 16
Assignment Operators ............................................................................................................... 17
Precedence Rules ....................................................................................................................... 17

Control statements ................................................................................................................. 18


The do Loop................................................................................................................................ 18
The for Loop ............................................................................................................................... 19
The break Statement ................................................................................................................. 20
More on the if Statement .......................................................................................................... 20
The switch Statement ................................................................................................................ 22
The Empty Statement ................................................................................................................ 24

Subroutines ............................................................................................................................ 25
SUBROUTINE as the Black Box ................................................................................................... 25
Static Methods and Static Variables .......................................................................................... 27
Parameters ................................................................................................................................. 29
Return Values ............................................................................................................................. 32

Objects and Classes ................................................................................................................ 34


Objects, Instance Variables, and Instance Methods .................................................................. 35
Constructors ............................................................................................................................... 39

1
Java client-server

Inheritance and Polymorphism .................................................................................................. 43


Interfaces, this and super, and visibility .................................................................................... 48
Interfaces .................................................................................................................................... 48
this and super............................................................................................................................. 50
Constructors in Subclasses ......................................................................................................... 52
Visibility ...................................................................................................................................... 53
Strings......................................................................................................................................... 53

Object-Oriented Programming ............................................................................................... 55


Object-oriented Analysis and Design ......................................................................................... 55
Generalized Software Components ........................................................................................... 56

Arrays ..................................................................................................................................... 57
Creating and Using Arrays .......................................................................................................... 57
Array Processing......................................................................................................................... 61
Partially Full Arrays .................................................................................................................... 63
Two-Dimensional Arrays ............................................................................................................ 64

Advanced I/O and Exceptions ................................................................................................. 68


Exceptions, try, and catch .......................................................................................................... 68
Streams ...................................................................................................................................... 73
Files ............................................................................................................................................ 81
File Names, Directories, and File Dialogs ................................................................................... 84

Networking ............................................................................................................................ 87
The URL Class ............................................................................................................................. 87
Sockets, Clients, and Servers ..................................................................................................... 90
Minimal TCP/IP Server .................................................................................................................. 93
Minimal TCP/IP Client .................................................................................................................. 94
A TVP/IP Client-Server model for robot programming .............................................................. 95

2
Java client-server

Java

 developed at Sun Microsystems, in 1991, by a team lead by James Gosling to be used for
consumer electronics (TV, VCR, freeze, washing machine, mobile phone …);
 the team created an updated version of C++ named Oak and demonstrated how the new
language could be used to control a list of home appliances using a hand held device;
 Oak was renamed to Java as it did not survive legal registration (at the time there was
already another programming language called Oak).

Java white paper defines Java as (http://www.stroustrup.com/1995_Java_whitepaper.pdf):

 Simple (the number of language constructs you need to understand to get your job done is
minimal) and Familiar (looks like C++)

 Safe as there are No:


o preprocessor (include, define), typedef
o global variables
o goto statements
o pointers
o fragile data types
o operator overloading
o unsafe structures
o multiple inheritance
o automatic coercions

 Object oriented: this programming paradigm assembles different pieces, parts or


components into an application. These parts are defined as objects, which perform specific
functions. The objects interact by sending and receiving messages. The programmer is
focused on how the actions of each of these objects are coordinated and how they
communicate with each other. The programmer does not have to know how a particular
object performs its function;

 Robust (as the Java run-time system manages memory for you);

 Architecture Neutral and Portable (applications are portable across multiple platforms. Onec
the applications are written you never need to port them—they will run without modification
on multiple operating systems and hardware architecture);

 High Performance and Threaded (The interactive graphical applications have high
performance because multiple concurrent threads of activity in your application are
supported by the multithreading built into Java environment);

 Dynamic (While the Java compiler is strict in its compile-time static checking, the language
and run-time system are dynamic in their linking stages. Classes are linked only as needed.
New code modules can be linked in on demand from a variety of sources, even from sources
across a network).

3
Java client-server Basic Java concepts

Java compared to another OO language (C, C++) by paradigm

 C > procedural and modular;


 C++ > procedural, modular, object oriented and generic;
 Java > object orientated and generic.

The two widely used paradigms are the procedural and the object oriented.

The procedural approach generally can be described by the following:


 Data structures can be represented as a network of associated structures referring to one
another.
 Procedures can be represented as a network of routines that call one another, as in a 'call
tree'.

The object-oriented approach generally can be described by the following:


 It is a collection of discrete objects that incorporate data structures and behavior.
 Each data structure has procedures that apply to that data structure.
 This approach contrasts with conventional programming, in which data structures and
behavior are only loosely connected.
 These entities, called objects, can be associated with one another in one network rather than
two.

Basic Java terminology


The following most commonly used Java concepts and terms will be utilized throughout this course:
 Class - A class is a template for an object. A class contains the attributes and behaviors of
the object it defines. Finding the right classification scheme is one of the keys to successful
object oriented analysis.
 Object - An object contains data and instructions for processing the data. An object is a
representation of something based on a class. Every object is an instance of a class and
provides some behavior (function) to the system.
 Attribute - Attributes describe the state of objects. Attributes values hold the object’s data,
this is why are also called data.
 Data Type - A data type describes what kind of information a certain attribute is.
 Behavior - Behaviors describe what operations can be done by an object. Behaviors are also
called methods.
 Method - A method implements an operation as a set of instructions that are executed by an
object.
 Inheritance - Inheritance is when some objects derive attributes and behaviors from other
objects.
 Encapsulation - Encapsulation is the process of combining data and methods together in
one class.
 Polymorfism -

4
Java client-server Basic Java concepts

5
Java client-server Basic Java concepts

Java Basics
This section is the first formal introduction to the elements of the java language. The success of
the programs depends on the proper use of language rules and the documentation of the
program's features.

Java has a rich set of keywords and symbols. Reserved words can be used to define certain types
of values such as true, false and null. The keyword new requests storage for an object.

Documentation benefits the programmer and other users of the class. The javadoc utility
interprets specific tags that are inserted into the source code to create HTML documents. These
types of comments are enclosed in /** */ documentation. The core API is available in HTML
format.

Documentation

There are two types of comments that can be included in a Java source file that defines a Java
class. Comments or remarks may be:
 single line > the comment is preceded with the symbols // or,
 multi-line in length > the symbol /* precedes the first line or is the first character on the
first line and ends with */ on the last line or after the last line .

Comments do not cause the computer to perform any tasks, that is, they are not executed when a
program runs.

Javadoc comments are used specifically for creating the HTML documents for the class. These
comments are also known as block comments because they can span more than one line. These
comments are used to explain the purpose of the class, what methods do, what the arguments
represent, and so on. Javadoc comments are enclosed within the symbols /** and */. As with
other comments, the compiler ignores these comments. While general comments can be placed
anywhere in a source file, comments used to generate HTML documents using the javadoc
utility have specific guidelines and symbols

Language elements

Every programming language has elements that make up the language. Almost all languages
have keywords, special symbols, names for data or operations, and syntax rules for their use.

Keywords
In the Java programming language, a keyword is one of 50 reserved words that have a
predefined meaning in the language; because of this, programmers cannot use keywords as
names for variables, methods, classes, or as any other identifier.

abstract continue for new switch


*** *
assert default goto package synchronized
boolean do if private this
break double implements protected throw

6
Java client-server Basic Java concepts

byte else import public throws


****
case enum instanceof return transient
catch extends int short try
char final interface static void
**
class finally long strictfp volatile
*
const float native super while

*
not used
**
added in 1.2
***
added in 1.4
****
added in 5.0
abstract
In front of a class keyword, prevents this class to be directly instantiated. In front of a method signature,
allows the implementation of this method to be deferred to an inheriting class.

assert
Assert describes a predicate (a true–false statement) placed in a java-program to indicate that the developer
thinks that the predicate is always true at that place. If an assertion evaluates to false at run-time, an assertion
failure results, which typically causes execution to abort. Optionally enable by ClassLoader method.

boolean
Defines a boolean variable for the values "true" or "false" only .

break
Used to end the execution in the current loop body.

byte
Defines a byte variable representing a sequence of 8 bits.

case
A statement in the switch block can be labeled with one or more case or default labels. The switch statement
evaluates its expression, then executes all statements that follow the matching case label; see switch.

catch
Used in conjunction with a try block and an optional finally block. The statements in the catch block specify
what to do if a specific type of exception is thrown by the try block.

char
Defines a character variable capable of holding any character of the java source file's character set.

class
A type that defines the implementation of a particular kind of object. A class definition defines instance and
class fields, methods, and inner classes as well as specifying the interfaces the class implements and the
immediate superclass of the class. If the superclass is not explicitly specified, the superclass is implicitly Object.
The class keyword can also be used in the form Class.class to get a Class object without needing an instance of
that class. For example, String.class can be used instead of doing new String().getClass().

const
Although reserved as a keyword in Java, const is not used and has no function. For defining constants in java,
see the 'final' reserved word.

continue
Used to resume program execution at the end of the current loop body. If followed by a label, continue
resumes execution at the end of the enclosing labeled loop body.

default
The default keyword can optionally be used in a switch statement to label a block of statements to be
executed if no case matches the specified value; see switch. Alternatively, the default keyword can also be used
to declare default values in a Java annotation. From Java 8 onwards, the default keyword is also used to specify
that a method in an interface provides the default implementation of an optional method.

do

7
Java client-server Basic Java concepts

The do keyword is used in conjunction with while to create a do-while loop, which executes a block of
statements associated with the loop and then tests a boolean expression associated with the while. If the
expression evaluates to true, the block is executed again; this continues until the expression evaluates to false.

double
The double keyword is used to declare a variable that can hold a 64-bit double precision IEEE 754 floating-point
number. This keyword is also used to declare that a method returns a value of the primitive type double.

else
The else keyword is used in conjunction with if to create an if-else statement, which tests a boolean
expression; if the expression evaluates to true, the block of statements associated with the if are evaluated; if it
evaluates to false, the block of statements associated with the else are evaluated.

enum (as of J2SE 5.0)


A Java keyword used to declare an enumerated type. Enumerations extend the base class Enum.

extends
Used in a class declaration to specify the superclass; used in an interface declaration to specify one or more
superinterfaces. Class X extends class Y to add functionality, either by adding fields or methods to class Y, or by
overriding methods of class Y. An interface Z extends one or more interfaces by adding methods. Class X is said to
be a subclass of class Y; Interface Z is said to be a subinterface of the interfaces it extends.
Also used to specify an upper bound on a type parameter in Generics.

final
Define an entity once that cannot be changed nor derived from later. More specifically: a final class cannot be
subclassed, a final method cannot be overridden, and a final variable can occur at most once as a left-hand
expression on an executed command. All methods in a final class are implicitly final.

finally
Used to define a block of statements for a block defined previously by the try keyword. The finally block is
executed after execution exits the try block and any associated catch clauses regardless of whether an exception
was thrown or caught, or execution left method in the middle of the try or catch blocks using the return keyword.

float
The float keyword is used to declare a variable that can hold a 32-bit single precision IEEE 754 floating-point
number. This keyword is also used to declare that a method returns a value of the primitive type float.

for
The for keyword is used to create a for loop, which specifies a variable initialization, a boolean expression, and
an incrementation. The variable initialization is performed first, and then the boolean expression is evaluated. If
the expression evaluates to true, the block of statements associated with the loop are executed, and then the
incrementation is performed. The boolean expression is then evaluated again; this continues until the expression
evaluates to false.

As of J2SE 5.0, the for keyword can also be used to create a so-called "enhanced for loop", which specifies an
array or Iterable object; each iteration of the loop executes the associated block of statements using a different
element in the array or Iterable.

goto
Although reserved as a keyword in Java, goto is not used and has no function.

if
The if keyword is used to create an if statement, which tests a boolean expression; if the expression evaluates
to true, the block of statements associated with the if statement is executed. This keyword can also be used to
create an if-else statement; see else.

implements
Included in a class declaration to specify one or more interfaces that are implemented by the current class. A
class inherits the types and abstract methods declared by the interfaces.

import
Used at the beginning of a source file to specify classes or entire Java packages to be referred to later without
including their package names in the reference. Since J2SE 5.0, import statements can import static members of
a class.

instanceof

8
Java client-server Basic Java concepts

A binary operator that takes an object reference as its first operand and a class or interface as its second
operand and produces a boolean result. The instanceof operator evaluates to true if and only if the runtime type
of the object is assignment compatible with the class or interface.

int
The int keyword is used to declare a variable that can hold a 32-bit signed two's complement integer. This
keyword is also used to declare that a method returns a value of the primitive type int.

interface
Used to declare a special type of class that only contains abstract methods, constant (static final) fields and
static interfaces. It can later be implemented by classes that declare the interface with the implements keyword.

long
The long keyword is used to declare a variable that can hold a 64-bit signed two's complement integer. This
keyword is also used to declare that a method returns a value of the primitive type long.

native
Used in method declarations to specify that the method is not implemented in the same Java source file, but
rather in another language.

new
Used to create an instance of a class or array object. Using keyword for this end is not completely necessary,
though it serves two purposes: it enables the existence of different namespace for methods and class names, it
defines statically and locally that a fresh object is indeed created, and of what runtime type it is (arguably
introducing dependency into the code).

package
A group of types. Packages are declared with the package keyword.

private
The private keyword is used in the declaration of a method, field, or inner class; private members can only be
accessed by other members of their own class.

protected
The protected keyword is used in the declaration of a method, field, or inner class; protected members can
only be accessed by members of their own class, that class's subclasses or classes from the same package.

public
The public keyword is used in the declaration of a class, method, or field; public classes, methods, and fields
can be accessed by the members of any class.

return
Used to finish the execution of a method. It can be followed by a value required by the method definition that
is returned to the caller.

short
The short keyword is used to declare a field that can hold a 16-bit signed two's complement integer. This
keyword is also used to declare that a method returns a value of the primitive type short.

static
Used to declare a field, method, or inner class as a class field. Classes maintain one copy of class fields
regardless of how many instances exist of that class. static also is used to define a method as a class method.
Class methods are bound to the class instead of to a specific instance, and can only operate on class fields.
(Classes and interfaces declared as static members of another class or interface are actually top-level classes and
are not inner classes.)

strictfp (as of J2SE 1.2)


A Java keyword used to restrict the precision and rounding of floating point calculations to ensure portability.

super
Used to access members of a class inherited by the class in which it appears. Allows a subclass to access
overridden methods and hidden members of its superclass. The super keyword is also used to forward a call from
a constructor to a constructor in the superclass.
Also used to specify a lower bound on a type parameter in Generics.

switch

9
Java client-server Basic Java concepts

The switch keyword is used in conjunction with case and default to create a switch statement, which evaluates
a variable, matches its value to a specific case, and executes the block of statements associated with that case.
If no case matches the value, the optional block labelled by default is executed, if included.

synchronized
Used in the declaration of a method or code block to acquire the mutex lock for an object while the current
thread executes the code. For static methods, the object locked is the class's Class. Guarantees that at most one
thread at a time operating on the same object executes that code. The mutex lock is automatically released
when execution exits the synchronized code. Fields, classes and interfaces cannot be declared as synchronized.

this
Used to represent an instance of the class in which it appears. this can be used to access class members and as
a reference to the current instance. The this keyword is also used to forward a call from one constructor in a
class to another constructor in the same class.

throw
Causes the declared exception instance to be thrown. This causes execution to continue with the first
enclosing exception handler declared by the catch keyword to handle an assignment compatible exception type.
If no such exception handler is found in the current method, then the method returns and the process is repeated
in the calling method. If no exception handler is found in any method call on the stack, then the exception is
passed to the thread's uncaught exception handler.

throws
Used in method declarations to specify which exceptions are not handled within the method but rather passed
to the next higher level of the program. All uncaught exceptions in a method that are not instances of
RuntimeException must be declared using the throws keyword.

transient
Declares that an instance field is not part of the default serialized form of an object. When an object is
serialized, only the values of its non-transient instance fields are included in the default serial representation.
When an object is deserialized, transient fields are initialized only to their default value. If the default form is
not used, e.g. when a serialPersistentFields table is declared in the class hierarchy, all transient keywords are
ignored.

try
Defines a block of statements that have exception handling. If an exception is thrown inside the try block, an
optional catch block can handle declared exception types. Also, an optional finally block can be declared that will
be executed when execution exits the try block and catch clauses, regardless of whether an exception is thrown
or not. A try block must have at least one catch clause or a finally block.

void
The void keyword is used to declare that a method does not return any value.[9]

volatile
Used in field declarations to specify that the variable is modified asynchronously by concurrently running
threads. Methods, classes and interfaces thus cannot be declared volatile, nor can local variables or parameters.

while
The while keyword is used to create a while loop, which tests a boolean expression and executes the block of
statements associated with the loop if the expression evaluates to true; this continues until the expression
evaluates to false. This keyword can also be used to create a do-while loop;

Identifiers
Identifiers are names that programmers assign to data or storage addresses. Since the compiler
and the JVM handle all the details of allocating memory, the Java programmer only needs to
provide a name or "handle" for accessing data in storage. Identifiers are also names that a
programmer assigns to classes and to methods.

10
Java client-server Data types, variables and operators

Java imposes some rules on creating identifiers:


 Any character from any alphabet can be used. Although characters used in the English
language are represented using the ASCII code, Java language provides
internationalization of code and data by using an extended set of coded symbols known
as Unicode. Unicode includes representations for the characters of almost every spoken
language in the world. For example characters from the Kanji (Japanese alphabet),
Arabic, and Greek alphabets can be used in identifiers.
 The first character must be a letter. The subsequent characters can be any alphanumeric
character or punctuation.
 Identifiers cannot contain the symbols % (percent) or # (number symbol). They may
contain $ (dollar sign) and an underscore.
 Generally, it is recommended that you not use special symbols such as $, &, and so on.
 Identifiers cannot contain spaces.
 Identifiers are case sensitive.
 Identifiers cannot use certain keywords, known as reserved words.

Special symbols
Symbols are necessary for proper Java code compilation. Following are the most commonly
used symbols in Java:
 Braces > { }- A block is a collection of statements that is bounded by braces { }. These
include braces to bound class definitions, method definitions, and other statements that
should be executed in a group. Braces that are used to define a class or a method should
generally be placed below the definition statement. The placement of braces in the
alignment of opening and closing braces makes it easier to find errors and debug
programs. Some editors, like BlueJ, include search features to search for matching
braces.
 Semicolons > ; - A statement consists of one or more lines of code that is terminated, or
ended, by a semicolon . Omitting semicolons is a common compiler error. Before
compiling a program, always check that every line of code has a semicolon.
 Commas > , - Commas serve as separators or delimiters of data. Methods that require a
list of values, require that each value be separated by a comma. The method declaration
defines values required for a method as one or more list pairs of data type and variable.
In general, if the syntax of a language identifies a comma in the description, the
programmer must also use the comma.
 White space > space, \t, \n, \r - White space separates keywords and identifiers, and
should be included when printing or displaying information:

When Java code is being compiled, Java ignores whites pace such as spaces, tabs, and carriage
returns. White space, particularly in the form of tabs, is used primarily to make programs more
readable for programmers.

Data types

A data type is a classification of a particular form of information. It tells the computer how to
interpret and store the data. While a person can tell if data is in the form of numbers or text just
by looking at it, a computer must use special codes to keep track of the different types of data it
processes. Data types are declared with the use of keywords in a language. These keywords

11
Java client-server Data types, variables and operators

inform the computer about the data type. Unlike other programming languages that require that
data be identified by type and amount of storage required, Java only requires that the type be
declared.

Java is a strongly typed language. This means that each data type has a defined amount of
storage allocated. The compiler knows exactly how much memory is needed for each of these
data types. Data can be converted from the codification of one data type to another (type
conversion) under specific conditions.

Basic Java Types


Java identifiers that are storing data are designed to hold only one particular type of data; it can
legally hold that type of data and no other. The compiler will consider it to be a syntax error if
you try to violate this rule. We say that Java is a strongly typed language because it enforces this
rule. The so-called basic or primitive types built into Java and are:
 Logical – boolean
 Textual – char and string
 Integral – byte, short, int, and long
 Floating Point – float and double

Any data value stored in the computer's memory must be represented as a binary number, that is
as a string of zeros and ones. A single zero or one is called a bit. A string of eight bits is called a
byte. Memory is usually measured in terms of bytes. Not surprisingly, the byte data type refers
to a single byte of memory. A variable of type byte holds a string of eight bits, which can
represent any of the integers between -128 and 127, inclusive.

The four integer types are distinguished by the ranges of integers they can hold. The float and
double types hold real numbers (such as 3.6 and -145.99). Again, the two real types are
distinguished by their range and accuracy. A variable of type char holds a single character from
the Unicode character set. A variable of type boolean holds one of the two logical values true
or false.

Variables, Declarations, and Assignments


A variable is an identifier or name used by the programmers to change or check data stored at
some memory address. A variable can only be used in a program if it has been declared. A
variable declaration statement is used to create one or more variables and to give them names.
When the computer executes a variable declaration, it sets aside memory for the variable and
associates the variable's name with that memory. A variable declaration can take the form:

type-name variable-name;

or

type-name variable-name = expression;

In the second form, the expression is used as an initial value for the new variable. The second
form is usually preferable, since it makes sure that the variable starts out with a definite, known
value. You can create several variables in the same declaration, if you separate them by
commas.

12
Java client-server Data types, variables and operators

One way to get data into a variable -- that is, change the value in the variable -- is with an
assignment statement. An assignment statement takes the form:

variable = expression;

where expression means anything that computes a data value. When the computer comes to an
assignment statement in the course of executing a program, it evaluates the expression and puts
the resulting data value into the variable.

Java Reference Types


As you have seen, there are eight primitive Java types: boolean, char, byte, short, int, long, float,
and double. All other types refer to objects rather than primitives. Variables that refer to objects
are reference variables.

Operators and expressions

An expression computes a value. The value can, for example, be assigned to a variable, used as
the output value in an output routine, or combined with other values into a more complicated
expression. The basic building blocks of expressions are literals (such as 674, 3.14, true, and
'X'), variables, and function calls (as these return values). Java also has some built in
mathematical functions for computing things like square roots and logarithms. The
mathematical functions are part of a class named "Math", and so they have compound names
beginning with "Math.", such as Math.sqrt for the square root function. For example, to
compute the square root of x and assign the answer to y, you would write Math.sqrt1(x). Here
is a list of the most important mathematical functions defined in the Math class:

 Math.abs(x) computes the absolute value of x.


 The usual trigonometric functions, Math.sin(x), Math.cos(x), and Math.tan(x).
 The inverse trigonometric functions arcsin, arccos, and arctan, which are written as:
Math.asin(x), Math.acos(x), and Math.atan(x).
 The exponential function Math.exp(x) for computing the number e raised to the power
x, and the natural logarithm function Math.log(x) for computing the logarithm of x in
the base e.
 Math.pow(x,y) for computing x raised to the power y.
 Math.round(x), which rounds x to the nearest integer. (For example,
Math.round(3.76) is 4.)
 Math.random(), which returns a randomly chosen double in the range 0.0 <=
Math.random() < 1.0. (The computer actually calculates pseudorandom numbers, but
they are random enough for most purposes.)

For most of these functions, the type of the argument -- the value inside parentheses -- must be
double. In Math.abs(x), the argument x can be of any numeric type. Note that Math.random()
does not have any argument. (You still need the parentheses, even though there's nothing
between them. The parentheses let the computer know that this is a subroutine rather than a
variable.)

Literals, variables, and function calls are simple expressions. More complex expressions can
be built up by using operators to combine simpler expressions. Operators include + for adding

13
Java client-server Data types, variables and operators

two numbers, < for comparing two values, and so on. When several operators appear in an
expression, there is a question of precedence, which determines how the operators are grouped
for evaluation. For example, in the expression "X + Y * Z", Y*Z is computed first and then the
result is added to X. We say that multiplication (*) has higher precedence than addition (+). If
the default precedence is not what you want, you can use parentheses to explicitly specify the
grouping you want. For example, you could use "(X + Y) * Z" if you want to add X to Y and
then multiply the result by Z.

Arithmetic Operators
Arithmetic operators include addition, subtraction, multiplication, and division. They are
indicated by +, -, *, and /. These operations can be used on values of any numeric type: byte,
short, int, long, float, or double. When the computer actually calculates one of these
operations, the two values that it combines must be of the same type. If your program tells the
computer to combine two values of different types, the computer will convert one of the values
from one type to another. For example, to compute 37.4 + 10, the computer will convert the
integer 10 to a real number 10.0 and will then compute 37.4 + 10.0. (The computer's internal
representations for 10 and 10.0 are very different, even though people think of them as
representing the same number.) Ordinarily, you don't have to worry about type conversion,
because the computer does it automatically.

When two numerical values are combined (after doing type conversion on one of them, if
necessary), the answer will be of the same type. If you multiply two ints, you get an int; if
you multiply two doubles, you get a double. This is what you would expect, but you have to be
very careful when you use the division operator /. When you divide two integers, the answer
will always be an integer; if the quotient has a fractional part, it is discarded. For example, the
value of 7/2 is 3, not 3.5. If N is an integer variable, then N/100 is an integer, and 1/N is always
equal to zero! This fact is a common source of programming errors. You can force the computer
to compute a real number as the answer by making one of the operands real: To compute 1.0/N,
for example, the computer converts N to a real number in order to match the type of 1.0, so you
get a real number as the answer.

Java also has an operator for computing the remainder when one integer is divided by another.
This operator is indicated by %. If A and B are integers, then A % B represents the remainder
when A is divided by B. For example, 7 % 2 is 1, 34577 % 100 is 77, and 50 % 8 is 2. In the
previous section, I used the expression N % 2 to compute the remainder when N is divided by 2.
This remainder will be 0 if N is even and will be 1 if N is odd.
Finally, you might need the unary minus operator, which takes the negative of a number. For
example, -X has the same value as (-1)*X. For completeness, Java also has a unary plus
operator, as in +X, even though it doesn't really do anything.

Increment and Decrement


You'll find that adding 1 to a variable is an extremely common operation in programming.
Subtracting 1 from a variable is also pretty common. In the case we have statements like:

a = a + 1;
counter = counter - 1;

14
Java client-server Data types, variables and operators

The operation of adding 1 to a variable named x can be accomplished by writing x++ (or, if you
prefer, ++x). This actually changes the value of x, so that it has the same effect as writing "x = x
+ 1". The two statements above could be written:

a++;
counter--;

Similarly, you could write x-- (or --x) to subtract 1 from x. Adding 1 to a variable is called
incrementing that variable, and subtracting 1 is called decrementing. The operators ++ and -- are
called the increment operator and the decrement operator, respectively.
Usually, when you use ++ or --, you write statements like "x++;" or "x--;". These statements are
commands to change the value of x. However, it is also legal to use x++, ++x, x--, or --x as
expressions, or as parts of larger expressions. That is, you can write things like:

y = x++;
y = ++x;
System.out.println(--x);
z = (++x) * (y--);

The statement "y = x++;" has the effects of adding 1 to the value of x and, in addition,
assigning some value to y. The value assigned to y is the value of the expression x++, which is
defined to be the old value of x, before the 1 is added. Thus, if the value of x is 6, the statement
"y = x++;" will change the value of x to 7 and will change the value of y to 6. On the other
hand, the value of ++x is defined to be the new value of x, after the 1 is added. So if x is 6, then
the statement "y = ++x;" changes the values of both x and y to 7. The decrement operator, --,
works in a similar way.

Relational Operators
Java has boolean variables and boolean-valued expressions that can be used to express
conditions that can be either true or false. One way to form a boolean-valued expression is to
compare two values using a relational operator. Relational operators are used to test whether
two values are equal, whether one value is greater than another, and so forth. The relation
operators in Java are: ==, !=, <, >, <=, and >=. The meanings of these operators are:
A == B Is A "equal to" B?
A != B Is A "not equal to" B?
A < B Is A "less than" B?
A > B Is A "greater than" B?
A <= B Is A "greater than or equal to" B?
A >= B Is A "less than or equal to" B?

These operators can be used to compare values of any of the numeric types. They can also be
used to compare values of type char. For characters, < and > are defined according the numeric
Unicode value of the characters. (This might not always be what you want.)

When using boolean expressions, you should remember that as far as the computer is concerned,
there is nothing special about boolean values. You can use boolean expressions in while
statements and if statements, but you can also assign them to boolean variables, just as you can
assign numeric values to numeric variables. A boolean variable can itself be used as a boolean
expression. By the way, the operators == and != can be used to compare boolean values. This is
occasionally useful.

15
Java client-server Data types, variables and operators

Boolean Operators
In English, complicated conditions can be formed using the words "and", "or", and "not." For
example, "If there is a test and you did not study for it...". "And", "or", and "not" are boolean
operators, and they exist in Java as well as in English.

In Java, the boolean operator "and" is represented by &&. The && operator is used to combine
two boolean values. The result is a boolean value that is true if both of the combined values
are true and is false if either of the combined values is false. For example, "(x == 0) && (y
== 0)" is true if and only if both x is equal to 0 and y is equal to 0.
The boolean operator "or" is represented by ||. "A || B" is true if either A is true or B is true,
or if both are true. "A || B" is false only if both A and B are false.

The operators && and || are said to be short-circuited versions of the boolean operators. This
means that the second operand of && or || is not necessarily evaluated. Consider the test
if ( (A != 0) && (B/A > 1) )

Suppose that the value of A is in fact zero. In that case, the division B/A is illegal, since division
by zero is not allowed. However, the computer will never perform the division, since when the
computer evaluates (A != 0), it finds that the result is false, and so it knows that (A != 0) &&
anything has to be false. Therefore, it doesn't bother to evaluate the second operand, (B/A > 1).
The evaluation has been short-circuited and the division by zero is avoided. Without the short-
circuiting, there would have been a division-by-zero error. (This may seem like a technicality,
and it is. But at times, it will make your programming life a little easier. To be even more
technical: There are actually non-short-circuited versions of && and ||, which are written as &
and |. Don't use them unless you have a particular reason to do so.)

The boolean operator "not" is a unary operator. In Java, it is indicated by ! and is written in front
of its single operand. For example, if test is a boolean variable, then

test = !test;

will reverse the value of test, changing it from true to false, or from false to true. You
could use

if ( !( (A == 0) && (B == 0) ) )

to test whether "it is not the case that both A equals 0 and B equals 0."

Conditional Operator
Any good programming language has some nifty little features that aren't really necessary but
that let you feel cool when you use them. Java has the conditional operator. It's a ternary
operator -- that is, it has three operands -- and it comes in two pieces, ? and :, that have to be
used together. It takes the form:

boolean-expression ? expression-1 : expression-2

The computer tests the value of boolean-expression. If the value is true, it evaluates
expression-1; otherwise, it evaluates expression-2.

16
Java client-server Data types, variables and operators

Assignment Operators
You are already familiar with the assignment statement, which uses the operator = to assign the
value of an expression to a variable. In fact, = is really an operator, and an assignment can itself
be used as an expression or as part of a more complex expression. The value of an assignment
such as A=B is the same as the value that is assigned to A. So, if you want to assign the value of
B to A and test at the same time whether that value is zero, you could say:
if ( (A=B) == 0 )

Usually, I would say, don't do things like this.


In general, the type of the expression on the right-hand side of an assignment statement must be
the same as the type of the variable on the left-hand side. However, in some cases, the computer
will automatically convert the type of value computed by the expression to match the type of the
variable. Consider the list of numeric types: byte, short, int, long, float, double. A value of
a type that occurs earlier in this list can be converted automatically to a value that occurs later.
For example:

int A = 17;
double X = A; // OK; A is converted to a double
short B = A; // illegal; no automatic conversion
// from int to short

The idea is that conversion should only be done automatically when it can be done without
changing the semantics of the value. Any int can be converted to a double with the same
numeric value. However, there are int values that lie outside the legal range of shorts. There is
simply no way to represent the int 100000 as a short, for example.
In some cases, you might want to force a conversion that wouldn't be done automatically. For
this, you could use what is called a type cast. A type cast is indicated by putting a type name, in
parentheses, in front of the value you want to convert. For example,

int A = 17;
short B = (short)A; // OK; A is explicitly type cast
// to a value of type short

You can do type casts from any numeric type to any other numeric type. However, you should
note that you might change the numeric value of a number by type casting it. For example,
(short)100000 is 34464. (The 34464 is obtained by taking the 4-byte int 100000 and throwing
away two of those bytes to obtain a short -- you've lost the real information that was in those
two bytes.)

You can also type cast between type char and the numeric types. The numeric value of a char
is its Unicode code number. For example, (char)97 is 'a', and (int)'+' is 43.

Java has several variations on the assignment operator, which exist to save typing. For example,
"A += B" is defined to be the same as "A = A + B". Operators -=, *=, /=, and %= are defined in
a similar way.

Precedence Rules

If you use several operators in one expression, and if you don't use parentheses to explicitly
indicate the order of evaluation, then you have to worry about the precedence rules that
determine the order of evaluation.

17
Java client-server Statements

Here is a listing of the operators discussed in this section, listed in order from highest
precedence (evaluated first) to lowest precedence (evaluated last):

Unary operators: ++, --, !, unary - and +, type cast


Multiplication and division: *, /, %
Addition and subtraction: +, -
Relational operators: <, >, <=, >=
Equality and inequality: ==, !=
Boolean and: &&
Boolean or: ||
Conditional operator: ?:
Assignment operators: = += -= *= /= %=

Operators on the same line have the same precedence. When they occur together, unary
operators and assignment operators are evaluated right-to-left, and the remaining operators are
evaluated left-to-right. For example, A*B/C means (A*B)/C, and A=B=C means A=(B=C).

Control statements

JAVA HAS THREE CONTROL STRUCTURES for doing loops: the while loop, the do loop,
and the for loop. It has two control structures for branching: the if statement and the switch
statement. This section covers the details of these statements.

The do Loop

You've already seen the while statement, in which the computer tests a condition at the
beginning of the loop, to determine whether it should continue looping:

while ( boolean-expression )
statement

The do loop is a variation of this in which the test comes at the end. It takes the form:

do
statement
while ( boolean-expression );

or, since as usual the statement can be a block,

do {
statements
} while ( boolean-expression );

(Note the semicolon, ';', at the end. This semicolon is part of the statement, just as the semicolon
at the end of an assignment statement or declaration is part of the statement. More generally,
every statement in Java ends either with a semicolon or a right brace, '}'.)

18
Java client-server Statements

To execute a do loop, the computer first executes the body of the loop -- that is, the statement or
statements inside the loop -- and then evaluates the boolean expression. If the value of the
expression is true, the computer returns to the beginning of the do loop and repeats the process;
if the value is false, it ends the loop and continues with the next part of the program.

The main difference between the do loop and the while loop is that the body of a do loop is
executed at least once, before the boolean expression is ever evaluated. In a while loop, if the
boolean expression is false when the computer first checks it, then the body of the loop will
never be executed at all.

The for Loop

The for loop exists to make a common type of while loop easier to write. Many while loops have
the same general form:

initialization
while ( continuation-condition ) {
statements
update
}

The initialization, continuation condition, and updating have all been combined in the first line
of the for loop. This keeps everything involved in the "control" of the loop in one place, which
helps makes the loop easier to read and understand. In general, a for loop takes the form:

for ( initialization; continuation-condition; update )


statement

or, using a block statement,:

for ( initialization; continuation-condition; update ) {


statement
}

The continuation-condition must be a boolean-valued expression. The initialization can be


any expression, as can the update. Any of the three can be empty. Usually, the initialization is
an assignment or a declaration, and the update is an assignment or an increment or decrement
operation. The official syntax actually allows the initialization and the update to consist of
several expressions, separated by commas. If you declare a variable in the initialization section,
the scope of that variable is limited to the for statement; that is, it is no longer valid after the for
statement is ended.

Here are a few examples of for statements:

// print out the alphabet on one line of output


for ( char ch='a'; ch <= 'z'; ch++)
System.out.print(ch);
System.out.println ();

// count up to 10 and down from 10 at the same time


for ( int i=0,j=10; i < 10; i++,j-- )
System.out.printf(”%5i, %i”,i,j); // output i,j in a
// 5-character wide column

// compute the number 1 * 2 * 3 * ... * 15

19
Java client-server Statements

long factorial = 1;
for (int num=2; num <= 15; num++)
factorial *= num;
System.out.println("20! = " + factorial);

The break Statement

The syntax of the while, do, and for loops allows you to make a test at either the beginning or at
the end of the loop to determine whether or not to continue executing the loop. Sometimes, it is
more natural to have the test in the middle of the loop, or to have several tests at different places
in the same loop. Java provides a general method for breaking out of the middle of any loop. It's
called the break statement, which takes the form

break;

When the computer executes a break statement, it will immediately jump out of the loop (or
other control structure) that contains the break. It then continues on to whatever follows the loop
in the program. Consider for example:

while (true) { // looks like it will run forever!


System.out.println("Enter a positive number: ");
N = console.getlnt();
if (N > 0) // input is OK; jump out of loop
break;
console.putln("Your answer must be > 0.");
}
// continue here after break

A break statement terminates the loop that immediately encloses the break statement. It is
possible to have nested loops, where one loop statement is contained inside another. If you use a
break statement inside a nested loop, it will only break out of that loop, not out of the loop that
contains the nested loop. There is something called a "labeled break" statement that allows you
to specify which loop you want to break. I won't give the details here; you can look them up if
you ever need them.

The if Statement

An if statement tells the computer to take one of two alternative courses of action, depending on
whether the value of a given boolean-valued expression is true or false. It is an example of a
"branching" or "decision" statement. It takes the form:

if (boolean-expression)
statement-1
else
statement-2

As usual, the statements inside an if statements are often blocks. The if statement represents a
two-way branch. The else part of an if statement -- consisting of the word "else" and the
statement that follows it -- can be omitted.

20
Java client-server Statements

Now, an if statement is, in particular, a statement. This means that either statement-1 or
statement-2 inside an if statement can itself be an if statement. (Note: If statement-1 is an if
statement, then it has to have an else part; if it does not, the computer will mistake the "else" of
the main if statement for the missing "else" of statement-1. This is called the dangling else
problem. You can avoid this problem by enclosing statement-1 between { and }, making it into
a block.)

In many cases, you want the computer to choose between doing something and not doing it. You
can do this with an if statement that omits the else part:

if ( boolean-expression )
statement

To execute this statement, the computer evaluates the expression. If the value is true, the
computer executes the statement that is contained inside the if statement; if the value is false,
the computer skips that statement.

As usual, each of the statement's in an if statement can be a block, so that an if statement often
looks like:

if ( boolean-expression ) {
statements
}
else {
statements
}

or:

if ( boolean-expression ) {
statements
}

An if statement in which the else part is itself an if statement would look like this (perhaps
without the final else part):

if (boolean-expression-1)
statement-1
else
if (boolean-expression-2)
statement-2
else
statement-3

However, since the computer doesn't care how a program is laid out on the page, this is usually
written in the format:

if (boolean-expression-1)
statement-1
else if (boolean-expression-2)
statement-2
else
statement-3

21
Java client-server Statements

You should think of this as a single statement representing a three-way branch. When the
computer executes this, one and only one of the three statements, statement-1, statement-2, and
statement-3, will be executed. The computer starts by evaluating boolean-expression-1. If it is
true, the computer executes statement-1 and then jumps all the way to the end of the big if
statement, skipping the other two statement's. If boolean-expression-1 is false, the computer
skips statement-1 and executes the second, nested if statement. That is, it tests the value of
boolean-expression-2 and uses it to decide between statement-2 and statement-3.

Here is an example that will print out one of three different messages, depending on the value of
a variable named temperature:

if (temperature < 10)


System.out.println("It's cold.");
else if (temperature < 27)
System.out.println("It's nice.");
else
System.out.println("It's hot.");

If temperature is, say, 7, the computer prints out the message "It's cold", and skips the rest --
without even evaluating the second condition.

You can go on stringing together "else-if's" to make multiway branches with any number of
cases:

if (boolean-expression-1)
statement-1
else if (boolean-expression-2)
statement-2
else if (boolean-expression-3)
statement-3
.
. // (more cases)
.
else if (boolean-expression-N)
statement-N
else
statement-(N+1)

You should just remember that only one of the statements will be executed and that the
computer will stop evaluating boolean-expressions as soon as it finds one that is true. Also,
remember that the final else part can be omitted and that any of the statements can be blocks,
consisting of a number of statements enclosed between { and }. (Admittedly, there is lot of
syntax here; as you study and practice, you'll become comfortable with it.)

The switch Statement

Java also provides a control structure that is specifically designed to make multiway branches of
a certain type: the switch statement. A switch statement allows you to test the value of an
expression and, depending on that value, to jump to some location within the switch statement.
The positions you can jump to are marked with "case labels" that take the form: "case
constant:". This marks the position the computer jumps to when the expression evaluates to the
given constant. As the final case in a switch statement you can, optionally, use the label

22
Java client-server Statements

"default:", which provides a default jump point that is used when the value of the expression is
not listed in any case label.

A switch statement has the form:

switch (integer-expression) {
case integer-constant-1:
statements-1
break;
case integer-constant-2:
statements-2
break;
.
. // (more cases)
.
case integer-constant-N:
statements-N
break;
default: // optional default case
statements-(N+1)
} // end of switch statement

The break statements are technically optional. The effect of a break is to make the computer
jump to the end of the switch statement. If you leave out the break statement, the computer will
just forge ahead after completing one case and will execute the statements associated with the
next case label. This is rarely what you want, but it is legal. (I will note here -- although you
won't understand it until you get to the next chapter -- that inside a subroutine, the break
statement is sometimes replaced by a return statement.)

Note that you can leave out one of the groups of statements entirely (including the break). You
then have two case labels in a row, containing two different constants. This just means that the
computer will jump to the same place and perform the same action for each of the two constants.

Here is an example of a switch statement. This is not a useful example, but it should be easy for
you to follow. Note, by the way, that the constants in the case labels don't have to be in any
particular order, as long as they are all different:

switch (N) { // assume N is an integer variable


case 1:
System.out.println("The number is 1.");
break;
case 2:
case 4:
case 8:
System.out.println("The number is 2, 4, or 8.");
System.out.println("(That's a power of 2.)");
break;
case 3:
case 6:
case 9:
System.out.println("The number is 3, 6, or 9.");
System.out.println("(That's a multiple of 3.)");
break;
case 5:
System.out.println("The number is 5.");
break;
default:
System.out.println("The number is 7,");

23
Java client-server Statements

System.out.println(" or is outside the range 1 to 9.");


}

The switch statement is pretty primitive as control structures go, and it's easy to make mistakes
when you use it. Java takes all its control structures directly from the older programming
languages C and C++. The switch statement is certainly one place where the designers of Java
should have introduced some improvements.

The Empty Statement

This is a statement that consists simply of a semicolon. The existence of the empty statement
makes the following legal, even though you would not ordinarily see a semicolon after a }.

if (x < 0) {
x = -x;
};

The semicolon is legal after the }, but the computer considers it to be an empty statement.
Occasionally, you might find yourself using the empty statement when what you mean is, in
fact, "do nothing". I prefer, though, to use an empty block, consisting of { and } with nothing
between, for such cases.

I mention the empty statement here mainly for completeness. You've now seen every type of
Java statement. A complete list is given below for reference. The only surprise here is what I've
listed as "other expression statement," which reflects the fact that a statement can consist of an
expression followed by a semicolon. To execute such a statement, the computer simply
evaluates the expression, and then ignores the value. Of course, this only makes sense the
evaluation has a side effect that makes some change in the state of the computer. An example of
this is the statement "x++;", which has the side effect of adding 1 to the value of x. Note that,
technically, assignment statements and subroutine call statements are also considered to be
expression statements.

 declaration statement
 assignment statement
 subroutine call statement (including input/output routines)
 other expression statement (such as "x++;")
 empty statement
 block statement
 while statement
 do...while statement
 if statement
 for statement
 switch statement

24
Java client-server Statements

Subroutines

ONE WAY TO BREAK UP A COMPLEX PROGRAM into manageable pieces is to use


subroutines. A subroutine consists of the instructions for carrying out a certain task, grouped
together and given a name. Elsewhere in the program, that name can be used as a stand-in for
the whole set of instructions. That is, as a computer executes a program, whenever it encounters
a subroutine name, it executes all the instructions necessary to carry out the task associated with
that subroutine.

Subroutines can be used over and over, at different places in the program. A subroutine can even
be used inside another subroutine. This allows you to write simple subroutines and then use
them to help write more complex subroutines, which can then be used in turn in other
subroutines. In this way, very complex programs can be built up step-by-step, where each step
in the construction is reasonably simple.

In Java, any subroutine you write has to be part of a class or object. Such subroutines are called
methods. A static method is part of a class, rather than part of the objects defined from that
class. Static methods correspond directly to subroutines in traditional, non-object oriented
programming languages. This chapter deals with the more traditional type of subroutines.
Methods that belong to objects, the so-called instance methods, will be covered in the next
chapter.

SUBROUTINE as the Black Box

A SUBROUTINE CONSISTS OF INSTRUCTIONS for performing some task, chunked


together and given a name. "Chunking" allows you to deal with a potentially very complicated
task as a single concept. Instead of worrying about the many, many steps that the computer
might have to go though to perform that task, you just need to remember the name of the
subroutine. Whenever you want your program to perform the task, you just call the subroutine.
Subroutines are a major tool for dealing with complexity.

25
Java client-server Subroutines

A subroutine is sometimes said to be a "black box" because you can't see what's "inside" it (or,
to be more precise, you usually don't want to see inside it, because then you would have to deal
with all the complexity that the subroutine is meant to hide). Of course, a black box that has no
way of interacting with the rest of the world would be pretty useless. A black box needs some
kind of interface with the rest of the world, which allows some interaction between what's inside
the box and what's outside. A physical black box might have buttons on the outside that you can
push, dials that you can set, and slots that can be used for passing information back and forth.
Since we are trying to hide complexity, not create it, we have the first rule of black boxes:

The interface of a black box should be fairly straightforward, well-defined, and easy to
understand.

Are there any examples of black boxes in the real world? Yes; in fact, you are surrounded by
them. Your television, your car, your VCR, your refrigerator... You can turn your television on
and off, change channels, and set the volume by using elements of the television's interface --
dials, remote control, don't forget to plug in the power -- without understand anything about how
the thing actually works. The same goes for a VCR, although if stories about how hard people
find it to set the time on a VCR are true, maybe the VCR violates the simple interface rule.

Now, a black box does have an inside -- the code in a subroutine that actually performs the task,
all the electronics inside your television set. The inside of a black box is called its
implementation. The second rule of black boxes is that

To use a black box, you shouldn't need to know anything about its implementation; all you
need to know is its interface.

In fact, it should be possible to change the implementation, as long as the behavior of the box,
as seen from the outside, remains unchanged. It should be possible to rewrite the inside of a
subroutine, to use more efficient code for example, without affecting the programs that use that
subroutine.

Of course, to have a black box, someone must have designed and built the implementation in the
first place. The black box idea works to the advantage of the implementor as well as of the user
of the black box. After all, the black box might be used in an unlimited number of different
situations. The implementor of the black box doesn't need to know about any of that. The
implementor just needs to make sure that the box performs its assigned task and interfaces
correctly with the rest of the world. This is the third rule of black boxes:

The implementor of a black box should not need to know anything about the larger
systems in which the box will be used.

In a way, a black box divides the world into two parts: the inside (implementation) and the
outside. The interface is at the boundary, connecting those two parts.

26
Java client-server Subroutines

Static Methods and Static Variables

EVERY SUBROUTINE IN JAVA MUST BE DEFINED inside some class. This makes Java
rather unusual among programming languages, since most languages allow free-floating,
independent subroutines. One purpose of a class is to group together related subroutines and
variables. Perhaps the designers of Java felt that everything must be related to something. As a
less philosophical motivation, Java's designers wanted to place firm controls on the ways things
are named, since a Java program potentially has access to a huge number of subroutines
scattered all over the Internet. The fact that those subroutines are grouped into named classes
(and, as we shall see later, classes are grouped into named "packages") helps control the
confusion that might result from so many different names.

A subroutine that is a member of a class is often called a method, and "method" is the term that
is preferred for subroutines in Java. I will start using the term "method"; however, I will also
continue to use the term "subroutine" because it is a more general term that applies to all
programming languages.

In Java, a method can be either static or non-static. Non-static methods are used in object-
oriented programming, which will be covered in the next chapter. For the rest of this chapter, we
will deal only with static methods. A static method plays the same role in Java that ordinary
subroutines play in more traditional, non-object-oriented programming.

A method definition in Java takes the form:

modifiers return-type method-name ( parameter-list ) {


statements
}

The statements between the braces, { and }, are the inside of the "black box". They are the
instructions that the computer executes when the method is called. Subroutines can contain any
of the statements as discussed previously. You've already seen some examples of methods,
namely the main() subroutines of the sample programs you've looked at. Here are some more
examples, with the "insides" omitted (but remember that definitions like these can only occur
inside classes):

static void functionCompute() {


// "static" is a modifier; "void" is the return-type;
// " functionCompute " is the method-name; the parameter-list
// is empty
. . . // statements that define what method does go here
}

int readInt(int i) {
// there are no modifiers; "int" in the return-type
// " readInt " is the method-name; the parameter-list includes
// one parameter whose name is "i" and whose type is "int"
. . . // statements that define what method does go here
}

public static boolean lessThan(double x, double y) {


// "public" and "static" are modifiers; "boolean" is the return-type;
// "lessThan" is the method-name; the parameter-list includes
// two parameters whose names are "x" and "y", and the type
// of each of these parameters is "double"
. . . // statements that define what method does go here

27
Java client-server Subroutines

The modifiers are words that set certain characteristics of the method, such as whether it is
static or not. In the second example given here, getNextN is a non-static method, since its
definition does not include the modifier "static" -- and so its not an example that we should be
looking at in this chapter! The other modifier shown in the examples is "public". This modifier
indicates that the method can be called from anywhere in a program, even from outside the class
where the method is defined. Another modifier, "private", indicates that the method can be
called only from inside the same class. The modifiers public and private are called access
specifiers. (If no access specifier is given for a method, then by default, that method can be
called from anywhere in the "package" that contains the class). There are a few other modifiers
that we'll run across as the course proceeds.

(By the way, you might be wondering why access specifiers should exist at all. Why not just let
everything be public? Well, think of the whole class itself as a kind of black box. The public
methods are part of the interface of that black box, while private methods are part of the hidden
implementation inside the box. The Rules of Black Boxes imply that you should only declare a
method to be public if it is really meant to be part of the interface that is visible from outside the
box. Do you see why this is true?)

Some subroutines are designed to compute and return a value. Such subroutines are called
functions. You've already seen examples of mathematical functions such as Math.sqrt(x) and
Math.random(). If you write a method that is meant to be used as a function, then you have to
specify the return-type of the method to be the type of value that is computed by your function.
For example, the return-type of boolean in

public static boolean lessThan(double x, double y)

specifies that lessThan is a function that computes a boolean value.

A method that is meant to be used as an ordinary subroutine, rather than one that returns a value,
must specify "void" as its return-type. The term void is meant to indicate that the return value is
empty or non-existent.

Finally, we come to the parameter-list of the method. Parameters are part of the interface of a
subroutine. They represent information that is passed into the subroutine from outside, to be
used by the subroutine's internal computations. For a concrete example, imagine a class named
Television that includes a method named changeChannel(). The immediate question is: What
channel should it change to? A parameter can be used to answer this question. Since the channel
number is an integer, the type of the parameter would be int, and the declaration of the
changeChannel() method might look like

public void changeChannel(int channelNum) {...}

This declaration just indicates that changeChannel() has a parameter of type int. When the
subroutine is called, an actual channel number must be provided; for example:
changeChannel(17);

When you define a subroutine, all you are doing is telling the computer that the subroutine exists
and what it does. The subroutine doesn't actually get executed until it is called. (This is true even

28
Java client-server Subroutines

for the main() method in a class -- even though you don't call it, it is called by the system when
the system runs your program.) For example, the solve() method defined above could be called
using the following subroutine call statement:

solve();

This statement could occur anywhere in the same class that includes the definition of solve(),
whether in a main() method or in some other method. Since solve() is a public method, it can
also be called from other classes, but in that case, you have to tell the computer which class it
comes from. Let's say, for example, that solve() is defined in a class named Bisect. Then to
call solve() from outside that class, you would have to say:

Bisect.solve();

The use of the class name here tells the computer which class to look in to find the method. It
also lets you distinguish between Bisect.solve() and other potential Bsolve() methods
defined in other classes, such as Newton.solve() or Iteration.solve().

More generally, a subroutine call statement takes the form

method-name(parameters);

if the method that is being called is in the same class, or

class-name.method-name(parameters);

if the method is a static method defined elsewhere, in a different class. (Non-static methods
defined in other classes are different.)

Variables can be declared in a class outside of any subroutine. Such variables can be either
static or non-static, but -- as with methods -- we'll stick to the static case in this chapter. Member
variables can optionally be declared to be public or private. A static member variable can be
used in any subroutine defined in the same class. If it is public, then it can be accessed from
outside the class using a compound name of the form

class-name.variable-name

Whereas a local variable in a subroutine only exists while a subroutine is being executed, a
static member variable exists as long as the program as a whole is running.

Parameters

IF A SUBROUTINE IS A BLACK BOX, then a parameter provides a method for passing


information from the outside world into the box. Parameters are part of the interface of a

29
Java client-server Subroutines

subroutine. They allow you to customize the behavior of a subroutine to adapt it to a particular
situation.

There are actually two very different sorts of parameters: the parameters that are used in the
definition of a subroutine, and the parameters that are passed to the subroutine when it is called.
Parameters in a subroutine definition are called formal parameters or dummy parameters. The
parameters that are passed to a subroutine when it is called are called actual parameters.

A formal parameter must be an identifier, that is, a name. A formal parameter is very much like
a variable, and -- like a variable -- it has a specified type such as int, boolean, or String. An
actual parameter is a value, and so it can be specified by any expression, provided that the
expression computes a value of the correct type. When you call a subroutine, you must provide
one actual parameter for each formal parameter in the subroutine's definition. The computer
evaluates each actual parameter and initializes the corresponding formal parameter with that
value. Consider, for example, a subroutine

static void doTask(int n, double x, boolean test) {


// statements to perform the task go here
}

This subroutine might be called with the statement

doTask(17, Math.sqrt(z+1), z >= 10);

When the computer executes this statement, it has essentially the same effect as the block of
statements:

{
int i = 17; // declare an int named i with initial value 17
double x = Math.sqrt(z+1); // compute Math.sqrt(z+1), and
// use it to initialize a new variable x of type double
boolean test = (z >= 10); // evaluate "z >= 10"
// and use the resulting true/false value to initialize
// a new variable named test
// statements to perform the task go here
}

(There are a few technical differences between this and


"doTask(17,Math.sqrt(z+1),z>=10);" -- besides the amount of typing -- because of
questions about scope of variables and what happens when several variables or parameters have
the same name.)

You can see that in order to call a subroutine legally, you need to know its name, you need to
know how many parameters it has, and you need to know the type of each parameter. This
information is called the subroutine's signature. We could write the signature of the subroutine
doTask as: doTask(int,double,boolean). Note that the signature does not include the names of
the parameters; in fact, if you just want to use the subroutine, you don't even need to know what
the names are. The names are only used by the person who writes the subroutine.

30
Java client-server Subroutines

Java is somewhat unusual in that it allows two different subroutines in the same class to have the
same name, provided that their signatures are different. (The language C++ on which Java is
based also has this feature.) We say that the name of the subroutine is overloaded because it has
several different meanings. The computer doesn't get the subroutines mixed up. It can tell which
one you want to call by the number and types of the actual parameters that you provide in the
subroutine call statement. Overloading used in the System class. This class includes many
different methods named println, for example. These methods all have different signatures,
such as:

println(int) println (double) println(String)


println (char) println(boolean) println ()

Of course all these different subroutines are semantically related, which is why it is OK to use
the same name for them all. But as far as the computer is concerned, printing out an int is very
different from printing out a String, which is different from printing out a boolean, and so
forth -- so that each of these operations requires a different method.

Note, by the way, that the signature does not include the return type of the subroutine. It is
illegal to have two subroutines in the same class that have the same signature, even if they have
different return types. For example, it would be a syntax error for a class to contain two methods
defined as:

int get() { ... }


double get() { ... }

So it should be no surprise that in the Scanner class, the methods for reading different types
have different names such as getInt() and getDouble().

At this point we have three different sorts of variables that can be used inside subroutines:

 local variables defined in the subroutines,


 formal parameter names,
 and variables that are defined outside the subroutine that are members of the same class.

Local variables have no connection to the outside world; they are purely part of the internal
working of the subroutine. Parameters are used to "drop" values into the subroutine when it is
called, but once the subroutine starts executing, parameters act much like local variables.
Changes made inside a subroutine to a formal parameter have no effect on the rest of the
program (at least if the type of the parameter is one of the primitive types -- things are more
complicated in the case of objects, as we'll see later).

Things are different when a subroutine uses a variable that is defined outside the subroutine.
That variable exists independently of the subroutine, and it is accessible to other parts of the
program, as well as to the subroutine. Such a variable is said to be global, as opposed to the
"local" variables of the subroutine. The scope of a global variable includes the entire class in
which it is defined. Changes made to a global variable can have effects that extend outside the
subroutine where the changes are made. This is not necessarily bad, but you should realize that
the global variable then has to be considered part of the subroutine's interface. The subroutine
uses the global variable to communicate with the rest of the program. This is a kind of sneaky,
back-door communication that is less visible than communication done through parameters, and

31
Java client-server Subroutines

it risks violating the rule that the interface of a black box should be straightforward and easy to
understand.

I don't advise you to take an absolute stand against using global variables inside procedures.
There is at least one good reason to do it: If you think of the class as a whole as being a kind of
black box, it might be reasonable to have the subroutines inside that box be a little sneaky about
communicating with each other, if that will make the class as a whole look simpler from the
outside.

However, you should definitely avoid using a global variable when a parameter would be more
appropriate.

Return Values

A SUBROUTINE THAT RETURNS A VALUE is called a function. A given function can


only return values of a specified type, called the return type of the function. A function call
generally occurs in a position where the computer is expecting to find a value, such as the right
side of an assignment statement, as an actual parameter in a subroutine call, or in the middle of
some larger expression. A boolean-valued function can even be used as the test condition in an
if, while, or do statement.

(It is also legal to use a function call as a stand-alone statement, just as if it were a regular
subroutine. In this case, the computer ignores the value computed by the subroutine. Sometimes
this makes sense. For example, the function console.getln(), with a return type of String,
reads and returns a line of input typed in by the user. Usually, the line that is returned is assigned
to a variable to be used later in the program, as in the statement "String name =
console.getln();". However, this function is also useful in the subroutine call statement
"console.getln();", which reads and discards all input up to and including the next carriage
return.)

You've already seen how functions such as Math.sqrt() and console.getInt() can be used.
What you haven't seen is how to write functions of your own. A function takes the same form as
a regular subroutine, except that you have to specify the value that is to be returned by the
subroutine. This is done with a return statement, which takes the form:

return expression;

Such a return statement can only occur inside the definition of a function, and the type of the
expression must match the return type that was specified for the function. When the computer
executes this return statement, it evaluates the expression, terminates execution of the function,
and uses the value of the expression as the returned value of the function.

(Inside an ordinary method -- with declared return type "void" -- you can use a return statement
with no expression to immediately terminate execution of the method and return control back to
the point in the program from which the method was called. This can be a useful way to
terminate execution of such a subroutine, but it is not required. In a function, on the other hand,
a return statement, with expression, is required.)

Here is a very simple function that could be used in a program to compute n! values. It is based
on recursion and the function computes n! based on the n!=n x (n-1)! with 0! = 1sequence:

32
Java client-server Subroutines

static long factorial(long n) {


if (n <= 0L)
return 1L;
else
return n * factorial(n - 1);
}

Some people prefer to use a single return statement at the very end of the function. This allows
the reader to find the return statement easily. We might choose to write factorial1() like this,
for example:

static long factorial1(long n ) {


long fact = 1L;
for(long i = 1L; i<=n ; ++i)
fact*=i;
return fact;
}

Here is a subroutine inside a class that uses our factorial function.

import java.util.Scanner;

public class Fact {


static long factorial(long n) {
if (n <= 0L)
return 1L;
else
return n * factorial(n - 1);
}

static long readN() {


Scanner in = new Scanner(System.in);
System.out.print("n: ");
return in.nextInt();
}

public static void main(String[] args) {


long n = readN();
System.out.println(n + "! = " + factorial(n));
}
}

Here are a few more examples of a functions. The first one computes a letter grade
corresponding to a given numerical grade, on a typical grading scale:

static char letterGrade(int numGrade) {

// returns the letter grade corresponding to


// the numerical grade numGrade

if (numGrade >= 90)


return 'A'; // 90 or above gets an A
else if (numGrade >= 80)
return 'B'; // 80 to 89 gets a B
else if (numGrade >= 65)
return 'C'; // 65 to 79 gets a C
else if (numGrade >= 50)
return 'D'; // 50 to 64 gets a D

33
Java client-server Subroutines

else
return 'F'; // anything else gets an F
} // end of function letterGrade()

The type of the return value of letterGrade() is char. Functions can return values of any type
at all. Here's a function whose return value is of type boolean:

static boolean isPrime(int N) {

// returns true if N is a prime number, that is,


// if N is a positive number that is not divisible
// by any positive integers except itself and N

int maxToTry = (int)Math.sqrt(N);


// We will try to divide N by numbers between
// 2 and maxToTry; If N is not evenly divisible
// by any of these numbers, then N is prime.
// (Note that since Math.sqrt(N) is defined to
// return a value of type double, the value
// must be typecast to type int before it can
// be assigned to maxToTry.)

for (int divisor = 2; divisor <= maxToTry; divisor++) {


if ( N % divisor == 0) // test if divisor evenly divides N
return false; // if so, we know N is not prime
}

// If we get to this point, N must be prime. Otherwise,


// the function would already have been terminated by
// a return statement in the previous for loop.

return true; // yes, N is prime

} // end of function isPrime()

In Java, a constant is defined by using the modifier "final" on a variable declaration. The word
final here indicates that the constant is in final form -- that the value that is assigned to it is final
and cannot be changed. Later, you'll see that the modifier final can also be used on method
definitions.

Objects and Classes

WHEREAS A SUBROUTINE represents a single task, an object can encapsulate both data (in
the form of instance variables) and a number of different tasks or "behaviors" related to that data
(in the form of instance methods). Therefore objects provide another, more sophisticated type of
structure that can be used to help manage the complexity of large programs.

34
Java client-server Object and classes

Objects, Instance Variables, and Instance Methods


OBJECT-ORIENTED PROGRAMMING (OOP) represents an attempt to make programs more
closely model the way people think about and deal with the world. At the heart of standard
programming is the idea of a task to be performed, and programming consists of finding a
sequence of instructions that will accomplish that task. At the heart of object-oriented
programming are objects -- entities that have behaviors, that hold information, and that can
interact with one another. Programming consists of designing a set of objects that somehow
model the problem at hand. Software objects in the program can represent real or abstract
entities in the problem domain. This is supposed to make the design of the program more natural
and hence easier to get right and easier to understand.

To some extent, OOP is just a change in point of view. We can think of an object in standard
programming terms as nothing more than a set of variables together with some subroutines for
manipulating those variables. In fact, it is possible to use object-oriented techniques in any
programming language. However, there is a big difference between a language that makes OOP
possible and one that actively supports it. An object-oriented programming language such as
Java includes a number of features that make it very different from a standard language. In order
to make effective use of those features, you have to "orient" your thinking correctly.

Classes in Java are templates for making objects. Every object belongs to some class. We say
that the object is an instance of that class. The class of an object determines what sort of data
that object contains and what behaviors it has. The object's data is contained in a set of
variables, which are called instance variables. It is important to understand that the class of an
object determines what types of variables the object contains; however, the actual data is
contained inside the individual object, not the class. Thus, each object has its own set of data.
For example, there might be a class named Student. The class could specify that every object of
type Student includes an instance variable called name, of type String. There could be any
number of objects belonging to the Student class. Each of those objects, because it is a
Student, would have a name. The point is that each of the Student objects would have its own
name. Similarly, if objects of class Student have instance variables to represent test grades,
then each Student object has its own set of grades.

In addition to data, an object also has behaviors. These behaviors are subroutines that belong to
the object. I will generally use the term "method" for subroutines that belong to an object and
that represent its behaviors. Such methods are called instance methods, because they belong to
an instance of a class.

Objects that belong to the same class have the same instance methods; that is, they have the
same behaviors. However, you should still think of instance methods as belonging to individual
objects, not to classes. There is a subtle difference between the instances of the same method in
two different objects of the same class: A method belonging to an object has direct access to that
particular object's instance variables. For example, a Student class might specify an instance
method for computing the overall grade of a student by averaging that student's test grades.
When this method is called for a particular object of class Student, it will use that Student's
test grades, taken from the object's instance variables. The same method called for a different
student will use the other student's grades instead.

35
Java client-server Object and classes

Classes in Java serve a double purpose. One of these is the one just described: to serve as
templates for making objects. The other is the one you have seen in previous chapters: to group
together related static variables and static methods. The rule is that static variables and static
methods -- that is, those declared with the static modifer -- belong to the class itself, and not to
objects created from that class. Non-static variables and methods don't belong to the class at all!
Instead, they are there to specify what instance variables and instance methods objects of that
class will have. (This is confusing, no doubt about it, and it could well be argued that Java's
design is defective in this regard.) Static variables and methods are sometimes called class
variables and class methods, since they belong to the class itself, rather than to instances of that
class. Consider the following example of the Student class:

public class Student {

public String name; // Student's name


public int ID; // unique ID number for this student
public double test1, test2, test3; // grades on three tests

public double getAverage() { // compute average test grade


return (test1 + test2 + test3) / 3;
}

private static int nextUniqueID = 1;

public static int getUniqueID() { // return a unique ID


int thisID = nextUniqueID;
nextUniqueID++;
return thisID;
}
}

This class definition says that an object of class Student will include instance variables name,
ID, test1, test2, and test3, and it will include an instance method named getAverage(). The
names, IDs, and tests in different objects will generally have different values. When called for a
particular student, the method getAverage() will compute that student's average, using that
student's test grades.

On the other hand, nextUniqueID and getUniqueID() are static members of class Student.
There is only one copy of the variable nextUniqueID, and it belongs to the class itself.
Similarly, getUniqueID() is associated with the class, not with any particular instance. It would
make no sense -- and would be a syntax error -- for getUniqueID() to refer to one of the
instance variables, such as name.

The method getUniqueID() can be called from outside the class using the name
"Student.getUniqueID()", indicating its membership in the class Student. This can be done,
of course, whether or not any instances of the class even exist. The instance method
getAverage(), on the other hand, can only be called through an object of class Student. If std
is such an object, then the method can be called using the name "std.getAverage()",
indicating that the method belongs to the object std. The instance variables of std would be
referred to as std.name, std.ID, std.test1, std.test2, and std.test3.

(By the way, it is possible to think of static methods and variables in a class as being "shared"
by all the instances of that class. Based on this reasoning, Java will let you refer to static
members through objects, as well as through the class name. Thus, if std is an instance of class

36
Java client-server Object and classes

Student, it is legal to refer to std.getUniqueID() instead of Student.getUniqueID().


However, I feel that this syntax can only increase the confusion, and I urge you to avoid it.)
It's worth taking a somewhat closer look at the static member variable nextUniqueID. Since it is
a static variable, there is just one version of this variable, and it exists for the whole time that the
program is running. (Instance variables, on the other hand, come into existence and disappear as
objects are created and disposed of.)

At the beginning of the program's execution, the initial value, 1, is stored in the variable
Student.nextUniqueID. Every time the static method Student.getUniqueID() is called, the
value of nextUniqueID is incremented. Now, since nextUniqueID is declared to be private, it
is completely inaccessible from outside the class Student. We can see everything that can ever
be done with this variable by examining the class Student. This means that it is absolutely
guaranteed that the only way that the value of nextUniqueID can change is when the method
getUniqueID() is called. It might be nice if other variables, such as the instance variables
test1, test2, and test3, had similar protection. I'll return to the question of controlling access
to member variables later.

Objects can be created using classes as templates, but I haven't told you how to create objects in
a program. If you have a class, such as Student, you can declare variables of that class:

Student std; // declare variable std of type Student

However, declaring a variable does not create an object! This is an important point, which is
related to this Very Important Fact:

In Java, no variable can ever hold an object.


A variable can only hold a reference to an object.

You should think of objects as floating around independently in the computer's memory. (In
fact, there is a portion of memory called the heap where objects live.) Instead of holding an
object itself, a variable holds the information necessary to find the object in memory. This
information is called a reference or pointer to the object. In effect, a reference to an object is the
address of the memory location where the object is stored. When you use a variable of object
type, the computer uses the reference in the variable to find the actual object.

Objects are actually created by an operator called new, which creates an object and returns a
reference to that object. For example, assuming that std is a variable of type Student,

std = new Student();

would create a new object of type Student and store a reference to that object in the variable
std. The instance variables and methods of the object could then be accessed through std, as in
"std.test1".

It is possible for a variable like std, whose type is given by a class, to refer to no object at all.
We say in this case that std holds a null reference. The null reference can be written in Java as
"null". You could assign a null reference to the variable std by saying

std = null;

37
Java client-server Object and classes

and you could test whether the value of std is null by testing

if (std == null) . . .

If the value of a variable is null, then it is, of course, illegal to refer to instance variables or
instance methods through that variable -- since there is no object, and hence no instance
variables to refer to. If your program attempts to use a null reference illegally, the result is an
error called a "null pointer exception."

Let's look at a sequence of statements that work with objects:

Student std = new Student(); // Declare a variable, std,


// and initialize it with a
// reference to a newly created
// object of the class Student.
Student std1 = new Student(); // Declare std1, and initialize
// it to refer to another
// new object.
Student std2 = std1; // Declare std2, and initialize
// it to refer to the SAME
// object that std1 refers to.
Student std3; // Declare std3, and initialize
// it to null. (This is done
// automatically.)
std.name = "John Smith";
std.ID = Student.getUniqueID();
std1.name = "Mary Jones";
std1.ID = Student.getUniqueID();
// (Other instance variables have default
// initial values of zero.)

After the computer executes these statements, the situation looks something like this (assuming
that the two calls to getUniqueID() were the very first two times in the program that this
method was called):

38
Java client-server Object and classes

This picture shows variables as little boxes, labeled with the names of the variables. Objects are
shown as boxes with round corners. When a variable contains a reference to an object, the value
of that variable is shown as an arrow pointing to the object. The arrows from std1 and std2
both point to the same object. This illustrates a Very Important Point:

When one object variable is assigned to another, only a reference is copied.


The object referred to is not copied.

This is very different from the usual semantics associated with an assignment statement. (When
the values involved belong to Java's primitive types, then assignments obey the expected
semantics. That is, primitive type values are copied when they are assigned.) Note that in this
example, since std1.name was assigned the value "Mary Jones", it will also be true that
std2.name has the value "Mary Jones". In fact, std1.name and std2.name are just different
ways of referring to exactly the same thing.

You can test objects for equality and inequality using the operators == and !=, but here again,
the semantics are unusual. When you make a test "if (std1 == std2)", you are testing
whether the object references in std1 and std2 point to exactly the same location in memory;
you are not testing whether the values stored in the objects are equal. It would be possible to
have two objects whose instance variables all have identical values. However, those objects are
not considered equal by the == operator because they are stored in distinct memory locations.

There are a few surprises in the above illustration. You'll notice that strings in Java are objects.
In particular, it is possible for the value of a String variable to be null, and in general, a
String variable stores a reference to a string, not the string itself. This explains why the ==
operator doesn't work as one would expect for Strings: Two strings are judged equal by == if
they are stored in the same place in memory, not merely if they happen to contain the same
characters.

You'll also notice that I've showed the class, Student, as an object. In Java, classes are
technically considered to be objects and to belong to a special class called, appropriately
enough, Class. The "instance variables" and "instance methods" of a class, considered as an
object, are just the static members of the class. Perhaps thinking of things this way will help you
to understand static and non-static members.

Constructors

OBJECT TYPES IN JAVA ARE VERY DIFFERENT from the primitive types. Simply
declaring a variable whose type is given as a class does not automatically create an object of that
class. Objects must be explicitely constructed. The process of constructing an object means,
first, finding some unused memory in the heap that can be used to hold the object and, second,
filling in the object's instance variables. As a programmer, you will usually want to exercise
some control over what initial values are stored in a new object's instance variables. There are
two ways to do this. The first is to provide initial values in the class definition, where the
instance variables are declared. For example, consider the class:

class Mosaic {

// class to represent "mosaics" consisting of

39
Java client-server Object and classes

// colored squares arranged in rows and columns

int ROWS = 10; // number of rows of squares


int COLS = 20; // number of columns of squares
.
. // (the rest of the class definition)
.
}

When an object of type Mosaic is created, it includes two instance variables named ROWS and
COLS, which are initialized with the values 10 and 20, respectively. This means that for every
newly created object, msc, of type Mosaic, the value of msc.ROWS will be 10, and the value of
msc.COLS will be 20. (Of course, there is nothing to stop you from changing those values after
the object has been created.)

If you don't provide any initial value for an instance variable, default initial values are provided
automatically. Instance variables of numerical type (int, double, etc.) are automatically
initialized to zero if you provide no other values; boolean variables are initialized to false; and
char variables, to the Unicode character with code number zero.

An instance variable can also be a variable of object type. For such variables, the default initial
value is null. (In particular, since Strings are objects, the default initial value for String
variables is null.) Of course, you can provide an alternative initial value if you like. For
example, the class Mosaic might contain an instance variable of type MosaicWindow, where
MosaicWindow is the name of another class. This instance variable could be initialized with a
new object of type MosaicWindow:

class Mosaic {
int ROWS = 10; // number of rows of squares
int COLS = 20; // number of columns of squares
MosaicWindow window = new MosaicWindow();
// a window to display the mosaic
.
. // (the rest of the class definition)
.
}

When an object of class Mosaic is constructed, another object of type MosaicWindow is


automatically constructed, and a reference to the new MosaicWindow is stored in the instance
variable named window. (Note that the statement "MosaicWindow window = new
MosaicWindow();" is not executed unless and until an object of class Mosaic is created. And it
is executed again for each new object of class Mosaic, so that each Mosaic object gets its own
new MosaicWindow object.)

There is a second way to get initial values into the instance variables of a class. That is to
provide one or more constructors for the class. In fact, constructors can do more than just fill in
instance variables: They let you program any actions whatsoever that you would like to take
place automatically, every time an object of the class is created.

A constructor is defined to be a subroutine in a class whose name is the same as the name of the
class and which has no return value, not even void. A constructor can have parameters. You can
have several different constructors in one class, provided they have different signatures (that is,
provided they have different numbers or types of parameters). A constructor cannot be declared

40
Java client-server Object and classes

to be static, but, on the other hand, it's not really an instance method either. The only way you
can call a constructor is with the new operator. In fact, the syntax of the new operator is:

new constructor-call

When the computer evaluates this expression, it creates a new object, executes the constructor,
and returns a reference to the new object.

As an example, let's rewrite the Student class that was used in the paragraph:

public class Student {

private String name; // Student's name


private int ID; // unique ID number for this student
public double test1, test2, test3; // grades on three tests

private static int nextUniqueID = 1;


// next available unique ID number

Student(String theName) {
// constructor for Student objects;
// provides a name for the Student,
// and assigns the student a unique
// ID number
name = theName;
ID = nextUniqueID;
nextUniqueID++;
}

public String getName() {


// accessor method for reading value of private
// instance variable, name
return name;
}

public getID() {
// accessor method for reading value of ID
return ID;
}

public double getAverage() { // compute average test grade


return (test1 + test2 + test3) / 3;
}

} // end of class Student

In this version of the class, I have provided a constructor, Student(String). This constructor
has a parameter of type String that specifies the name of the student. I've made the instance
variable name into a private member, so that I can keep complete control over its value. In fact,
by examining the class, you can see that once the value of name has been set by the constructor,
there is no way for it ever to be changed: A name is assigned to a Student object when it is
created, and the name remains the same for as long as the object exists.

Notice that since name is a private variable, I've provided a function, getName() that can be
used from outside the class to find out the name of the student. Thus, from outside the class, it's
possible to discover the name of a student, but not to change the name. This is a very typical

41
Java client-server Object and classes

way of controlling access to a variable. The ID instance variable in the class Student is handled
in a similar way.

I should note, by the way, that if you provide initial values for instance variables, those values
are computed and stored in the variables before the constructor is called. It's common to use a
combination of initial values and constructors to set up new objects just the way you want them.

Since the constructor in this class has a parameter of type String, a value of type String must
be included when the constructor is called. Here are some examples of using this constructor to
make new Student objects:

std = new Student("John Smith");


std1 = new Student("Mary Jones");

You've probably noticed that the previous version of class Student did not include a
constructor. Yet, we were able to construct instances of the class using the operator "new
Student()". The rule is that if you don't provide any constructor in a class, then a default
constructor, with no parameters, is provided automatically. The default constructor doesn't do
anything beyond filling in the instance variables with their initial values.

An object exists on the heap, and it can be accessed only through variables that hold references
to the object. What happens to an object if there are no variables that refer to it? Consider the
following two statements (though in reality, you'd never do anything like this):

Student std = new Student("John Smith");


std = null;

In the first line, a reference to a newly created Student object is stored in the variable std. But
in the next line, the value of std is changed, and the reference to the Student object is gone. In
fact, there are now no references whatsoever to that object stored in any variable. So there is no
way for the program ever to use the object again. It might as well not exist. In fact, the memory
occupied by the object should be reclaimed to be used for another purpose.

Java uses a procedure called garbage collection to reclaim memory occupied by objects that are
no longer accessible to a program. It is the responsibility of the system, not the programmer, to
keep track of which objects are "garbage".

In many other programing languages, it's the programmer's responsibility. Unfortunately,


keeping track of memory usage is very error-prone, and many serious program bugs are caused
by such errors. A programmer might accidently delete an object even though there are still
references to that object. This is called a "dangling pointer" error, and it leads to problems when
the program tries to access an object that is no longer there. Another type of error is a "memory
leak," where a programmer neglects to delete objects that are no longer in use. This can lead to
filling memory with objects that are completely inaccessible, and the program might run out of
memory even though, in fact, large amounts of memory are begin wasted.

Because Java uses garbage collection, such errors are simply impossible. You might wonder
why all languages don't use garbage collection. In the past, it was considered too slow and
wasteful. However, research into garbage collection techniques combined with the incredible

42
Java client-server Object and classes

speed of modern computers have combined to make garbage collection feasible. Programmers
should rejoice.

Inheritance and Polymorphism

A CLASS REPRESENTS A SET OF OBJECTS which share the same structure and behaviors.
The class determines structure by specifying variables that are contained in each instance of the
class, and it determines behavior by providing the methods that express the behavior of those
instances. This is a powerful idea. However, something like this can be done in most
programming languages. The central new idea in object-oriented programming -- the idea that
really distinguishes it from traditional programming -- is to allow classes to express the
similarities among objects that share some, but not all, of their structure and behavior. Such
similarities can expressed using inheritance.

The term "inheritance" refers to the fact that one class can inherit part or all of
its structure and behavior from another class. The class that does the inheriting
is said to be a subclass of the class from which it inherits. If class B is a subclass
of class A, we also say that class A is a superclass of class B. (Sometimes the
terms derived class and base class are used instead of subclass and superclass.)
A subclass can add to the structure and behavior that it inherits. It can also
replace or modify inherited behavior (though not inherited structure). The
relationship between subclass and superclass is sometimes shown by a diagram in which the
subclass is shown below, and connected to, its superclass.

In Java, when you create a new class, you can declare that it is a subclass of an existing class. If
you are definining a class named "B" and you want it to be a subclass of a class named "A", you
would write:

class B extends A {
.
. // additions to, and modifications of,
. // stuff inherited from class A
.
}

Several classes can be declared as subclasses of the same superclass. The subclasses, which
could be considered to be "sibling classes," share some sturctures and behaviors -- namely, the
ones they inherit from their common superclass. The superclass can be seen as expressing those
shared structures and behaviors. In the diagram to the left, classes B, C, and D are sibling

43
Java client-server Object and classes

classes. Inheritance can also extend over several "generations" of classes. This is shown in the
diagram, where class E is a subclass of class D which is itself a subclass of class A. In this case,
class E is considered to be a subclass of class A, even though the inheritance is indirect.

Let's look at an example. Suppose that a program has to deal with motor vehicles, including
cars, trucks, and motorcycles. (This might be a program used by a Department of Motor
Vehicles to keep track of registrations.) The program could use a class named Vehicle to
represent all types of vehicles. The Vehicle class might include instance variables such as
registration number and owner and instance methods such as transferOwnership(). These are
variables and methods common to all vehicles. Three subclasses of Vehicle -- Car, Truck, and
Motorcycle -- could then be used to hold variables and methods specific to particular types of
vehicles. The Car class might add an instance variable numberOfDoors, the Truck class might
have numberOfAxels, and the Motorcycle could have a boolean variable hasSidecar. (Well, it
could in theory at least.) The declarations of these classes in Java program would look, in
outline, like this:

class Vehicle {
int registrationNumber;
Person owner; // (assuming that a Person class has been defined)
void transferOwnership(Person newOwner) {
. . .
}
. . .
}

class Car extends Vehicle {


int numberOfDoors;
. . .
}

class Truck extends Vehicle {


int numberOfAxels;
. . .
}

class Motorcycle extends Vehicle {


boolean hasSidecar;
. . .
}

Suppose that myCar is a variable of type Car that has been declared and initialized with the
statement

Car myCar = new Car();

Then it would be legal to refer to myCar.numberOfDoors, since numberOfDoors is an instance


variable in the class Car. But since class Car extends class Vehicle,
myCar.registrationNumber, myCar.owner, and myCar.transferOwnership() also exist.

44
Java client-server Object and classes

Now, in the real world, cars, trucks, and motorcycles are in fact vehicles. The same is true in a
program. That is, an object of type Car or Truck or Motorcycle is automatically an object of
type Vehicle. The practical effect of this is that an object of type Car can be assigned to a
variable of type Vehicle. That is, it would be legal to say

Vehicle myVehicle = myCar;

or even

Vehicle myVehicle = new Car();

After either of these statements, the variable myVehicle holds a reference to an object that
happens to be an instance of the class Car. The object "remembers" that it is in fact a Car, not
just a Vehicle. Information about the actual class of an object is stored as part of that object. On
the other hand, if myVehicle is a variable of type Vehicle the statement

Car myCar = myVehicle;

would be illegal because myVehicle could potentially refer to other types of vehicles besides
cars. The computer will not allow you to assign an int value to a variable of type short,
because not every int is a short. As in that case, the solution here is to use type casting. If, for
some reason, you happen to know that myVehicle does in fact refer to a Car, you can use the
type cast (Car)myVehicle to tell the computer to treat myVehicle as if it were actually of type
Car. So, you could say

Car myCar = (Car)myVehicle;

and you could even refer to ((Car)myVehicle).numberOfDoors. Note that for object types, when
the computer executes a program, it checks whether type casts are valid. So, for example, if
myVehicle refers to an object of type Truck, then the type cast (Car)myVehicle will produce
an error.

As another example, consider a program that deals with shapes drawn on the screen. Let's say
that the shapes include rectangles, ovals, and roundrects of various colors.

Three classes, Rectangle, Oval, and RoundRect, could be used to represent the three types of
shapes. These three classes could have a common superclass, Shape, to represent features that
all three shapes have in common. The Shape class could include instance variables to represent
the color, position, and size of a shape. It could include instance methods for changing the color,
position, and size of a shape. Changing the color, for example, would involve changing the
value of an instance variable, and then redrawing the shape in its new color:

class Shape {

Color color; // color of shape. Note that class Color

45
Java client-server Object and classes

// is defined in package java.awt. Assume


// that this class has been imported.

void setColor(Color newColor) {


// method to change the color of the shape
color = newColor; // change value of instance variable
redraw(); // redraw shape, which will appear in new color
}

void redraw() {
// method for redrawing the shape
? ? ? // what commands should go here?
}

. . . // more instance variables and methods

} // end of class Shape

Now, you might see a problem here with the method redraw(). The problem is that each
different type of shape is drawn differently. The method setColor() can be called for any type
of shape. How does the computer know which shape to draw when it executes the redraw()?
Informally, we can answer the question like this: The computer executes redraw() by asking
the shape to redraw itself. Every shape object knows what it has to do to redraw itself.

In practice, this means that each of the specific shape classes has its own redraw() method:

class Rectangle extends Shape {


void redraw() {
. . . // commands for redrawing a rectangle
}
. . . // possibly, more methods and variables
}
class Oval extends Shape {
void redraw() {
. . . // commands for redrawing an oval
}
. . . // possibly, more methods and variables
}
class RoundRect extends Shape {
void redraw() {
. . . // commands for redrawing a rounded rectangle
}
. . . // possibly, more methods and variables
}

If oneShape is a variable of type Shape, it could refer to an object of any of the types
Rectangle, Oval, or RoundRect. As a program executes, and the value of oneShape changes, it
could even refer to objects of different types at different times! Whenever the statement

oneShape.redraw();

is executed, the redraw method that is actually called is the one appropriate for the type of object
to which oneShape actually refers. There may be no way of telling, from looking at the text of
the program, what shape this statement will draw, since it depends on the value that oneShape
happens to have when the program is executed. Even more is true. Suppose the statement is in a
loop and gets executed many times. If the value of oneShape changes as the loop is executed, it
is possible that the very same statement "oneShape.redraw();" will call different methods and

46
Java client-server Object and classes

draw different shapes as it is executed over and over. We say that the redraw() method is
polymorphic. Polymorphism is one of the major distinguishing features of object-oriented
programming.

Perhaps this becomes more understandable if we change our terminology a bit: In object-
oriented programming, calling a method is often referred to as sending a message to an object.
The object responds to the message by executing the appropriate method. The statement
"oneShape.redraw();" is a message to the object referred to by oneShape. Since that object
knows what type of object it is, it knows how it should respond to the message. From this point
of view, the computer always executes "oneShape.redraw();" in the same way: by sending a
message. The response to the message depends, naturally, on who receives it. From this point of
view, objects are active entities that send and receive messages, and polymorphism is a natural,
even necessary, part of this view. Polymorphism just means that different objects can respond to
the same message in different ways.

One of the most beautiful things about polymorphism is that it let's code that
you write do things that you didn't even conceive of, at the time you wrote it.
If for some reason, I decide that I want to add beveled rectangles to the types
of shapes my program can deal with, I can write a new subclass,
BeveledRect, of class Shape and give it is own redraw() method.
Automatically, code that I wrote previously -- such as the statement
oneShape.redraw() and the call to redraw() inside Shape's setColor()
method -- can now suddenly start drawing beveled rectangles!

Look back at the Shape class. It includes a redraw() method; it has to include this method, or
else it would be illegal to call redraw() from inside Shape's setColor() method. But how
should we define it? The answer may be surprising: We should leave it blank! The fact is that
the class Shape represents the abstract idea of a shape, and there is no way to draw such a thing.
Only particular, concrete shapes can be drawn. In fact, if you think about it, there can never be
any reason to construct an actual object of type Shape. You can have variables of type Shape,
but the objects they refer to will always belong to one of the subclasses of Shape. We say that
Shape is an abstract class. An abstract class is one that is not used to construct objects, but only
as a basis for making subclasses. An abstract class exists only to express the common properties
of all its subclasses.

Similarly, we could say that the redraw() method in class Shape is an abstract method, since it
is never meant to be called. In fact, there is nothing for it to do -- any actual redrawing is done
by redraw() methods in one of the subclasses of Shape. The redraw() method in Shape has to
be there. But it is there only to tell the computer that all Shapes understand the redraw message.
As an abstract method, it exists merely to specify the common interface of all the actual,
concrete versions of redraw() in the subclasses of Shape. There is no reason for the abstract
redraw() in class Shape to contain any code at all.

Shape and its redraw() method are semantically abstract. You can also tell the computer,
syntactically, that they are abstract by adding the modifier "abstract" to their definitions:

abstract class Shape {

Color color; // color of shape.

void setColor(Color newColor) {


// method to change the color of the shape

47
Java client-server Object and classes

color = newColor; // change value of instance variable


redraw(); // redraw shape, which will appear in new color
}

abstract void redraw();


// abstract method -- must be redefined in
// concrete subclasses

. . . // more instance variables and methods

} // end of class Shape

Once you have done this, it becomes illegal to try to create actual objects of type Shape, and the
computer will report an error if you try to do so.

In Java, every class that you declare has a superclass. If you don't specify a superclass, then the
superclass is automatically taken to be Object, a predefined class that is part of the package
java.lang. (The class Object itself has no superclass.) Thus,

class myClass { . . .

is exactly equivalent to

class myClass extends Object { . . .

Every other class is, directly or indirectly, a subclass of Object. This means that any object,
belonging to any class whatsoever, can be assigned to a variable of type Object. The class
Object represents very general properties that are shared by all objects, of any class. Object is
the most abstract class of all!

Interfaces, this and super, and visibility

THIS SECTION COVERS A FEW ASPECTS of Java classes and objects that didn't find their
way into previous sections of this chapter. That doesn't mean that they're not important,
though...

Interfaces

Some object-oriented programming languages, such as C++, allow a class to extend two or more
superclasses. This is called multiple inheritance. In the illustration below, for example, class E is
shown as having both class A and class B as direct superclasses, while class F has three direct
superclasses.

48
Java client-server Object and classes

Such multiple inheritance is not allowed in Java. The designers of Java wanted to keep the
language reasonably simple, and felt that the benefits of multiple inheritance were not worth the
cost in increased complexity. However, Java does have a feature that can be used to accomplish
many of the same goals as multiple inheritance: interfaces.

We've encountered the term "interface" before, in connection with black boxes in general and
subroutines in particular. The interface of a subroutine consists of the name of the subroutine, its
return type, and the number and types of its parameters. This is the information you need to
know if you want to call the subroutine. A subroutine also has an implementation: the block of
code which defines it and which is executed when the subroutine is called.

In Java, interface is a reserved word with an additional meaning. An interface consists of a set
of subroutine interfaces, without any associated implementations. A class can implement an
interface by providing an implementation for each of the subroutines specified by the
interface. (An interface can also include definitions of constants -- that is, variables declared
as final static. The constants are there as a convenience to provide meaningful names for
certain quantities.) Here is an example of a very simple Java interface:

public interface Drawable {


public void draw();
}

This looks much like a class definition, except that the implementation of the method redraw()
is omitted. A class that implements the interface Drawable must provide an implementation
for this method. Of course, it can also include other methods and variables. For example,

class Line implements Drawable {


public void draw() {
. . . // do something -- presumably, draw a line
}
. . . // other methods and variables
}

While a class can extend only one other class, it can implement any number of interfaces. In
fact, a class can both extend another class and implement one or more interfaces. So, we can
have things like

class FilledCircle extends Circle implements Drawable, Fillable {


. . .
}

49
Java client-server Object and classes

The point of all this is that, although interfaces are not classes, they are something very similar.
An interface is very much like an abstract class, that is a class that can never be used for
constructing object, but can be used as a basis for building other classes. The subroutines in an
interface are like abstract methods, which must be implemented in concrete subclasses of the
abstract class. And as with abstract classes, even though you can't construct an object from an
interface, you can declare variables whose type is given by an interface. For example, if
Drawable is an interface, and if Line and FilledCircle are classes that implement Drawable,
they you could say:

Drawable figure = new Line(); // variable of type Drawable, referring


// to an object of class Line
. . . // do some stuff with figure
figure.draw(); // calls draw() method from class Line
figure = new FilledCircle(); // now, figure refers to an object
// of class FilledCircle
. . .
figure.draw(); // calls draw() method from class FilledCircle

A variable of type Drawable can refer to any object of any class that implements the Drawable
interface. A statement like figure.draw(), above, is legal because any such class has a draw()
method.

Note that a type is something that can be used to declare variables. They can also be used to
specify the type of a parameter in a subroutine, or the return type of a function. In Java, a type
can be either a class, an interface, or one of the eight built-in primitive types. Of these, however,
only classes can be used to construct new objects.

You are not likely to need to write your own interfaces until you get to the point of writing fairly
complex programs. However, there are a few interfaces that are used in important ways in Java's
standard packages, so you do need to know something about them.

this and super

Inside a class, when you want to refer to a variable or method that belongs to the same class, you
simply give its name. This is also true for variables and methods inherited from a superclass of
the class. For a variable or method from outside the class, however, you have to use a compound
name that tells what object it belongs to (or, in the case of a static variable or method, what class
it belongs to). For example, you have to refer to the square root function as Math.sqrt, since it
belongs to the class Math. Similarly, to call the getln method belonging to a Console object
named console, you have to refer to it as console.getln. On the other hand, if you were
writing the Console class itself, you could refer to it simply as getln.

There are some circumstances, though, when you need to use a compound name even for a
member of the same class. When an instance method is executed -- which happens because a
message has been sent to some object -- the system sets up two special variables to refer to the
object that received the message. The variables are named this and super. You can use these
variables in the definition of any instance method.

Use the variable named this when you need a name for "the object to which this method
belongs." You might need to use this if you want to assign the object to a variable or pass it as

50
Java client-server Object and classes

a parameter. This is quite common, actually. Consider, for example, a class that represents icons
on a computer screen. Icons can be "selected", and you want to keep track of which icon is
currently selected. You could say

class Icon {
static Icon selectedIcon = null; // currently selected icon
void select() { // instance method for selecting this icon
selectedIcon = this; // record that this is the selected icon
. . . // more stuff
}
. . . // more variables and methods
}

Another use of "this" is to clear up ambiguities when there are two variables or parameters or
local variables of the same name. For example, consider

class Shape {
Color color; // the color of this shape
void setColor(Color color) {
// change shape to a new color
this.color = color; // change value of instance variable
redraw();
}
. . . // more stuff
}

Inside the method setColor(), there are two things with the same name: A parameter and an
instance variable are both named "color." The rule in situations like this is that the parameter
hides the instance variable, so that when the name "color" is used by itself, it refers to the
parameter. Fortunately, the instance variable can still be accessed by referring to it with the
compound name "this.color", which can only mean an instance variable in the object, this.
(You could still ask whether it's reasonable to use the same name for instance variable and
parameter. It probably is; it saves you from having to make up some funny new name, such as
newColor, for the parameter.)

The variable named super is even more important than the variable named this, but it is also
more subtle: super refers to the same object as this, but it refers to that object treated as if it
were a member of the superclass of the class containing the method that you are writing.
So, for example, super.x would refer to an instance variable named x in the superclass of the
current class. This can be useful for the following reason: If a class contains an instance variable
with the same name as an instance variable in its superclass, than an object of that class will
contain an instance of each variable. The variable in the subclass does not replace the variable
of the same name in the superclass; it merely hides it. The variable from the superclass can still
be accessed, using super. (Again, you might ask whether its reasonable to name variables in
this way. The answer is, probably not.)

When you write a method in a subclass that has the same signature as a method in its superclass,
the method from the superclass is hidden in the same way. We say that the method in the
subclass overrides the method from the superclass. Again, however, super can be used to access
the method from the superclass.

The major use of super is to override a method with a new method that extends the behavior of
the inherited method, instead of changing that behavior entirely. The new method can use super

51
Java client-server Object and classes

to call the inherited method, and it can include additional code to provide additional behavior.
As a somewhat silly example,

class FilledRectangle extends Rectangle {


void draw() {
super.draw(); // call draw method from class Rectangle
fill(); // then do some additional stuff
}
.
.

As a more realistic example, suppose that the class TextBox represents a box on the screen
where the user can type some input. Let's say that TextBox has an instance method called key
which is called whenever the user presses a key on the keyboard. The purpose of this method is
to add the character that the user has typed to the text box. Now, I might want a subclass,
NumberBox, of TextBox that will let the user type in an integer. I might define NumberBox like
this:

class NumberBox extends TextBox {


void key(char ch) {
// user has typed the character ch; enter it in
// the box, but only if it is a digit in the
// range '0' to '9'
if (ch >= '0' && ch <= '9')
super.key(ch);
}
}

Constructors in Subclasses

Constructors exist to make certain that new objects start out with known initial states. If an
object is a member of a subclass, part of its structure is inherited from that class's superclass,
and that part of its structure must be initialized by calling a constructor from the superclass.

If the superclass has a default constructor -- that is, one with no parameters -- then the system
will call it automatically. (This will also happen if no constructor at all is defined for the
superclass, since in that case the system provides a default constructor.) However, if all the
constructors defined in the superclass require parameters, then any constructor you write for the
subclass must explicitly call a constructor for the superclass. Sometimes, even if you aren't
forced to do it, you might want to call a particular constructor from the superclass. The syntax
for doing so uses the special variable super. Here is an example. Assume that the class TextBox
has a constructor that specifies the maximum number of characters that the box can hold. Then,
we might define a subclass

class TextBoxWithDefault extends TextBox {


TextBoxWithDefault(int maxChars, String defaultContents) {
super(maxChars); // call constructor from superclass
setContents(defaultContents);
}
. . . // more stuff
}

52
Java client-server Object and classes

It is also possible for one constructor to call another constructor from the same class. This is
done using the special variable this in place of super. The class TextBoxWithDefault, for
example, might have a constructor

TextBoxWithDefault() {
this(20,"");
}

This constructor exists only to call another, more complicated constructor and to provide it with
reasonable default values for its parameters.

Visibility

I have already noted that both classes and their members can be declared to be public or
private. The modifiers public and private are used for visibility or access control. A class,
method, or variable that is declared to be public can be is visible and can be accessed from
anywhere at all. A method or variable that is declared to be private can is visible and only be
accessed from inside the class where it is defined. (A private class wouldn't make much sense
-- it couldn't be used at all!)

There is also an intermediate level of visibility or access control called protected. A method or
variable that is declared to be protected can be accessed from inside the same class or from
any subclass of that class. You should use protected access if you want to hide some details of a
class's implementation from the "outside world", but you want to allow the implementation to be
overridden in subclasses.

In my examples, I have been pretty careless about specifying access control. For the most part, I
have left out access control modifiers entirely. You might recall that when no access control is
specified, then access is permitted by all other classes in the same package. And if you don't
specify a package, then everything goes into a default package. In that case, everything in your
program will be visible to everything else. This is not a terrible thing for small demonstration
programs, but for serious programming, access control is an important safety mechanism as well
as a useful design tool.

Strings

JAVA HAS A BUILT-IN TYPE, String, to represent strings of characters. This type differs
from the eight primitive types because a value of type String is an object. A String is not just
a simple data value; it also has "methods.". For example, if str is a variable of type String, you
can call the method str.length(), which is a function that returns the number of characters in
the string. There's a lot more you can do with strings. This section covers some of the details.

One thing that you cannot do with strings is to use the relational operators <, >, <=, and <= to
compare them. You can legally use == and != to compare Strings, but because of peculiarities
in the way objects behave, they won't give the results you want. (I'll get back to this in a later
chapter.) Instead, you should use methods that are provided for comparing Strings.

53
Java client-server Object and classes

The String class defines a lot of methods. Here are just a few that you might find useful.
Assume that s1 and s2 are Strings:

 s1.equals(s2) is a boolean-valued function that returns true if s1 consists of exactly the


same sequence of characters as s2.
 s1.equalsIgnoreCase(s2) is another boolean-valued function that checks whether s1 is
the same string as s2, but this function considers upper and lower case letters to be equivalent.
Thus, if s1 is "cat", then s1.equals("Cat") is false, while s1.equalsIgnoreCase("Cat") is
true.
 s1.compareTo(s2) is an integer-valued function that compares the two strings. If the
strings are equal, the value returned is zero. If s1 is less than s2, the value returned is -1, and if
s1 is greater than s2, the value returned is 1. (If the strings consist entirely of lowercase letters,
then "less than" and "greater than" refer to alphabetical order.)
 s1.charAt(N), where N is an integer, is a char-valued function that returns the N-th
character in the string. Positions are numbered starting with 0, so s1.charAt(0) is the actually
the first character, s1.charAt(1) is the second, and so on.
 s1.length(), as mentioned above, is an integer-valued function that gives the number of
characters in s1.
 s1.toUpperCase() is a String-valued function that returns a new string that is equal to s1,
except that any lower case letters in s1 have been converted to upper case. There is also a
method s1.toLowerCase().

For the method s1.toUpperCase(), note that the value of s1 is not changed. Instead a new
string is created and returned as the value of the function. You could use this function, for
example, in an assignment statement "s2 = s1.toLowerCase();".

Also useful is the fact that you can use the operator + to concatenate two strings. The
concatenation of two strings is a new string consisting of all the characters of the first string
followed by all the characters of the second string. For example, "Hello" + "World" evaluates to
"HelloWorld". (Gotta watch those spaces, of course.)

Even more surprising is that you can concatenate values belonging to one of the primitive types
onto a String using the + operator. The value of primitive type is converted to a string, just as it
would be if you printed it to the console. For example, the expression "Number" + 42 evaluates
to the string "Number42". And the statements

System.out.print("After ");
System.out.print (years);
System.out.print (" years, the value is ");
System.out.print (principal);

can be replaced by the single statement:

System.out.print("After " + years +


" years, the value is " + principal);

Obviously, this is very convenient. And you can even use the += operator to add something onto
the end of a String. For example, if you want to read a sequence of letters from the console and
store them in a String, you could do it like this:

String str = ""; // start with an empty string


char ch;

54
Java client-server Object and classes

try {
ch = (char) System.in.read(); // read one character
while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
// ch is a letter, so append it to the string
// and read the next character.
str += ch; // (note: same as str = str + ch;)
ch = (char) System.in.read();
} // end of while loop
} catch (IOException e) {
System.out.println("Error while reading : " + e);
}
System.out.println("I read : " + str);

Object-Oriented Programming

THERE ARE SEVERAL WAYS in which object-oriented concepts can be applied to the
process of designing and writing programs. The broadest of these is object-oriented analysis and
design which applies an object-oriented methodology to the earliest stages of program
development, during which the overall design of a program is created. For help in writing
programs, OOP makes it possible to produce generalized software components that can be used
in a wide variety of programming projects. More generally, inheritance makes it easier for
programmers to reuse old work by building on existing classes.

Object-oriented Analysis and Design

A large programming project goes through a number of stages, starting with specification of the
problem to be solved, followed by analysis of the problem and design of a program to solve it.
Then comes coding, in which the program's design is expressed in some actual programming
language. This is followed by testing and debugging of the program. After that comes a long
period of maintenance, which means fixing any new problems that are found in the program and
modifying it to adapt it to changing requirements. Together, these stages form what is called the
software life cycle. (In the real world, the ideal of consecutive stages is seldom if ever acheived.
During the analysis stage, it might turn out that the specifications are incomplete or inconsistant.
A problem found during testing requires at least a brief return to the coding stage. If it is serious
enough, it might even require a new design.)

Large, complex programming projects are only likely to succeed if a careful, systematic
approach is adapted during all stages of the software life cycle. The systematic approach to
programming, using accepted principles of good design, is called software engineering. The
software engineer tries to efficiently construct programs that verifyably meet their specifications
and that are easy to modify if necessary. There is a wide range of "methodologies" that can be
applied to help in the systematic design of programs. (Most of these methodologies seem to
involve drawing little boxes to represent program components, with labeled arrows to represent
relationships among the boxes.)

We have been discussing object orientation in programming languages, which is relevant to the
coding stage of program development. But there are also object-oriented methodologies for
analysis and design. The question here is, how can one discover or invent the overall structure of
a program? As an example of a rather simple object-oriented approach to analysis and design,

55
Java client-server Object and classes

consider this advice: Write down a description of the problem. Underline all the nouns in that
description. The nouns should be considered as candidates for becoming classes in the program
design. Similarly, underline all the verbs. These are candidates for methods. This is your
starting point. Further analysis might uncover the need for more classes and methods, and it
might reveal how the classes can be organized into class hierarchies to take advantage of
similarities among classes.

This is perhaps a bit simple-minded, but the idea is clear: Analyze the problem to discover the
concepts that are involved, and create classes to represent those concepts. The design should
arise from the problem itself, and you should end up with a program whose structure reflects the
structure of the problem in a natural way.

Generalized Software Components

Every programmer builds up a stock of techniques and expertise expressed as snippets of code
that can be reused in new programs using the tried-and-true method of cut-and-paste: The old
code is physically copied into the new program and then edited to customize it as necessary. The
problem is that the editing is error-prone and time-consuming, and the whole enterprise is
dependent on the programmer's ability to pull out that particular piece of code from last year's
project that looks like it might be made to fit. (On the level of a corporation that wants to save
money by not reinventing the wheel for each new project, just keeping track of all the old
wheels becomes a major task.)

Well-designed classes are software components that can be reused without editing. If a class
needs to be customized, a subclass can be created, and additions or modifications can be made
in the subclass without making any changes to the original class. This can be done even if the
programmer doesn't have access to the source code of the class and doesn't know any details of
its internal, hidden implementation.

Some classes are designed specifically as a basis for making subclasses. For example, the
package java.awt includes a class called Frame. An object of this class represents a window on
the screen in a graphical user interface. A Frame object has many of the usual behaviors
associated with such windows: It can be dragged and resized, for example. All it lacks is
content. A Frame window is completely blank. A programmer who wants a window can create a
subclass of Frame and override a few methods in order to add some content to the window.
Much of the behavior of that window is inherited from Frame and does not have to be
reprogrammed.

56
Java client-server Arrays

Arrays

COMPUTERS GET A LOT OF THEIR POWER from working with data structures. A data
structure is an organized collection of related data. An object is a type of data structure
(although it is in fact more than this, since it also includes operations or methods for working
with that data). However, this type of data structure -- consisting of a fairly small number of
named instance variables -- is only one of the many different types of data structure that a
programmer might need. In many cases, the programmer has to build more complicated data
structures by linking objects together. But there is one type of data structure that is so important
and so basic that it is built into every programming language: the array.

An array is a data structure consisting of a numbered list of items, where all the items are of the
same type. In Java, the items in an array are always numbered from zero up to some maximum
value. For example, an array might contain 1000 integers, numbered from zero to 999. The
items in an array can be objects, so that you could, for example, make an array containing all the
Buttons in an applet.

This chapter discusses how arrays are created and used in Java.

Creating and Using Arrays

WHEN A NUMBER OF DATA ITEMS are chunked together into a unit, the result is a data
structure. Data structures can be quite complicated, but in many cases, a data structure consists
simply of a sequence of data items. Data structures of this simple variety can be either arrays or
records.

The term "record" is not used in Java. A record is essentially the same as a Java object that has
instance variables only, but no instance methods. Some other languages, which do not support
objects in general, nevertheless do support records. The data items in a record -- in Java, its
instance variables -- are called the fields of the record. Each item is referred to using a field
name. In Java, field names are just the names of the instance variables. The distinguishing
characteristics of a record are that the data items in the record are referred to by name and that
different fields in a record are allowed to be of different types. For example, if the class Person
is defined as:

class Person {
String name;
int id_number;
Date birthday;
int age;
}

then an object of class Person could be considered to be a record with four fields. The field
names are name, id_number, birthday, and age. Note that the fields are of various types:
String, int, and Date.

57
Java client-server Arrays

Because records are just a special type of object, I will not discuss them further.

Like a record, an array is just a sequence of items. However, where items in a record are referred
to by name, the items in an array are numbered, and individual items are referred to by their
position number. Furthermore, all the items in an array must be of the same type. So we could
define an array as a numbered sequence of items, which are all of the same type. The number of
items in an array is called the length of the array. The position number of an item in an array is
called the index of that item. The type of the individual elements in an array is called the base
type of the array.

In Java, items in an array are always numbered starting from zero. The index of the first item in
the array is zero. If the length of the array is N, then the index of the last item in the array is N-1.
Once an array has been created, its length cannot be changed.

Java arrays are objects. This has several consequences. Arrays are created using the new
operator. No variable can ever hold an array; a variable can only refer to an array. Any variable
that can refer to an array can also hold the value null, meaning that it doesn't at the moment
refer to anything. Like any object, an array belongs to a class, which like all classes is a subclass
of the class Object.

Nevertheless, even though arrays are objects, there are differences between arrays and other
types of objects, and there are a number of special language features in Java for creating and
using arrays.

Suppose that A is a variable that refers to an array. Then the item at index k in A is referred to as
A[k]. The first item is A[0], the second is A[1], and so forth. "A[k]" can be used just like a
variable. You can assign values to it, you can use it in expressions, and you can pass it as a
parameter to subroutines. All of this will be discussed in more detail below. For now, just keep
in mind the syntax

array-variable [ integer-expression ]

for referring to an item in an array.

Although every array is a member of some class, array classes never have to be defined. Once a
type exists, the corresponding array class exists automatically. If the name of the type is
BaseType, then the name of the associated array class is BaseType[]. That is, an object
belonging to the class BaseType[] is an array of items, where each item is a value of type
BaseType. The brackets, "[]", are meant to recall the syntax for referring to the individual items
in the array. "BaseType[]" can be read as "array of BaseType."

The base type of an array can be any legal Java type, that is, it can be a primitive type, an
interface, or a class. From the primitive type int, the array type int[] is derived. Each item in
an array of type int[] is a value of type int. From a class named Shape, the array type
Shape[] is derived. Each item in an array of type Shape[] is a value of type class, which can be
either null or a reference to an object belonging to the class Shape. (It might be worth

58
Java client-server Arrays

mentioning here that if ClassA is a subclass of ClassB, then ClassA[] is automatically a


subclass of ClassB[].)

Let's try to get a little more concrete about all this, using arrays of
integers as an example. Since int[] is a class, it can be used to
declare variables. For example,

int[] list;

creates a variable named list of type int[]. This variable is


capable of referring to an array of ints, but initially its value is null. The new operator can be
used to create a new array object, which can then be assigned to list. The syntax for using new
with arrays is different from the syntax you learned previously for regular classes. As an
example,

list = new int[5];

creates an array of five integers. More generally, "new BaseType[N]" creates an array
belonging to the class BaseType[]. The value N in brackets gives the length of the array, that is,
the number of items that it holds. Note that the array "knows" how long it is. The length of the
array is something like an instance variable of the array object, and in fact, the length of the
array list can be referred to as list.length. (However, you are not allowed to change the
value of list.length, so its not really a regular instance variable.) The situation produced by
the statement "list = new int[5];" can be pictured like this:

Note that the newly created array of integers is automatically filled with zeros. (By the way, the
number in brackets in "new int[5]" can be replaced by a variable or an expression whose value
is an integer.)

The items in list are referred to a list[0], list[1], list[2], list[3], and list[4]. (Note
that the index for the last item is one less than list.length.) However, array references can be
much more general than this. The brackets in an array reference can contain any expression
whose value is an integer. For example if indx is a variable of type int, then list[indx] and
list[2*indx+7] are syntactically correct references to items in the array list. And the
following loop would print all the integers in the array, list, to standard output:

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


System.out.println( list[i] );
}

59
Java client-server Arrays

The first time through the loop, i is 0, and list[i] refers to list[0]. The second time through,
i is 1, and list[i] refers to list[1]. The loop ends after printing list[4], when i becomes
equal to 5 and the continuation condition "i<list.length", is no longer true. This is a typical
example of using a loop to process an array

If you think for a moment about what the computer will do when it encounters an expression
such as "list[k]" while it is executing a program, you'll see that there are two things that can
go wrong. The expression is an attempt to access some specific element in the array referred to
by the variable list. But suppose that the value of list is null. If that case, then list doesn't
even refer to an array, and so the attempt to use the array item list[k] is an error. This is an
example of a "null pointer error." Even if list does refer to an array, it's possible that the value
of k is outside the legal range of indices for that array. This will happen if k < 0 or if k >=
list.length. In that case, once again, there is no such thing as list[k]. This is called an
"array index out of bounds" error. When you use arrays in a program, you should be careful not
to commit either of these errors.

For an array variable, just as for any variable, you can declare the variable and initialize it in a
single step. For example,

int[] list = new int[5];

The new array is filled with the default value appropriate for the base type of the array -- zero
for int and null for class types, for example. However, Java also provides a way to initialize an
array variable with a new array filled with a specified list of values. This is done with an array
initializer. For example,

int[] list = { 1, 4, 9, 16, 25, 36, 49 };

creates a new array containing the seven values 1, 4, 9, 16, 25, 36, and 49, and sets list to refer
to that new array. The value of list[0] will be 1, the value of list[1] will be 2, and so forth.

An array initializer takes the form of an array of values, separated by commas and enclosed
between braces. The length of the array does not have to be specified, because it is implicit in
the list of values. The items in an array initializer don't have to be constants. They can be
variables or expressions, provided that their values are of the appropriate type. For example, the
following declaration creates an array of eight Colors. Some of the colors are given by
expressions of the form "new Color(r,g,b)":

Color[] palette =
{
Color.black,
Color.red,
Color.pink,
new Color(0,180,0), // dark green
Color.green,
Color.blue,
new Color(180,180,255), // light blue
Color.white
}

60
Java client-server Arrays

One final note: For historical reasons, the declaration

int[] list;

can also be written as

int list[];

which is a syntax used in the languages C and C++. However, this alternative syntax does not
really make much sense in the context of Java, and it is probably best avoided. After all, the
intent is to declare a variable of a certain type, and the name of that type is " int[]". It makes
sense to follow the "type-name variable-name;" syntax for such declarations.

Array Processing

ARRAYS ARE THE MOST BASIC AND THE MOST IMPORTANT type of data structure,
and techniques for processing arrays are among the most important programming techniques
you can learn. This section introduces some of the basic ideas of array processing in general.

In many cases, processing an array means applying the same operation to each item in the array.
This is commonly done with a for loop. A loop for processing all the items in an array A has the
form:

// do any necessary initialization


for (int i = 0; i < A.length; i++) {
. . . // process A[i]
}

Suppose, for example, that A is an array of type double[]. Suppose that the goal is to add up all
the numbers in the array. An informal algorithm for doing this would be:

Start with 0;
Add A[0]; (process the first item in A)
Add A[1]; (process the second item in A)
.
.
.
Add A[ A.length - 1 ]; (process the last item in A)

Putting the obvious repetition into a loop and giving a name to the sum, this becomes:

double sum = 0; // start with 0


for (int i = 0; i < A.length; i++)
sum += A[i]; // add A[i] to the sum, for
// i = 0, 1, ..., A.length - 1

Note that the continuation condition, "i < A.length", implies that the last value of i that is
actually processed is A.length-1, which is the index of the final item in the array. It's important
to use "<" here, not "<=", since "<=" would give an array out of bounds error.

61
Java client-server Arrays

Eventually, you should almost be able to write loops similar to this one in your sleep. I will give
a few more simple examples. Here is a loop that will count the number of items in the array A
which are less than zero:

int count = 0; // start with 0 items counted


for (int i = 0; i < A.length; i++) {
if (A[i] < 0.0) // if this item is less than zero...
count++; // ...then count it
}
// At this point, the value of count is the number
// of items that have passed the test of being < 0

Replace the test "A[i] < 0.0", if you want to count the number of items in an array that satisfy
some other property. Here is a variation on the same theme. Suppose you want to count the
number of times that an item in the array A is equal to the item that follows it. The item that
follows A[i] in the array is A[i+1], so the test in this case is "A[i] == A[i+1]". But there is a
catch: This test cannot be applied when A[i] is the last item in the array, since then there is no
such item as A[i+1]. The result of trying to apply the test in this case would be an array out of
bounds error. This just means that we have to stop one item short of the final item:

int count = 0;
for (int i = 0; i < A.length-1; i++) {
if (A[i] == A[i+1])
count++;
}

Another typical problem is to find the largest number in A. The strategy is to go through the
array, keeping track of the largest number found so far. We'll store the largest number found so
far in a variable called max. As we look through the array, whenever we find a number larger
than the current value of max, we change the value of max to that larger value. After the whole
array has been processed, max is the largest item in the array overall. The only question is, what
should the original value of max be? It makes sense to start with max equal to A[0], and then to
look through the rest of the array, starting from A[1], for larger items:

double max = A[0];


for (int i = 1; i < A.length; i++) {
if (A[i] > max)
max = A[i];
}
// at this point, max is the largest item in A

(There is one subtle problem here. It's possible in Java for an array to have length zero. In that
case, A[0] doesn't exist, and the reference to A[0] in the first line gives an array out of bounds
error. However, zero-length arrays are something that you want to avoid in real problems.
Anyway, what would it mean to ask for the largest item in an array that contains no items at
all?)

As a final example of basic array operations, consider the problem of copying an array. To make
a copy of our sample array A, it is not sufficient to say

double[] B = A;

since this does not create a new array object. All it does is declare a new array variable and
make it refer to the same object to which A refers. (So that, for example, a change to A[i] will

62
Java client-server Arrays

automatically change B[i] as well.) To make a new array that is a copy of A, it is necessary to
make a new array object and to copy each of the individual items from A into the new array:

double[] B = new double[A.length]; // make a new array object,


// the same size as A
for (int i = 0; i < A.length; i++)
B[i] = A[i]; // copy each item from A to B

Partially Full Arrays

Once an array object has been created, it has a fixed size. Often, though, the number of items
that we want to store in an array changes as the program runs. Since the size of the array can't
actually be changed, a separate counter variable must be used to keep track of how many spaces
in the array are in use. (Of course, every space in the array has to contain something; the
question is, how many spaces contain useful or valid items?)

Suppose that a class Shape represents the general idea of a shape drawn on a screen, and that it
has subclasses to represent specific types of shapes such as lines, rectangles, rounded rectangles
ovals, filled-in ovals, and so forth. (Shape itself would be an abstract class.) Then an array of
type Shape[] can be declared and can hold references to objects belonging to the subclasses of
Shape. For example, the situation created by the statements

Shape[] shapes = new Shape[100]; // array to hold 100 shapes


shapes[0] = new Rect(); // put some objects in the array
shapes[1] = new Line(); // (A real program would
shapes[2] = new FilledOval(); // use some parameters here.)
int shapeCt = 3; // keep track of number of objects in array

could be illustrated as:

Such an array could be useful in a drawing program. The array could be used to hold a list of
shapes to be displayed. If the Shape class includes a redraw() method for drawing the shape,
then all the shapes in the array could be redrawn with a simple for loop:

for (int i = 0; i < shapeCt; i++)


shapes[i].redraw();

The statement "shapes[i].redraw();" calls the redraw() method belonging to the particular
shape at index i in the array. Each object knows how to redraw itself, so that repeated
executions of the statement can produce a variety of different shapes on the screen. This is nice
example both of polymorphism and of array processing.

63
Java client-server Arrays

Two-Dimensional Arrays

ANY TYPE CAN BE USED AS THE BASE TYPE FOR AN ARRAY. You can have an array
of ints, an array of Strings, or an array of Objects... And, since an array type is a first-class
Java type, you can, in particular, have an array of arrays. For example, an array of ints is said
to have the type int[]. This means that there is automatically another type, int[][], which
represents an "array of arrays of ints". Such an array is said to be a two-dimensional array. (Of
course once you have the type int[][], there is nothing to stop you from forming the type
int[][][], which represents a three-dimensional array -- and so on. However, in these notes I
won't venture beyond the second dimension.)

The command "int[][] A = new int[3][4]" declares a variable, A, of type int[][], and it
initializes that variable to refer to a newly created object. That object is an array of arrays of
ints. The notation int[3][4] indicates that there are 3 arrays-of-ints in the array A, and that
there are 4 ints in each of those arrays. However, trying to think in such terms can get a bit
confusing -- as you might have already noticed. So it is customary to think of a two-dimensional
array of items as a rectangular grid or matrix of items. The notation int[3][4] can then be
taken to describe a grid of ints with 3 rows and 4 columns. The following picture might help:

For the most part, you can ignore the reality and keep the picture of a grid in mind. Sometimes,
though, you will need to remember that each row in the grid is really an array in itself. These
rows can be referred to as A[0], A[1], and A[2]. Each row is in fact an array of type int[]. It
could, for example, be passed to a subroutine that asks for a parameter of type int[].

You can pick out a particular item from a row by adding another index. For example, A[1][3]
refers to item number 3 in row number 1. Keep in mind, of course, that both rows and columns
are numbered starting from zero. So, in the above example, A[1][3] is 5. More generally,
A[i][j] refers to the int in row number i and column number j. The 12 items in A would be
named as follows:

A[0][0] A[0][1] A[0][2] A[0][3]


A[1][0] A[1][1] A[1][2] A[1][3]

64
Java client-server Arrays

A[2][0] A[2][1] A[2][2] A[2][3]

It might be worth noting that A.length gives the number of rows of A. To get the number of
columns in A, you have to ask how many ints there are in a row; this number would be given
by A[0].length, or equivalently by A[1].length or A[2].length. (There is actually no rule
that says that the rows of an array must have the same length, and some advanced applications
of arrays use varying-sized rows. But if you use the new operator to create an array in the
manner described above, you'll get an array with equal-sized rows.)

It's possible to fill a two-dimensional array with specified items at the time it is created. Recall
that when an ordinary one-dimensional array variable is declared, it can be assigned an "array
initializer," which is just a list of values enclosed between braces, { and }. Similarly, a two-
dimensional array can be created as a list of array initializers, one for each row in the array. For
example, the array A shown in the picture above could be created with:

int[][] A = { { 1, 0, 12, -1 },
{ 7, -3, 2, 5 },
{ -5, -2, 2, 9 }
};

If no initializer is provided for an array, then when it is created it is automatically filled with the
appropriate value: zero for numbers, false for boolean, and null for objects.

A two-dimensional array can be used whenever the data being represented can be naturally
arranged into rows and columns. Often, the grid is built into the problem. For example, a chess
board is a grid with 8 rows and 8 columns. If a class named ChessPiece is available to represent
individual chess pieces, then the contents of a chess board could be represented by a two-
dimensional array

ChessPiece[][] board = new ChessPiece[8][8];

The grid is not always so visually obvious in a problem. Consider a company that owns 25
stores. Suppose that the company has data about the profit earned at each store for each month
in the year 1995. If the stores are numbered from 0 to 24, and if the twelves months from
January '95 through December '95 are numbered from 0 to 11, then the profit data could be
stored in an array, profit, constructed as follows:

double[][] profit = new double[25][12];

profit[3][2] would be the amount of profit earned at store number 3 in March, and more
generally, profit[storeNum][monthNum] would be the amount of profit earned in store
number storeNum in month number month. In this example, the one-dimensional array
profit[storeNum] has a very useful meaning: It is just the profit data for one particular store
for the whole year.

65
Java client-server Arrays

Just as in the case of one-dimensional arrays, two-dimensional arrays are often processed using
for statements. To process all the items in a two-dimensional array, you have to use one for
statement nested inside another. If the array A is declared as

int[][] A = new int[3][4];

then you could store a zero into each location in A with:

for (int row = 0; row < 3; row++) {


for (int column = 0; row < 4; column++) {
A[row][column] = 0;
}
}

The first time the outer for loop executes (with row = 0), the inner four loop fills in the four
values in the first row of A, namely A[0][0] = 0, A[0][1] = 0, A[0][2] = 0, and A[0][3] =
0. The next execution of the outer for loop fills in the second row of A. And the third and final
execution of the outer loop fills in the final row of A.

Similarly, you could add up all the items in A with:

int sum = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; i++)
sum = sum + A[i][j];

If we did the same thing with the profit array discussed above, this example might seem a little
more interesting. The sum would be the total profit earned by the company over the course of
the entire year in all of its twenty-five stores.

This profit example demonstrates that sometimes it is necessary to process a single row or a
single column of an array. For example, to compute the total profit earned by the company in
December, that is, in month number 11, you could use the loop:

double decemberProfit = 0.0;


for (storeNum = 0; storeNum < 25; storeNum++)
decemberProfit += profit[storeNum][11];

We could extend this idea to create a one-dimensional array that contains the total profit for
each month of the year:

double[] monthlyProfit = new double[12];

for (int month = 0; month < 12; month++) {


// compute the total profit from all stores in this month
monthlyProfit[month] = 0.0;
for (int store = 0; store < 25; store++)
monthlyProfit[month] += profit[store][month];
}

As a final example of processing two-dimensional arrays, suppose that we wanted to know


which store generated the most profit over the course of the year. To do this, we have to add up
the monthly profits for each store. In array terms, this means that we want to find the sum of
each row in the array. As we do this, we want to keep track of which row produces the largest
total.

66
Java client-server Arrays

double maxProfit; // maximum profit earned by a store


int bestStore; // the number of the store with the
// maximum profit

double total = 0.0; // total profit for one store;

// first compute the profit from store number 0

for (int month = 0; month < 12; month++)


total += profit[0][month];

bestStore = 0; // start by assuming that the best


maxProfit = total; // store is store number 0

// Now, go through the other stores, and whenever you


// find one with a bigger profit than maxProfit, revise
// the assumptions about bestStore and maxProfit

for (store = 1; store < 25; store++) {


total = 0.0;
for (month = 0; month < 12; month++)
total += profit[store][month];
if (total > maxProfit) {
maxProfit = total; // best profit seen so far!
bestStore = store; // and it came from this store
}
}

// At this point, maxProfit is the best profit of any


// of the 25 stores, and bestStore is a store that
// generated that profit. (Note that there could be
// other stores that generated the same profit.)

67
Java client-server I/O and exceptions

Advanced I/O and Exceptions

IT IS AN UNFORTUNATE TRUTH that sometimes when programs are running, errors occur.
There are programming errors, such as an array index that is out of the actual range of indices
for the array or an attempt to divide a number by zero. Then there are errors over which the
programmer has less control or none at all, such as when the user types in a word when the
program is expecting a number or when the system runs out of memory.

Often, when an error occurs, the program simply crashes (or worse, goes on to produce incorrect
results). However, Java provides a neat mechanism for handling errors and other "exceptional
conditions." This exception-handling capability is one of the topics of this chapter. The other
topic is Java's built-in input/output facilities, which are implemented through objects called
Streams. It turns out to be impossible to use these objects without some understanding of
exceptions.

Exceptions, try, and catch

GETTING A PROGRAM TO WORK UNDER IDEAL circumstances is usually a lot easier


than making the program robust. A robust program is one that can survive unusual or
"exceptional" circumstances without crashing. For example, a program that does a calculation
that involves taking a square root will crash if it tries to take the square root of a negative
number. A robust program must anticipate the possibility of a negative number and guard
against it. This could be done with an if statement:

if (disc >= 0) {
r = Math.sqrt(disc); // Since disc >= 0, this must be OK
}
else {
... // Do something to handle the case where disc < 0
}

We would say that the statement "r = Math.sqrt(disc);" has the precondition that disc >=
0. A precondition is a condition that must be true at a certain point in the execution of a
program, if that program is to continue without error and give a correct result. One approach to
writing robust programs is to rigorously apply the rule, "At each point in the program identify
any preconditions and make sure that they are true" -- either by using if statements to check
whether they are true, or by verifying that the required preconditions are consequences of what
the program has already done. An example of the latter case would be the sequence of
statements

x = Math.abs(x);
// At this point, we know that x >= 0, since
// the absolute value of any number is defined to be >= 0
y = Math.sqrt(x); // Since x >= 0, this must be OK

68
Java client-server I/O and exceptions

There are some problems with this approach. It is difficult and sometimes impossible to
anticipate all the possible things that might go wrong. Furthermore, trying to anticipate all the
possible problems can turn what would otherwise be a straightforward program into a messy
tangle of if statements.

Java (like its cousin, C++) provides a neater, more structured alternative method for dealing
with possible errors that can occur while a program is running. The method is referred to as
exception-handling. The word "exception" is meant to be more general than "error." It includes
any circumstance that arises as the program is executed which is meant to be treated as an
exception to the normal flow of control of the program. An exception might be an error, or it
might just be a special case that you would rather not have clutter up your elegant algorithm.

When an exception occurs during the execution of a program, we say that the exception is
thrown. When this happens, the normal flow of the program is thrown off-track, and the
program is in danger of crashing. However, the crash can be avoided if the exception is caught
and handled in some way. An exception can be thrown in one part of a program and caught in a
completely different part. An exception that is not caught will generally cause the program to
crash.

By the way, since Java programs are executed by a Java interpreter, having a program crash
simply means that it terminates abnormally and prematurely. It doesn't mean that the Java
interpreter will crash. In effect, the interpreter catches any exceptions that are not caught by the
program. The interpreter responds by terminating the program. In many other languages, a
crashed program will often crash the entire system and freeze the computer until it is restarted.
With Java, such system crashes should be impossible -- which means that when they happen,
you have the satisfaction of blaming the system rather than your own program.

When an exception occurs, it is actually an object that is thrown. This object can carry
information (in its instance variables) from the point where the exception occurred to the point
where it is caught and handled. This information typically includes an error message describing
what happened to cause the exception, but it could also include other data. The object thrown by
an exception must be an instance of the class Throwable or of one of its subclasses. In general,
each different type of exception is represented by its own subclass of Throwable. Throwable
has two direct subclasses, Error and Exception. These two subclasses in turn have many other
predefined subclasses. In addition, a programmer can create new exception classes to represent
new types of exceptions.

Most of the subclasses of the class Error represent serious errors within the Java virtual
machine that should ordinarily cause program termination because there is no reasonable way to
handle them. You should not try to catch and handle such errors. An example is the
ClassFormatError, which occurs when the Java virtual machine finds some kind of illegal data
in a file that is supposed to contain a compiled Java class. If that class was supposed to be part
of the program, then there is really no way for the program to proceed.

Subclasses of Exception represent exceptions that are meant to be caught. In many cases, these
are exceptions that might naturally be called "errors," but they are errors in the program, or in
input data, that a programmer can anticipate and possibly respond to in some reasonable way.
(You have to avoid the temptation of saying, "Well, I'll just put a thing here to catch all the
errors that might occur, so my program won't crash." If you don't have a reasonable way to

69
Java client-server I/O and exceptions

respond to the error, it's usually best just to terminate the program, because trying to go on will
probably only lead to worse things down the road -- in the worst case, a program that gives an
incorrect answer without giving you any indication that the answer might be wrong!)

The class Exception has its own subclass, RuntimeException. This class groups together
many common exceptions such as ArithmeticException, which occurs for example when
there is an attempt to take the square root of a negative number, and NullPointerException,
which occurs when there is an attempt to use a null reference in a context when an actual object
reference is required. RuntimeExceptions and Errors share the property that a program can
simply ignore the possibility that they might occur. For example, a program does this every time
it uses Math.sqrt() without making arrangements to catch a possible ArithmeticException.
For all other exception classes besides Error, RuntimeException, and their subclasses,
exception-handling is "mandatory" in a sense that I'll discuss below.

The following diagram is a class hierarchy showing the class Throwable and just a few of its
subclasses. Classes that require mandatory exception-handling are shown in red.

To handle exceptions in a Java program, you need a try statement. The idea is that you tell the
computer to "try" to execute some commands. If it succeeds, all well and good. But if an
exception is thrown during the execution of those commands, you can catch the exception and
handle it. For example,

try {
d = Math.sqrt(b*b - 4*a*c);
r1 = (-b + d) / (2*a);
r2 = (-b - d) / (2*a);
System.out.println("The roots are " + r1 + " and " + r2);
}
catch ( ArithmeticException e ) {
System.out.println("There are no real roots.");
}

The computer tries to execute the block of statements following the word "try". If no exception
occurs during the execution of this block, then the "catch" part of the statement is simply
ignored. However, if an ArithmeticException occurs, then the computer jumps immediately
to the block of statements labeled "catch (ArithmeticException e)". This block of

70
Java client-server I/O and exceptions

statements is said to be a exception handler for ArithmeticExceptions. By handling the


exception in this way, you prevent it from crashing the program.

You might notice that there are some other possible sources of error in this try statement. For
example, if the value of the variable a is zero, you would probably expect the division by zero to
produce an error. The reality is a bit surprising: If the numbers that are being divided are of type
int, then division by zero will indeed throw an ArithmeticException. However, no arithmetic
operations with floating-point numbers will ever produce an exception. Instead, the double type
includes a special value called not-a-number to represent the result of an illegal operation. When
this value is printed out, it is written as "NaN" -- which is hardly what you would like to see in
the output!

Another possible error in this example is even more subtle: If the value of the variable console
is null, then a NullPointerException will be thrown when the console is referenced in the
last line of the try block. You could catch such an exception by adding another catch clause to
the try statement:

try {
d = Math.sqrt(b*b - 4*a*c);
r1 = (-b + d) / (2*a);
r2 = (-b - d) / (2*a);
System.out.println("The roots are " + r1 + " and " + r2);
}
catch ( ArithmeticException e ) {
System.out.println("There are no real roots.");
}
catch ( NullPointerException e ) {
System.out.println("Programming error! " + e.getMessage());
}

I haven't tried to use the console in the handler for NullPointerExceptions, because it's
likely that the value of console is itself the problem. In fact, it would almost surely be better in
this case just to let the program crash! However, this does show how multiple catch clauses can
be used with one try block. This example also shows what that little "e" is doing in the catch
clauses. The e is actually a variable name. (You can use any name you like.) Recall that when an
exception occurs, it is actually an object that is thrown. Before executing a catch clause, the
computer sets this variable to refer to the exception object that is being caught. This object
contains information about the exception. In particular, every exception object includes an error
message, with can be retrieved using the object's getMessage() method, as is done in the above
example.

The example I've given here is not particularly realistic. You are more likely to use an if
statement than to use exception-handling to guard against taking the square root of a negative
number. You would certainly resent it if the designers of Java forced you to set up a try...catch
statement every time you wanted to take a square root. This is why handling of potential
RuntimeExceptions is not mandatory. There are just too many things that might go wrong!
(This also shows that exception-handling does not solve the problem of program robustness. It
just gives you a tool that will in many cases let you approach the problem in a more organized
way.)

The syntax of a try statement is a little more complicated than I've indicated so far. The syntax
can be described as

71
Java client-server I/O and exceptions

try
statement
optional-catch-clauses
optional-finally-clause

where, as usual, the statement can be a block of statements enclosed between { and }. The try
statement can include zero or more catch clauses and, optionally, a finally clause. (The
statement must include either a finally clause or at least one catch clause.) The syntax for a
catch clause is

catch ( exception-class-name variable-name )


statement

and the syntax for a finally clause is

finally
statement

The semantics of the finally clause is that the statement or block of statements in the finally
clause is guaranteed to be executed as the last step in the execution of the try statement, whether
or not any exception occurs and whether or not any exception that does occur is caught and
handled. The finally clause is meant for doing essential cleanup that under no circumstances
should be omitted. You'll see an example of this later in the chapter.

There are times when it makes sense for a program to deliberately throw an exception. This is
the case when the program discovers some sort of exceptional or error condition, but there is no
reasonable way to handle the error at the point where the problem is discovered. The program
can throw an exception in the hope that some other part of the program will catch and handle the
exception.

To throw an exception, use a throw statement. The syntax of the throw statement is

throw exception-object ;

The exception-object must be an object belonging to one of the subclasses of Throwable.


Usually, it will in fact belong to one of the subclasses of Exception. In most cases, it will be a
newly constructed object created with the new operator. For example:

throw new ArithmeticException("Division by zero");

The parameter in the constructor becomes the error message in the exception object.

When an exception is thrown during the execution of a subroutine and the exception is not
handled in the same subroutine, then the subroutine is terminated (after the execution of any
pending finally clauses). Then the routine that called that subroutine gets a chance to handle
that exception. If it doesn't do so, then it also is terminated and the routine that called it gets the
next shot at the exception. The exception will crash the program only if it passes up through the
entire chain of subroutine calls without being handled.

A subroutine that can throw an exception can announce this fact by adding the phrase "throws
exception-class-name" to the specification of the routine. For example:

72
Java client-server I/O and exceptions

static double root(double A, double B, double C) throws ArithmeticException {


// returns the larger of the two roots of
// the quadratic equation A*x*x + B*x + C = 0
double d = Math.sqrt(b*b - 4*a*c); // might throw an exception!
if (a == 0)
throw new ArithmeticException("Division by zero.");
return (-b + d) / (2*a);
}

In this case, declaring that root() can throw an ArithmeticException is just a courtesy to
potential users of this routine. This is because handling of ArithmeticExceptions is not
mandatory. A routine can throw an ArithmeticException without announcing the possibility.
And the user of such a routine is free either to catch or ignore such an exception.

For those exception classes that require mandatory handling, the situation is different. If a
routine can throw such an exception, that fact must be announced in a throws clause in the
routine definition. If you use such a routine you should use it inside a try statement that catches
the exception. (If you don't do this, then, since calling the offending routine might implicitly
throw the same exception in your own routine, you have to add an appropriate throws clause to
your own routine.) If you don't handle the exception one way or another, it will be considered a
syntax error, and the compiler will not accept your program. Exception-handling is mandatory in
this sense for any exception class that is not a subclass of either Error or RuntimeException.

Among the exceptions that require mandatory handling are several that can occur when using
Java's input/output routines. This means that you can't even use these routines unless you
understand something about exception-handling. The rest of this chapter deals with input/output
and uses exception-handling extensively.

Streams

WITHOUT THE ABILITY TO INTERACT WITH the rest of the world, a program would be
useless. The interaction of a program with the rest of the world is referred to as input/output or
I/O. Historically, one of the hardest parts of programming language design has been coming up
with good facilities for doing input and output. A computer can be connected to many different
types of input and output devices. If a programming language had to deal with each type of
device as a special case, the complexity would be overwhelming. One of the major
achievements in the history of programming has been to come up with good abstractions for
representing I/O devices. In Java, the I/O abstractions are called streams. This section is an
introduction to streams, but it is not meant to cover them in full detail. See the official Java
documentation for more information. Also note that a few important general facts about streams
are deferred until the next two sections of this chapter, where they are actually used.

There are two types of streams, input streams and output streams. In Java, these are represented
by the classes InputStream and OutputStream. A program can read data from an
InputStream. It can write data to an OutputStream. Each of these classes have several
subclasses that provide various types of I/O facilities. The stream classes are defined in the
package java.io. You must import the classes from this package if you want to use them in
your program.

73
Java client-server I/O and exceptions

Streams are not used in Java's graphical user interface, which has its own form of I/O. But they
necessary for working with files (using the classes FileInputStream and FileOutputStream)
and for doing communication over a network. They can be also used for communication
between two concurrently running threads.

Java's standard packages include a standard input stream and a standard output stream, which
are meant for basic communication with the user. (In fact, the proper definition of a "user" is: a
particularly slow and unreliable input/output device that is attached by default to the standard
input and output streams.) These standard streams are objects belonging to the classes
InputStream and PrintStream. PrintStream is a subclass of OutputStream. The standard
stream objects are referenced by the static variables System.in and System.out in the class
java.lang.System. You have already seen how methods belonging to the object System.out
can be used to output information to the user. Similarly, System.in can be used to read
characters typed by the user. (Note that as of this writing, System.in is not functional on
Macintosh computers.)

The beauty of the stream abstraction is that it is as easy to write data to a file or to send data
over a network as it is to print information on the screen. In fact, you can use a PrintStream
object in all three cases and use simple PrintStream methods like println().

The basic classes InputStream and OutputStream provide only very primitive I/O operations,
which treat data as a stream of uninterpreted bytes. InputStream includes the instance method

public int read() throws IOException

for reading one byte of data (a number in the range 0 to 255) from an input stream. If the end of
the input stream is encountered, the read() method will return the value -1 instead. In the case
where the data being read is ordinary ASCII text, the value returned can be typecast to type
char (after checking to make sure that the value is not -1, of course). InputStream provides no
convenient methods for reading other types of data from a stream. Note that read() will throw
an IOException if some error is encountered during the read operation. Since IOException is
one of the exception classes that requires mandatory exception-handling, this means that you
can't use the read() method except inside a try statement or in a subroutine that is itself
declared with a "throws IOException" clause.

The primitive output operation provided by the class OutputStream is

public void write(int b) throws IOException

This method outputs one byte of data to the output stream. The parameter b should be in the
range 0 to 255. (To be more exact, no matter what the value of b, only the last 8 bits of the 32-
bit integer value are output.)

You will probably use these primitive I/O methods only rarely (although the read() method is
worth using in some cases). In fact, you cannot even directly create instances of InputStream
and OutputStream, since they are abstract classes.

The subclasses of the two basic steam classes provide more useful I/O methods. One of the neat
things about Java's I/O package is that it lets you add capabilities to a steam by "wrapping" it in

74
Java client-server I/O and exceptions

another object that provides those capabilities. The wrapper object is still considered to be a
stream, so you can read from or write to it -- but you can do so using fancier operations than
those available for basic streams. Objects that can be used as wrappers in this way belong to
subclasses of FilterInputStream or FilterOutputStream. By writing new subclasses of
these classes, you can make your own I/O filters to provide any style of I/O that you want.

For example, PrintStream is a subclass of FilterOutputStream that provides convenient


methods for outputting ASCII-text representations of all of Java's basic data types. If you have
an object belonging to the OutputStream class, or any of its subclasses, and you would like to
use PrintStream methods to write to that OutputStream, all you have to do is wrap the
OutputStream in a PrintStream object. You do this by constructing a new PrintStream
object, using the OutputStream as input to the constructor. For example, if dataSink is of type
OutputStream then you could say

PrintStream printableDataSink = new PrintStream(dataSink);

When you output data to printableDataSink, using PrintStream's advanced data output
methods, that data will go to exactly the same place as data written directly to dataSink. You've
just provided a better interface to the same output stream.

For the record, the output methods of the PrintStream class include:

public void print(String s) // methods for outputting


public void print(char c) // standard data types
public void print(int i) // to the stream
public void print(long l)
public void print(float f)
public void print(double d)
public void print(boolean b)

public void println() // output a carriage return to the stream

public void println(String s) // these methods are identical


public void println(char c) // to the previous set,
public void println(int i) // except that the output
public void println(long l) // value is followed by
public void println(float f) // a carriage return
public void println(double d
public void println(boolean b)

Note that none of these methods will ever throw an IOException. Instead, the PrintStream
class includes the method

public boolean checkError()

which will return true if any error has been encountered while writing to the stream. The
PrintStream class catches any IOExceptions internally, and sets the value of an internal error
flag if one occurs. The checkError() method can be used to check the error flag. This allows
you to use PrintStream methods without worrying about catching exceptions. On the other
hand, to write a fully robust program, you should call CheckError() to test for possible errors
every time you use a PrintStream method.

75
Java client-server I/O and exceptions

You might wonder why the PrintStream methods are not simply included in the basic output
class, OutputStream. The reason is that PrintStream makes one very big assumption: that the
output stream is meant to be human-readable. The PrintStream methods convert the internal
binary-number representations of data into ASCII-text representations that are meaningful to
human readers. However, if the output data is really meant to be read later by a computer, the
computer will have to convert the data back into its internal format. These conversions between
internal format and ASCII text are wasteful and inefficient for data that is never meant to be
read by humans. In fact, many data files are not written in human-readable form. Such files look
like gibberish if you try to interpret them as ASCII text. Since many streams do not use ASCII-
text representation of data, methods for working with such representations are not included in
the basic I/O stream classes.

The java.io package includes a class, DataOutputStream that can be used for writing data to
streams in internal, binary-number format. It provides methods for outputting all the basic Java
types in machine-readable format. As with PrintStream, you can wrap any OutputStream in a
DataOuputStream object. This makes it possible to write machine-readable data to that
OutputStream.

For inputing such machine-readable data, java.io provides the class DataInputStream. You
can wrap any InputStream in a DataInputStream object to provide it with machine-readable
data-input capabilities. Data written by a DataOutputSteam is guaranteed to be in a format that
can be read by a DataInputStream, and vice versa. This is true even if the data stream is
created on one type of computer and read on another type of computer. The cross-platform
compatibility of binary data is a major aspect of Java's platform independence.

There so many streams, however only some of the will be discussed further as these can be used
when working with data over a network to implement a client-server applications. The best
overview on streams can be found at
https://docs.oracle.com/javase/tutorial/essential/io/index.html. Java's I/O operations is more
complicated than C/C++ to support internationalization (i18n). Java internally stores characters
(char type) in 16-bit UCS-2 character set. But the external data source/sink could store
characters in other character set (e.g., US-ASCII, ISO-8859-x, UTF-8, UTF-16, and many
others), in fixed length of 8-bit or 16-bit, or in variable length of 1 to 4 bytes. As a consequence,
Java needs to differentiate between byte-based I/O for processing raw bytes or binary data, and
character-based I/O for processing texts made up of characters.

Some of the streams handeled by Java are:

 Byte Streams handle I/O of raw binary data.


 Character Streams handle I/O of character data, automatically handling translation to
and from the local character set.
 Buffered Streams optimize input and output by reducing the number of calls to the
native API.
 Scanning and Formatting allows a program to read and write formatted text.
 Data Streams handle binary I/O of primitive data type and String values.
 Object Streams handle binary I/O of objects.

76
Java client-server I/O and exceptions

Byte streams are used to read/write raw bytes serially from/to an external device. All the byte
streams are derived from the abstract superclasses InputStream and OutputStream, as
illustrated in the class diagram.

The DataInputStream and DataOutputStream can be stacked on top of any InputStream and
OutputStream to parse the raw bytes so as to perform I/O operations in the desired data format,
such as int and double.

To use DataInputStream for formatted input, you can chain up the input streams as follows:

DataInputStream in = new DataInputStream(


new BufferedInputStream(
new FileInputStream("in.dat")));

DataInputStream implements DataInput interface, which provides methods to read formatted


primitive data and String, such as:

// 8 Primitives
public final int readInt() throws IOExcpetion; // Read 4 bytes and
convert into int
public final double readDouble() throws IOExcpetion; // Read 8 bytes and
convert into double
public final byte readByte() throws IOExcpetion;
public final char readChar() throws IOExcpetion;
public final short readShort() throws IOExcpetion;
public final long readLong() throws IOExcpetion;
public final boolean readBoolean() throws IOExcpetion; // Read 1 byte.
Convert to false if zero
public final float readFloat() throws IOExcpetion;

77
Java client-server I/O and exceptions

public final int readUnsignedByte() throws IOExcpetion; // Read 1 byte in


[0, 255] upcast to int
public final int readUnsignedShort() throws IOExcpetion; // Read 2 bytes in
[0, 65535], same as char, upcast to int
public final void readFully(byte[] b, int off, int len) throws IOException;
public final void readFully(byte[] b) throws IOException;

// Strings
public final String readLine() throws IOException;
// Read a line (until newline), convert each byte into a char - no
unicode support.
public final String readUTF() throws IOException;
// read a UTF-encoded string with first two bytes indicating its UTF
bytes length

public final int skipBytes(int n) // Skip a number of bytes

Similarly, you can stack the DataOutputStream as follows:

DataOutputStream out = new DataOutputStream(


new BufferedOutputStream(
new FileOutputStream("out.dat")));

DataOutputStream implements DataOutput interface, which provides methods to write


formatted primitive data and String. For examples,

// 8 primitive types
public final void writeInt(int i) throws IOExcpetion; // Write the int
as 4 bytes
public final void writeFloat(float f) throws IOExcpetion;
public final void writeDoube(double d) throws IOExcpetion; // Write the
double as 8 bytes
public final void writeByte(int b) throws IOExcpetion; // least-
significant byte
public final void writeShort(int s) throws IOExcpetion; // two lower bytes
public final void writeLong(long l) throws IOExcpetion;
public final void writeBoolean(boolean b) throws IOExcpetion;
public final void writeChar(int i) throws IOExcpetion;

// String
public final void writeBytes(String str) throws IOExcpetion;
// least-significant byte of each char
public final void writeChars(String str) throws IOExcpetion;
// Write String as UCS-2 16-bit char, Big-endian (big byte first)
public final void writeUTF(String str) throws IOException;
// Write String as UTF, with first two bytes indicating UTF bytes length

public final void write(byte[] b, int off, int len) throws IOException
public final void write(byte[] b) throws IOException
public final void write(int b) throws IOException // Write the least-
significant byte

import java.io.*;
public class DataIOStream {
public static void main(String[] args) {
String filename = "out.dat";
String message = "Hi, Vπsile";

// Write primitives to an output file


try (DataOutputStream out =

78
Java client-server I/O and exceptions

new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(filename)))) {
out.writeByte(127);
out.writeShort(0xFFFF); // -1
out.writeInt(0xABCD);
out.writeLong(0x1234_5678); // JDK 7 syntax
out.writeFloat(11.22f);
out.writeDouble(55.66);
out.writeBoolean(true);
out.writeBoolean(false);
for (int i = 0; i < message.length(); ++i) {
out.writeChar(message.charAt(i));
}
out.writeChars(message);
out.writeBytes(message);
out.flush();
} catch (IOException ex) {
ex.printStackTrace();
}

// Read raw bytes and print in Hex


try (BufferedInputStream in =
new BufferedInputStream(
new FileInputStream(filename))) {
int inByte;
while ((inByte = in.read()) != -1) {
System.out.printf("%02X ", inByte); // Print Hex codes
}
System.out.printf("%n%n");
} catch (IOException ex) {
ex.printStackTrace();
}

// Read primitives
try (DataInputStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(filename)))) {
System.out.println("byte: " + in.readByte());
System.out.println("short: " + in.readShort());
System.out.println("int: " + in.readInt());
System.out.println("long: " + in.readLong());
System.out.println("float: " + in.readFloat());
System.out.println("double: " + in.readDouble());
System.out.println("boolean: " + in.readBoolean());
System.out.println("boolean: " + in.readBoolean());

System.out.print("char: ");
for (int i = 0; i < message.length(); ++i) {
System.out.print(in.readChar());
}
System.out.println();

System.out.print("chars: ");
for (int i = 0; i < message.length(); ++i) {
System.out.print(in.readChar());
}
System.out.println();

System.out.print("bytes: ");
for (int i = 0; i < message.length(); ++i) {
System.out.print((char)in.readByte());

79
Java client-server I/O and exceptions

}
System.out.println();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

FileReader and FileWriter are concrete implementations to the abstract superclasses


Reader and Writer, to support I/O from disk files. FileReader/FileWriter assumes that the
default character encoding (charset) is used for the disk file. BufferedReader and
BufferedWriter can be stacked on top of FileReader/FileWriter or other character streams
to perform buffered I/O, instead of character-by-character. BufferedReader provides a new
method readLine(), which reads a line and returns a String (without the line delimiter). Lines
could be delimited by "\n" (Unix), "\r\n" (Windows), or "\r" (Mac).

import java.io.*;
import java.util.StringTokenizer;

public class LineTokenStreamIO {


public static void main(String[] args) {
int l = 0;
String linie = null;
PrintWriter fout = null;
BufferedReader fin = null;
String s;
int x;
double y1, y2;

try {
fout = new PrintWriter(new FileWriter("linefile.txt"));

for (int i = 0; i < 5; ++i)


fout.println("Linia " + (i + 1) + "," + i + "," + Math.sin(i)
+
"," + Math.cos(i));
fout.close();

fin = new BufferedReader(new FileReader("linefile.txt"));


while ((linie = fin.readLine()) != null) {
System.out.println("S-a citit linia: "+linie);

//extragerea simbolurilor pe baza separatoului '


StringTokenizer t = new StringTokenizer(linie,",");
s = t.nextToken();
x = Integer.parseInt(t.nextToken());
y1 = Double.parseDouble(t.nextToken());
y2 = Double.parseDouble(t.nextToken());
System.out.println(s+x+y1+y2);

++l;
}
System.out.println("S-au citit si prelucrat" + l + " linii");

fin.close();

} catch (IOException e) {
}
}

80
Java client-server I/O and exceptions

Files

THE DATA AND PROGRAMS IN A COMPUTER'S MAIN MEMORY survives only as long
as the power is on. For more permanent storage, computers use files, which are collections of
data stored on the computer's hard disk, on a floppy disk, on a CD-ROM, or on some other type
of storage device. Files are organized into directories (sometimes called "folders"). A directory
can hold other directories, as well as files. Both directories and files have names that are used to
identify them.

Programs can read data from existing files. They can create new files and can write data to files.
In Java, input and output is generally done using streams. Data is read from a file using an
object belonging to the class FileInputStream, which is a subclass of InputStream. Similarly,
data is written to a file through an object of type FileOutputStream, a subclass of
OutputStream.

It's worth noting right at the start that applets which are downloaded over a network connection
are generally not allowed to access files. This is a security consideration. You can download and
run an applet just by visiting a Web page with your browser. If downloaded applets had access
to the files on your computer, it would be easy to write an applet that would destroy all the data
on any computer that downloads it. To prevent such possibilities, there are a number of things
that downloaded applets are not allowed to do. Accessing files is one of those forbidden things.
Standalone programs written in Java, however, have the same access to your files as any other
program. When you write a standalone Java application, you can use all the file operations
described in this section.

The FileInputStream class has a constructor which takes the name of a file as a parameter and
creates an input stream that can be used for reading from that file. This constructor will throw an
exception of type FileNotFoundException if the file doesn't exist. This exception type requires
mandatory exception handling, so you have to call the constructor in a try statement (or inside a
routine that is declared to throw FileNotFoundException). For example, suppose you have a
file named "data.txt", and you want your program to read data from that file. You could do
the following to create an input stream for the file as follows:

FileInputStream data; // declare the variable before the


// try statement, or else the variable
// is local to the try block

try {
data = new FileInputStream("data.dat"); // create the stream
}
catch (FileNotFoundException e) {
... // do something to handle the error -- maybe, end the program
}

Once you have successfully created a FileInputStream, you can start reading data from it. But
since FileInputStreams have only the primitive input methods inherited from the basic
InputStream class, you will probably want to wrap your FileInputStream in either a

81
Java client-server I/O and exceptions

DataInputStream object or an AsciiInputStream object. You can use the built-in


DataInputStream class if you want to read data in binary, machine-readable format.

Working with output files is no more difficult than this. You simply create an object belonging
to the class FileOutputStream. You will probably want to wrap this output stream in an object
of type DataOutputStream (for binary data) or PrintStream (for ASCII text). For example,
suppose you want to write data to a file named "result.dat". Since the constructor for
FileOutputStream can throw an exception of type IOException, you should use a try
statement:

PrintStream result;

try {
result = new PrintStream(new FileOutputStream("result.dat"));
}
catch (IOException e) {
... // handle the exception
}

If no file named result.dat exists, a new file will be created. If the file already exists, then the
current contents of the file will be erased and replaced with the data that your program writes to
the file. An IOException might occur if, for example, you are trying to create a file on a disk
that is "write-protected," meaning that it cannot be modified.

After you are finished using a file, it's a good idea to close the file, to tell the operating system
that you are finished using it. (If you forget to do this, the file will probably be closed
automatically when the program terminates or when the file stream object is garbage collected,
but it's best to close a file as soon as you are done with it.) You can close a file by calling the
close() method of the associated file stream. Once a file has been closed, it is no longer
possible to read data from it or write data to it, unless you open it again as a new stream. (Note
that for most stream classes, the close() method can throw an IOException, which must be
handled; however, PrintStream override this method so that it cannot throw such exceptions.)

As a complete example, here is a program that will read numbers from a file named data.dat,
and will then write out the same numbers in reverse order to another file named result.dat. It
is assumed that data.dat contains only one number on each line, and that there are no more
than 1000 numbers altogether. Exception-handling is used to check for problems along the way.
At the end of this program, you'll find an example of the use of a finally clause in a try
statement. When the computer executes a try statement, the commands in its finally clause
are guaranteed to be executed, no matter what.

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class ReverseFile {


public static void main(String[] args) {
DataInputStream data; // stream for reading data
PrintStream result; // stream for output

double[] number = new double[1000]; // array to hold the numbers

82
Java client-server I/O and exceptions

// read from the input file


int numberCt; // number of items stored in the array

try {
data = new DataInputStream(new FileInputStream("out.dat"));
} catch (FileNotFoundException e) {
System.out.println("Can't find file out.dat!");
return; // end the program by returning from main()
}
try {
result = new PrintStream(new FileOutputStream("result.dat"));
} catch (IOException e) {
System.out.println("Can't open file result.dat!");
System.out.println(e.toString());
try {
data.close();
} catch (IOException f) {
System.out.println("out.data was closed > " +
f.getMessage());
} // close the input file
return; // end the program
}
numberCt = 0;
try {
// read the data from the input file,
double inDouble;
while ((inDouble = data.readDouble()) != -1) {
number[numberCt] = inDouble;
System.out.println(numberCt + " " + inDouble);
numberCt++;
}
} catch (EOFException e) {
//
} catch (IOException e) {
System.out.println("Input/Output errorOF. > " + e.getMessage());
}
try {
// then output the numbers in reverse order
for (int i = numberCt - 1; i >= 0; i--)
result.println(number[i]);
} catch (IndexOutOfBoundsException e) {
// must have tried to put too many numbers in the array
System.out.println("Too many numbers in data file.");
} finally {
// finish by closing the files, whatever else may have happened
try {
data.close();
} catch (IOException e) {
System.out.println("out.dat closed succesfuly.");
}
result.close();
}
} // end of main()
} // end of class

The data in the out.dat must be created with the following code:

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

83
Java client-server I/O and exceptions

public class CreateDataFile {


static final String dataFile = "out.dat";
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
public static void main(String[] args) throws IOException {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i ++) {
out.writeDouble(prices[i]);
}
out.close();
}
}

File Names, Directories, and File Dialogs

The subject of file names is actually more complicated than I've let on so far. To fully specify a
file, you have to give both the name of the file and the name of the directory where that file is
located. A simple file name like "data.dat" or "result.dat" is taken to refer to a file in a directory
that is called the current directory (or "default directory" or "working directory"). The current
directory is not a permanent thing. It can be changed by the user or by a program. Files not in
the current directory must be referred to by a path name, which includes both the name of the
file and information about the directory where it can be found.

To complicate matters even further, there are two types of path names, absolute path names and
relative path names. An absolute path name uniquely identifies one file among all the files
available to the computer. It contains full information about which directory the file is in and
what its name is. A relative path name tells the computer how to locate the file, starting from the
current directory.

Unfortunately, the syntax for file names and path names varies quite a bit from one type of
computer to another. Here are some examples:

 data.dat -- on any computer, this would be a file named data.dat in the current
directory.
 /home/eck/java/examples/data.dat -- This is an absolute path name in the UNIX
operating system. It refers to a file named data.dat in a directory named examples, which
is in turn in a directory named java,....
 C:\eck\java\examples\data.dat -- An absolute path name on a DOS or Windows
computer.
 Hard Drive:java:examples:data.data -- Assuming that "Hard Drive" is the name of
a disk drive, this would be an absolute path name on a Macintosh computer.
 examples/data.dat -- a relative path name under UNIX. "Example" is the name of a
directory that is contained within the current directory, and data.data is a file in that
directory. The corresponding relative path names for Windows and Macintosh would be
examples\data.dat and examples:data.dat.

Similarly, the rules for determining which directory is the current directory are different for
different types of computers. It's reasonably safe to say, though, that if you stick to using simple
file names only, and if the files are stored in the same directory with the program that will use
them, then you will be OK.

84
Java client-server I/O and exceptions

In many cases, though, you would like the user to be able to select a file for input or output. If
you let the user type in a file name, you will just have to assume that the user understands how
to work with files and directories. But in a graphical user interface, the user expects to be able to
select files using a file dialog box, which is a special window that a program can open when it
wants the user to select a file for input or output. Java provides a platform-independent method
for using file dialog boxes in the form of a class called FileDialog. This class is part of the
package java.awt. (Note: as of this writing, I have only ever tested the following on Macintosh
computers, and I am uncertain that some of the details will hold on other platforms.)

There are really two types of file dialog windows: one for selecting an existing file to be used
for input, and one for specifying a file for output. You can specify which type of file dialog you
want in the constructor for the FileDialog object, which has the form

public FileDialog(Frame parent, String title, int mode)

where parent is meant to be the main application window (but can be null, at least on a
Macintosh), title is meant to be a short string describing the dialog box, and mode is one of the
constants FileDialog.SAVE or FileDialog.LOAD. Use FileDialog.SAVE if you want an
output file, and use FileDialog.LOAD if you want a file for input. You can actually omit the
mode parameter, which is equivalent to using FileDialog.LOAD.

Once you have a FileDialog, you can use its show() method to make it appear on the screen. It
will stay on the screen until the user either selects a file or cancels the request. The instance
method getFile() can then be called to retrieve the name of the file selected by the user. If the
user has canceled the file dialog, then the String value returned by getFile() will be null.
Since the user can select a file that is not in the current directory, you will also need directory
information, which can be retrieved by calling the method getDirectory. For example, if you
want the user to select a file for output, and if the main window for you application is mainWin,
you could say:

FileDialog fd = new FileDialog(mainWin, "Select output file",


FileDialog.SAVE);
fd.show();
String fileName = fd.getFile();
String directory = fd.getDirectory();

if (fileName != null) { // (otherwise, the user canceled the request)

... // open the file, save the data, then close the file

Once you have the file name and directory information, you will have to combine them into a
usable file specification. The best way to do this is to create an object of type File. The file
object can then be used as a parameter in a constructor for a FileInputStream or a
FileOutputStream. For example, the body of the if statement in the above example could
include:

File file = new File(directory, fileName);


PrintStream out = new PrintStream(new FileOutputStream(file));
... // write the data to the output stream, out
out.close();

85
Java client-server I/O and exceptions

Of course, you'll have to do something about handling possible exceptions, in particular the
IOException that could be generated by the constructor for the FileOutputStream. But for the
most part, FileDialogs and streams provide a reasonably easy-to-use interface to the file
system of any computer.

86
Java client-server Networking

Networking

AS FAR AS A PROGRAM IS CONCERNED, A NETWORK is just another possible source


of input data, and another place where data can be output. That does oversimplify things,
because networks are still not quite as easy to work with as files are. But in Java, you can do
network communication using input streams and output streams, just as you can use such
streams to communicate with the user or to work with files. Opening a network connection
between two computers is a bit tricky, since there are two computers involved and they have to
somehow agree to open a connection. And when each computer can send data to the other,
synchronizing communication can be a problem. But the fundamentals are the same as for other
forms of I/O.

One of the standard Java packages is called java.net. This package includes several classes
that can be used for networking. Two different styles of network I/O are supported. One of
these, which is fairly high-level, is based on the World-Wide Web, and provides the sort of
network communication capability that is used by a Web browser when it downloads pages for
you to view. The main class for this style of networking is called URL. An object of type URL is
an abstract representation of a Universal Resource Locator, which is an address for an HTML
document or other resource on the Web.

The second style of I/O is much more low-level, and is based on the idea of a socket. A socket is
used by a program to establish a connection with another program on a network. Two-way
communication over a network involves two sockets, one on each of the computers involved in
the communication. Java uses a class called Socket to represent sockets that are used for
network communication. The term "socket" presumably comes from an image of physically
plugging a wire into a computer to establish a connection to a network, but it is important to
understand that a socket, as the term is used here, is simply an object belonging to the class
Socket. In particular, a program can have several sockets at the same time, each connecting it to
another program running on some other computer on the network. All these connections use the
same physical network connection.

This section gives a brief introduction to the URL and Socket classes, and shows how they relate
to input and output streams and to exceptions.

The URL Class

The URL class is used to represent resources on the World-Wide Web. Every resource has an
address, which identifies it uniquely and contains enough information for a Web browser to find
the resource on the network and retrieve it. The address is called a "url" or "universal resource
locator."

An object belonging to the URL class represents such an address. If you have a URL object, you
can use it to open a network connection to the resource at that address. The URL class, and an
associated class called URLConnection, provide a large number of methods for working with
such connections, but the most straightforward method -- and the only one I will talk about here

87
Java client-server Networking

-- yields an object of type InputStream that can be used to read the data contained in the
resource. For example, if the resource is a standard Web page in HTML format, then the data
read through the input stream is the actual HTML code that describes the page.

A url is ordinarily specified as a string, such as "http://www.hws.edu/~eck/index.html".


There are also relative url's. A relative url specifies the location of a resource relative to the
location of another url, which is called the base or context for the relative url. For example, if
the context is given by the url http://www.hws.edu/~eck/, then the incomplete, relative url
"index.html" would really refer to http://www.hws.edu/~eck/index.html.

An object of the class URL is not simply a string, but it can be constructed from a string
representation of a url. A URL object can also be constructed from another URL object,
representing a context, and a string that specifies a url relative to that context. These
constructors have prototypes

public URL(String urlName) throws MalformedURLException

and

public URL(URL context, String relativeName) throws MalformedURLException

Note that these constructors will throw an exception of type MalformedURLException if the
specified strings don't represent legal url's. So of course it's a good idea to put your call to the
constructor inside a try statement and handle the potential MalformedURLException in a catch
clause.

When you write an applet, there are two methods available that provide useful URL contexts.
The method getDocumentBase(), defined in the Applet class, returns an object of type URL.
This URL represents the location from which the HTML page that contains the applet was
downloaded. This allows the applet to go back and retrieve other files that are stored in the same
location as that document. For example,

URL address = new URL(getDocumentBase(), "data.txt");

constructs a URL that refers to a file named data.txt on the same computer and in the same
directory as the web page in which the applet is running. Another method, getCodeBase()
returns a URL that gives the location of the applet itself (which is not necessarily the same as the
location of the document).

Once you have a valid URL object, the method openStream() from the URL class can be used to
obtain an InputStream, which can then be used to read the data from the resource that the URL
points to. For example, if address is an object of type URL, you could simply say

InputStream in = address.openStream();

to get the input stream. This method does all the work of opening a network connection, and
when you read from the input stream, it does all the hard work of obtaining data over that
connection. To make things even easier on yourself, you could even wrap the InputStream
object in a DataInputStream or AsciiInputStream and do all your input through that.

Various exceptions can be thrown as the attempt is made to open the connection and read data
from it. Most of these exception are of type IOException, and such errors must be caught and

88
Java client-server Networking

handled. But these operations can also cause security exceptions. An object of type
SecurityException is thrown when a program attempts to perform some operation that it does
not have permission to perform. For example, a Web browser is typically configured to forbid
an applet from making a network connection to any computer other than the computer from
which the applet was downloaded. If an applet attempts to connect to some other computer, a
SecurityException is thrown. A security exception can be caught and handled like any other
exception.

To put this all together, here is a subroutine that could be used in an applet to read a file over the
network. The contents of that file, which are assumed to be in plain text format, are stored in a
StringBuffer as they are read. (A StringBuffer is similar to a String, except that it can
grow in size as characters are appended to it.) At the end of the method, the contents of the
StringBuffer are returned as a String. This version is somewhat simplified, and the error
handling is certainly not good enough for serious use:

import java.io.IOException;
import java.io.InputStream;
import java.net.*;

public class ReadURL {


String loadURL(String urlName) {

// Loads the data in the url specified by urlName, relative


// to the document base, and returns that data as a String.
// Exception handling is used to detect and respond to errors
// that might occur by returning an error message.

try {

URL url = new URL(urlName); // Create an input stream


InputStream in = url.openStream(); // for reading the data
// from the url.

StringBuffer buffer = new StringBuffer(); // Store input data


here until
// it has all been
read.

int input; // one item read from the input stream


do {
input = in.read(); // This is either -1, if all the data has
been
// read, or else it is the ASCII code of
a
// character read from the input stream.
if (input >= 0) {
char ch = (char)input; // Convert the ASCII code to a
char.
buffer.append(ch); // Add the character to the
buffer.
}
} while (input >= 0);

return buffer.toString(); // return the data that has been read.

}
catch (MalformedURLException e) { // can be thrown when URL is
created

89
Java client-server Networking

return "ERROR! Improper syntax given for the URL to be


loaded.";
}
catch (SecurityException e) { // can be thrown when the connection
is created
return "SECURITY ERROR! " + e;
}
catch (IOException e) { // can be thrown while data is being read
return "INPUT ERROR! " + e;
}

} // end of loadURL() method

public static void main(String[] args) {


ReadURL s = new ReadURL();

System.out.println(s.loadURL("http://www.east.utcluj.ro/mb/mep/antal/download
s.html"));
}
}// end of loadURL() method

Because it can take some time to open a network connection and read the data from it, it is
reasonable to create a separate Thread object to do the work asynchronously. Here is an actual
working applet that uses this technique. The applet is configured so that it will attempt to load
its own source code when it runs.

You can also try to use this applet to look at the HTML source code for this very page. Just type
s4.html into the input box at the bottom of the applet and then click on the Load button.
However, this might generate a security exception, depending on the configuration of your
browser. If so, you'll get a message to that effect in the applet. You might want to experiment
with generating other errors. For example, entering bogus.html is likely to generate a
FileNotFoundException, since no document of that name exists in the directory that contains
this page. As another example, you can probably generate a security error by trying to connect
to http://www.whitehouse.gov.

Sockets, Clients, and Servers

Communication over the Internet is based on a pair of protocols called the Internet Protocol and
the Transmission Control Protocol, which are collectively referred to as TCP/IP. (In fact, there is
a basic type of communication that can be done without TCP, but for this discussion, I'll stick to
the full TCP/IP, which provides reliable two-way communication between networked
computers.)

For two programs to communicate using TCP/IP, each program must create a socket, as
discussed earlier in this section, and those sockets must be connected. Once such a connection is
made, communication takes place using input streams and output streams. Each program has its
own input stream and its own output stream. Data written by one program to its output stream is
transmitted to the other computer. There, it enters the input stream of the program at the other
end of the network connection. When that program reads data from its input stream, it is
receiving the data that was transmitted to it over the network.

90
Java client-server Networking

The hard part, then, is making a network connection in the first place. Two sockets are involved.
To get things started, one program must create a socket that will wait passively until a
connection request comes in from another socket. The waiting socket is said to be listening for a
connection. On the other side of the connection-to-be, another program creates a socket that
sends out a connection request to the listening socket. When the listening socket receives the
connection request, it responds, and the connection is established. Once that is done, each
program can obtain an input stream and an output stream for the connection. Communication
takes place through these streams until one program or the other closes the connection.

A program that creates a listening socket is sometimes said to be a server, and the socket is
called a server socket. A program that connects to a server is called a client, and the socket that
it used to make a connection is called a client socket. The idea is that the server is out there
somewhere on the network, waiting for a connection request from some client. The server can
be thought of as offering some kind of service, and the client gets access to that service by
connecting to the server. This is called the client/server model of network communication. In
many actual applications, a server program can provide connections to several clients at the
same time. When a client connects to a server's listening socket, that socket does not stop
listening. Instead, it continues listening for additional client connections at the same time that
the first client is being serviced.

This client/server model, in which there is one server program that supports multiple clients, is a
perfect application for threads. A server program has one main thread that manages the listening
socket. This thread runs continuously as long as the server is in operation. Whenever the server
socket receives a connection request from a client, the main thread makes a new thread to handle
the communications with that particular client. This client thread will run only as long as the
client stays connected. The server thread and any active client threads all run simultaneously, in
parallel. Client programs, on the other hand, tend to be simpler, having just one network
connection and just one thread (although there is nothing to stop a program from using several
client sockets at the same time, or even a mixture of client sockets and server sockets).

The URL class that was discussed at the beginning of this section uses a client socket behind the
scenes to do any necessary network communication. On the other side of that connection is a
server program that accepts a connection request from the URL object, reads a request from that
object for some particular file on the server computer, and responds by transmitting the contents
of that file over the network back to the URL object. After transmitting the data, the server closes
the connection.

To implement TCP/IP connections, the java.net package provides two classes, ServerSocket
and Socket. A ServerSocket represents a listening socket that waits for connection requests
from clients. A Socket represents one endpoint of an actual network connection. A Socket,
then, can be a client socket that sends a connection request to a server. But a Socket can also be
created by a server to handle a connection request from a client. This allows the server to create
multiple sockets and handle multiple connections. (A ServerSocket does not itself participate
in connections; it just listens for connection requests and creates Sockets to handle the actual
connections.)

To use Sockets and ServerSockets, you need to know about internet addresses. After all, a
client program has to have some way to specify which computer, among all those on the
network, it wants to communicate with. Every computer on the Internet has an IP address which
identifies it uniquely among all the computers on the net. Many computers can also be referred
to by domain names such as math.hws.edu or www.whitehouse.gov. Now, a single computer

91
Java client-server Networking

might have several programs doing network communication at the same time, or one program
communicating with several other computers. To allow for this possibility, a port number is
added to the Internet address. A port number is simply a 16-bit integer. A server does not simply
listen for connections -- it listens for connections on a particular port. A potential client must
know both the Internet address of the computer on which the server is running and the port
number on which the server is listening. A Web server, for example, generally listens for
connections on port 80; other standard Internet services also have standard port numbers. (The
standard port numbers are all less than 1024. If you create your own server programs, you
should use port numbers greater than 1024.)

When you construct a ServerSocket object, you have to specify the port number on which the
server will listen. The prototype for the constructor is

public ServerSocket(int port) throws IOException

As soon as the ServerSocket is established, it starts listening for connection requests. The
accept() method in the ServerSocket class accepts such a request, establishes a connection
with the client, and returns a Socket that can be used for communication with the client. The
accept() method has the form

public Socket accept() throws IOException

When you call the accept() method, it will not return until a connection request is received (or
until some error occurs). The method is said to block while waiting for the connection. While
the method is blocked, the thread that called the method can't do anything else. However, other
threads in the same program can proceed. (This is why a server needs a separate thread just to
wait for connection requests.) The ServerSocket will continue listening for connections until it
is closed, using its close() method, or until some error occurs.

Suppose that you want a server to listen on port 1728. Each time the server receives a
connection request, it should create a new thread to handle the connection with the client.
Suppose that you've written a method createClientThread(Socket) that creates such a
thread. Then a simple version of the run() method for the server thread would be:

public run() {
try {
ServerSocket server = new ServerSocket(1728);
while (true) {
Socket connection = server.accept();
createClientThread(connection);
}
}
catch (IOException e) {
System.out.println("Server shut down with error: " + e);
}
}

On the client side, a client socket is created using a constructor in the Socket class. To connect
to a server on a known computer and port, you would use the constructor

public Socket(String computer, int port) throws IOException

This constructor will block until the connection is established or until an error occurs. (This
means that even when you write a client program, you might want to use a separate thread to

92
Java client-server Networking

handle the connection, so that the program can continue to respond to user inputs while the
connection is being established. Otherwise, the program will just freeze for some indefinite
period of time.) Once the connection is established, you can use the methods
getInputStream() and getOutputStream() to obtain streams that can be used for
communication over the connection. Keeping all this in mind, here is the outline of a method for
working with a client connection:

void doClientConnection(String computerName, int listeningPort) {


// computerName should give the name of the computer
// where the server is running, such as math.hws.edu;
// listeningPort should be the port on which the server
// listens for connections, such as 1728.
Socket connection;
InputStream in;
OutputStream out;
try {
connection = new Socket(computerName,listeningPort);
in = connection.getInputStream();
out = connection.getOutputStream();
}
catch (IOException e) {
System.out.println("Attempt to create connection failed with error: "
+ e);
return;
}
.
. // use the streams, in and out, to communicate with server
.
connection.close();
}

All this makes network communication sound easier than it really is. (And if you think it
sounded hard, then it's even harder.) If networks were completely reliable, things would be
almost as easy as I've described. The problem, though, is to write robust programs that can deal
with network and human error. I won't go into detail here -- partly because I don't really know
enough about serious network programming in Java myself. However, what I've covered here
should give you the basic ideas of network programming, and it is enough to write some simple
network applications. (Just don't try to write a replacement for Netscape.)

Minimal TCP/IP Server


TCP/IP server applications rely on the ServerSocket and Socket networking classes provided by
the Java programming language. The ServerSocket class takes most of the work out of
establishing a server connection.

import java.net.*;
import java.io.*;

public class SimpleServer {


public static void main(String args[]) {
int i = 0;
ServerSocket s = null;
Socket s1;

// Register your service on port 5432


try {

93
Java client-server Networking

s = new ServerSocket(5432);
} catch (IOException e) {
// ignore
}

// Run the listen/accept loop forever


while (true) {
try {
// Wait here and listen for a connection
s1 = s.accept();

// Get output stream associated with the socket


OutputStream s1out = s1.getOutputStream();
DataOutputStream dos = new DataOutputStream(s1out);

// Send your string!


dos.writeUTF(++i + " > Hello Net World!");

// Close the connection, but not the server socket


dos.close();
s1.close();
} catch (IOException e) {
// ignore
}
}
}
}

Minimal TCP/IP Client


The client side of a TCP/IP application relies on the Socket class. Again, much of the work
involved in establishing connections has been done by the Socket class. The client attaches to
the server presented on the previous page and prints everything sent by the server to the console.

import java.net.*;
import java.io.*;

public class SimpleClient {


public static void main(String args[]) {
try {
// Open your connection to a server, at port 5432
// localhost used here
Socket s1 = new Socket("127.0.0.1", 5432);

// Get an input stream from the socket


InputStream is = s1.getInputStream();
// Decorate it with a "data" input stream
DataInputStream dis = new DataInputStream(is);

// Read the input and print it to the screen


System.out.println(dis.readUTF());

// When done, just close the steam and connection


dis.close();
s1.close();
} catch (ConnectException connExc) {
System.err.println("Could not connect to the server.");
} catch (IOException e) {
// ignore
}
}

94
Java client-server Networking

A TCP/IP Client-Server model for robot programming


The following code simulates an interaction of a client with a robot Server. Once the connection
from the client is accepted this will be hold until the client releases the connection. During this
period no other connections will be accepted.

Server implementation:

import java.net.*;
import java.io.*;

public class SimpleServerV1 {


public static void main(String args[]) throws IOException {
int i = 0;
ServerSocket s = null;
Socket s1 = null;
OutputStream s1out = null;
DataOutputStream dos = null;
InputStream s1in = null;
DataInputStream din = null;

// Register your service on port 5432


try {
s = new ServerSocket(5432);
s1 = s.accept();
// Get output stream associated with the socket
s1out = s1.getOutputStream();
dos = new DataOutputStream(s1out);
s1in = s1.getInputStream();
din = new DataInputStream(s1in);
s.close();
} catch (IOException e) {
// ignore
}

System.out.println("Server is UP!");
// Run the listen/accept loop forever
while (true) {
try {
// Wait here and listen for a connection

String sin = din.readUTF();


if (sin.equals("bye")) {
s = new ServerSocket(5432);
s1 = s.accept();
s1out = s1.getOutputStream();
dos = new DataOutputStream(s1out);
s1in = s1.getInputStream();
din = new DataInputStream(s1in);
s.close();
i=1;
System.out.println(i + " > Connection reopened ");
continue;
}
// Send your string!
dos.writeUTF(++i + " > Hello from Server over the Net! > " +
sin + " at: " + s1.getInetAddress());

95
Java client-server Networking

dos.flush();
System.out.println(i + " > Hello from Server over the Net! >
" + sin);
} catch (IOException e) {
// Close the connection, but not the server socket
dos.close();
din.close();
s1.close();
// ignore
}
}
}
}

Client implementation:

import java.net.*;
import java.io.*;
import java.util.Scanner;

public class SimpleClientV1 {


public static void main(String args[]) {
Scanner keyboard_in = new Scanner(System.in);
Socket s1;
InputStream is;
DataInputStream dis;
OutputStream os;
DataOutputStream dos;

try {
// Open your connection to a server, at port 5432
// localhost used here
s1 = new Socket("127.0.0.1", 5432);
// Get an input stream from the socket

is = s1.getInputStream();
os = s1.getOutputStream();

// Decorate it with a "data" input stream


dis = new DataInputStream(is);
dos = new DataOutputStream(os);

while (true) {
System.out.print("Enter one line of code: ");
//read a line from the keyboard
String codeline = keyboard_in.nextLine();

if (codeline.equals("bye")) {
System.out.println("Client terminated by bye<Enter>
character");
dos.writeUTF(codeline);
// When done, just close the stream and connection
dis.close();
dos.close();
s1.close();
break;
} else {
dos.writeUTF(codeline);
System.out.println("The serve said: " + dis.readUTF());
}
}
} catch (ConnectException connExc) {

96
Java client-server Networking

System.err.println("Could not connect to the server.");

} catch (IOException e) {
// ignore
}
}
}

97

You might also like