Vous êtes sur la page 1sur 184

Object Oriented Programming with Java

Revision 0.3

Lee Chuk Munn July 19, 1999

Contents
1 Introducing Java 1.1 What is Java? . . . . . . . . . 1.1.1 Simple . . . . . . . . . 1.1.2 Object Oriented . . . 1.1.3 Network Savvy . . . . 1.1.4 Interpreted . . . . . . 1.1.5 Robust . . . . . . . . 1.1.6 Secure . . . . . . . . . 1.1.7 Architecture Neutral . 1.1.8 Portable . . . . . . . . 1.1.9 High Performance . . 1.1.10 Multithreaded . . . . 1.1.11 Dynamic Language . . 1.2 First Java Program . . . . . . 1.2.1 Java Development Kit 1.2.2 Java CLASSPATH . . . 2 The 2.1 2.2 2.3 2.4 1 1 1 2 2 2 2 2 4 4 4 5 5 5 6 7 8 8 8 9 10 10 10 10 11 11 11 11 12 12 13 13

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

2.5

Java Programming Language Reserved Word . . . . . . . . . . . . . . . . Primitive Types . . . . . . . . . . . . . . . . Identiers . . . . . . . . . . . . . . . . . . . Operators . . . . . . . . . . . . . . . . . . . 2.4.1 Binary Operators . . . . . . . . . . . 2.4.2 String Concatenation . . . . . . . . 2.4.3 Unary Operator . . . . . . . . . . . 2.4.4 Increment and Decrement Operators 2.4.5 Conditional Operators . . . . . . . . 2.4.6 Relational Operators . . . . . . . . . 2.4.7 Bitwise Operators . . . . . . . . . . 2.4.8 Conditional Expression Operator . . 2.4.9 Assignment Operators . . . . . . . . Flow Control Statements . . . . . . . . . . . 2.5.1 Comments . . . . . . . . . . . . . . . i

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

CONTENTS

2.6 2.7

2.5.2 Semicolons, Blocks and Whitespace . 2.5.3 if-else Statement . . . . . . . . . . . 2.5.4 switch-case Statement . . . . . . . . 2.5.5 while Statement . . . . . . . . . . . . 2.5.6 do-while Statement . . . . . . . . . . 2.5.7 for Statement . . . . . . . . . . . . . 2.5.8 break, continue and Label Statement 2.5.9 return Statement . . . . . . . . . . . Array . . . . . . . . . . . . . . . . . . . . . . 2.6.1 Multidimensional Array . . . . . . . . Dening Methods . . . . . . . . . . . . . . . . 2.7.1 main Method . . . . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

14 14 15 17 17 17 18 19 19 20 21 22 23 23 23 23 24 24 24 25 25 26 26 26 27 29 30 31 34 35 36 37 38 39 42 42 42 44 45 45 47

3 Object Orientation 3.1 Why the Hype? . . . . . . . . . . . . . 3.2 Object Concepts . . . . . . . . . . . . 3.2.1 What is an Object? . . . . . . 3.2.2 What is a Class? . . . . . . . . 3.2.3 What is a Property? . . . . . . 3.2.4 What is Encapsulation? . . . . 3.2.5 What is Abstraction? . . . . . 3.2.6 What is Inheritance? . . . . . . 3.2.7 What is Polymorphism? . . . . 3.3 Expressing Objects with Java . . . . . 3.3.1 Objects and Class . . . . . . . 3.3.2 Constructors and Destructors . 3.3.3 Encapsulation . . . . . . . . . . 3.3.4 Method and Member Visibility 3.3.5 Inheritance . . . . . . . . . . . 3.3.6 Polymorphism . . . . . . . . . 3.3.7 Overriding . . . . . . . . . . . 3.3.8 Overloading . . . . . . . . . . . 3.3.9 super and this . . . . . . . . 3.4 final Keyword . . . . . . . . . . . . . 3.5 static Keyword . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

4 Abstract Class and Interface 4.1 Abstract Class . . . . . . . . . . . . . . . . . . . 4.1.1 Dening an Abstract Class . . . . . . . . 4.2 Interface . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Dening an Interface . . . . . . . . . . . . 4.2.2 Implementing an Interface . . . . . . . . . 4.3 Dierences between Abstract Class and Interface

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

ii

CONTENTS

5 Building GUIs with AWT 5.1 Component and Container . . . . . . . . . 5.2 LayoutManager . . . . . . . . . . . . . . . 5.2.1 FlowLayout . . . . . . . . . . . . . 5.2.2 GridLayout . . . . . . . . . . . . . 5.2.3 BorderLayout . . . . . . . . . . . 5.3 Container . . . . . . . . . . . . . . . . . . 5.3.1 Using Panel in Complex GUIs . . 5.3.2 Frame . . . . . . . . . . . . . . . . 5.4 AWT Components . . . . . . . . . . . . . 5.4.1 Button . . . . . . . . . . . . . . . 5.4.2 Checkbox . . . . . . . . . . . . . . 5.4.3 CheckboxGroup . . . . . . . . . . . 5.4.4 Choice . . . . . . . . . . . . . . . 5.4.5 List . . . . . . . . . . . . . . . . . 5.4.6 Label . . . . . . . . . . . . . . . . 5.4.7 TextField . . . . . . . . . . . . . 5.4.8 TextArea . . . . . . . . . . . . . . 5.5 The AWT Paint Cycle . . . . . . . . . . . 5.5.1 paint(Graphics) Method . . . . . 5.5.2 repaint() Method . . . . . . . . . 5.5.3 update(Graphics) Method . . . . 5.6 Graphics . . . . . . . . . . . . . . . . . . 5.6.1 Using Font . . . . . . . . . . . . . 5.7 Customizing Components . . . . . . . . . 5.8 Adding Menus to Frame . . . . . . . . . . 5.8.1 MenuBar . . . . . . . . . . . . . . . 5.8.2 Menu . . . . . . . . . . . . . . . . . 5.8.3 MenuItem and CheckboxMenuItem 5.9 AWT Events . . . . . . . . . . . . . . . . 6 Inner Class 6.1 Member Class . . . . . . . . . . . . . 6.1.1 Referencing Containing Class 6.1.2 Instantiating Member Classes 6.2 Anonymous Class . . . . . . . . . . . 6.2.1 Instance Initializers . . . . . 7 Handling AWT Events 7.1 The MVC Architecture 7.1.1 The Model . . . 7.1.2 The View . . . . 7.1.3 The Controller . 7.2 Events . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49 49 51 52 53 53 55 55 56 57 57 58 58 59 59 60 61 61 62 62 62 63 63 64 65 66 66 67 67 68 69 69 71 72 73 74 75 76 76 77 77 77 iii

. . . . . . Members . . . . . . . . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

CONTENTS

7.3

7.2.1 The AWT Event Model . . . . . . . . . . . . . 7.2.2 Identifying Source, Listener and Event Objects 7.2.3 Event Listener Interface . . . . . . . . . . . . . 7.2.4 Event Source . . . . . . . . . . . . . . . . . . . 7.2.5 Event Objects . . . . . . . . . . . . . . . . . . 7.2.6 A Button Example . . . . . . . . . . . . . . . . 7.2.7 Another Button Example . . . . . . . . . . . . Creating Customized Events . . . . . . . . . . . . . . . 7.3.1 Account Class Again . . . . . . . . . . . . . . . 7.3.2 The AccountEvent Object . . . . . . . . . . . . 7.3.3 AccountListener Interface . . . . . . . . . . . 7.3.4 Event Listener Registration/Deregistration . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

77 77 79 80 80 80 82 83 84 84 84 85 89 89 89 90 91 92 94 94 95 98 98 100 100 101 103 105 107 107 112 112 113 113 114 114 115 116 118

8 Exceptions 8.1 Exceptions . . . . . . . . . . . . 8.1.1 An Exceptional Example 8.1.2 Exception Objects . . . . 8.1.3 try-catch Block . . . . . 8.1.4 And finally... . . . . . . 8.1.5 Declaring Exceptions . . . 8.1.6 Generating Exceptions . . 8.2 Creating Your Own Exception .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

9 Streams 9.1 The File Class . . . . . . . . . . . . 9.2 Character and Byte Streams . . . . . 9.2.1 InputStream . . . . . . . . . 9.2.2 OutputStream . . . . . . . . 9.2.3 A File Copy Example . . . . 9.3 Stream Chaining . . . . . . . . . . . 9.3.1 Decorator Design . . . . . . . 9.3.2 Performing I/O with Account

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

10 Object Serialization 10.1 Serialization . . . . . . . . . . . . . . . . . . . . . . . . 10.1.1 The Serializable Interface . . . . . . . . . . 10.1.2 ObjectInputStream and ObjectOutputStream 10.2 Customizing the Serialization Process . . . . . . . . . 10.2.1 Specifying What Gets Serialized . . . . . . . . 10.2.2 readObject and writeObject Method . . . . 10.3 Validating a Deserialized Object . . . . . . . . . . . . 10.4 Versioning . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

iv

CONTENTS

11 Networking 11.1 Addresses and Ports . . . . . . . . . 11.2 Client-Server Model . . . . . . . . . 11.2.1 Dening the Protocol . . . . 11.2.2 Writing a Server . . . . . . . 11.2.3 Writing the Server API . . . 11.2.4 Using the AccountServerAPI 12 Threads and Synchronization 12.1 What is a Thread? . . . . . . . . . 12.1.1 Creating a Thread . . . . . 12.1.2 Thread Class . . . . . . . . 12.1.3 Priorities . . . . . . . . . . 12.1.4 States of a Thread . . . . . 12.2 Enhancing AccountServer . . . . 12.3 Synchrnonization . . . . . . . . . . 12.3.1 synchronized Keyword . . 12.3.2 Thread Safe Objects . . . . 12.4 Condition Variables . . . . . . . . 12.5 Implication of static on Threads

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

120 . 120 . 121 . 123 . 125 . 129 . 133 135 136 137 139 143 143 143 150 151 154 158 160 163 163 163 166 166 166 167 167 167 168 168 168 169 169 169 169 170 172 172 173 173

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

A Listing A.1 Core Classes . . . . . . . . . . . . . . . . . A.1.1 Account.java . . . . . . . . . . . A.1.2 AccountListener.java . . . . . . A.1.3 AccountEvent.java . . . . . . . . A.1.4 AccountException.java . . . . . A.1.5 Interest.java . . . . . . . . . . . A.2 Subclass and Implementations . . . . . . . A.2.1 FixedDepositAccount.java . . . A.2.2 SavingAccount.java . . . . . . . A.2.3 GenerousRate.java . . . . . . . . A.2.4 TightRate.java . . . . . . . . . . A.2.5 ErroneousAmountException.java A.2.6 OverDrawnException.java . . . . A.3 Account Server . . . . . . . . . . . . . . . A.3.1 AccountServer.java . . . . . . . A.3.2 ConnectionHandler.java . . . . . A.3.3 AccountProtocol.java . . . . . . A.3.4 Counter.java . . . . . . . . . . . A.3.5 Counter.java . . . . . . . . . . . A.3.6 AccountServerAPI.java . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

List of Figures
1.1 1.2 3.1 3.2 3.3 4.1 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 7.1 7.2 7.3 7.4 7.5 9.1 9.2 9.3 9.4 The JDK 1.2 security architecture . . . . . . . . . . . . . . . Environment settings . . . . . . . . . . . . . . . . . . . . . . . Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . The cat class hierarchy . . . . . . . . . . . . . . . . . . . . . . Account class hierarchy . . . . . . . . . . . . . . . . . . . . . Concept of an interface . . . . . . . . . . . . . . . . . . . . . . GUI produced by AFrame.java . . . . . Container managed by FlowLayout . . . Container managed by GridLayout . . . Container managed by BorderLayout . A Java calculator . . . . . . . . . . . . . A Checkbox component . . . . . . . . . A Choice component . . . . . . . . . . . A List component . . . . . . . . . . . . A TextField component . . . . . . . . . An example of using font . . . . . . . . PrettyPanel with a button . . . . . . . An example of a Frame with a MenuBar A menu with MenuItems . . . . . . . . . A typical GUI front end . . . . . . . . . The model/view/controller architecture Event source and listeners . . . . . . . . A RolloverButton example . . . . . . . Notifying listeners . . . . . . . . . . . . InputStream class hierarchy . . . . . . . InputStream class hierarchy . . . . . . . Stream chaining example . . . . . . . . Contents of an AccountInputStream le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 7 24 25 35 44 51 52 53 54 55 58 59 60 61 64 65 67 68 75 76 78 82 88 101 103 106 109

10.1 Graphical interface of serialver . . . . . . . . . . . . . . . . . 119

vi

LIST OF FIGURES

11.1 IP address format . . . . . . . . . . . . . . . . . . . . . . . . . 121 11.2 Interaction between ServerSocket and Socket . . . . . . . . 123 11.3 Flow diagram of AccountServers protocol . . . . . . . . . . 126 12.1 12.2 12.3 12.4 12.5 Time/thread graph . . . . . . . . . . . . . . . . . . . . Life cycle of a thread . . . . . . . . . . . . . . . . . . . A modifed ow diagram of AccountServers protocol A race condition . . . . . . . . . . . . . . . . . . . . . wait() and notify() interaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 144 145 152 161

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

vii

List of Tables
2.1 2.2 2.3 2.4 2.5 2.6 3.1 4.1 7.1 9.1 9.2 List of Java reserved words Binary operators . . . . . . Conditional operators . . . Relational operators . . . . Bitwise operators . . . . . . Assigment operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 10 11 12 12 13 31 48 79

Method and member accessibility . . . . . . . . . . . . . . . . Dierences between abstract class and interface . . . . . . . . AWT event list . . . . . . . . . . . . . . . . . . . . . . . . . .

Input stream classes . . . . . . . . . . . . . . . . . . . . . . . 102 Output stream classes . . . . . . . . . . . . . . . . . . . . . . 104

viii

Chapter 1

Introducing Java
1.1 What is Java?
Java: A simple, object oriented, network savvy, interpreted, robust, secure, architecture neutral, portable, high performance, multithreaded, dynamic language. Since the sentence is loaded with so many buzzwords, lets take a look at what it is trying to say.

Java can be describe as follows:

1.1.1

Simple

What exactly do we mean by simple? The Java designers had the following objectives in mind: Java was designed to be programmed easily without a lot of esoteric training. Java omits many rarely used, poorly understood, confusing features of C++ that brings more grief than benet. These omitted features primarily consist of operator overloading, multiple inheritance, and extensive automatic coercions. Automatic garbage collection for simplifying the task of Java programming. A common source of complexity in many C and C++ applications is storage management: the allocation and freeing of memory. By virtue of having automatic garbage collection the Java language not only makes the programming task easier, it also dramatically cuts down on bugs. Another aspect of being simple is being small. One of the goals of Java is to enable the construction of software that can run standalone in small machines. 1

Secure

1.1.6

Finally, delivering a set of well dened library that is easy to learn and use.

1.1.2

Object Oriented

Object-oriented design is a technique that focuses design on the data and on the interfaces to it. Object-oriented design facilitates the clean denition of interfaces and makes software component and reuse a reality. The object-oriented facilities of Java are essentially those of C++, with extensions from Objective C for more dynamic method resolution. A common misconception is that using an object oriented language like Java makes your program object oriented. This could not be further from the truth. Object orientedness is a matter of technique, training, experience and personal preference.

1.1.3

Network Savvy

Java has an extensive library of routines for coping easily with TCP/IP protocols like HTTP and FTP. This makes creating network connections much easier than in C or C++. Java applications can open and access objects across the net via URLs with the same ease that programmers are used to when accessing a local le system.

1.1.4

Interpreted

The Java compiler generates byte codes instead of native machine code. A byte coded class le contains all the necessary information that the Java interpreter or Java Virtual Machine (JVM) needs to run. The class le can be executed on whatever native platform that JVM has been ported to.

1.1.5

Robust

Java puts a lot of emphasis on early checking for possible problems, later dynamic (runtime) checking, and eliminating situations that are error prone. One of the advantages of a strongly typed language like Java is that it allows extensive compile-time checking so bugs can be found early. The single biggest dierence between Java and C/C++ is that Java has a pointer model that eliminates the possibility of overwriting memory and corrupting data. Instead of pointer arithmetic, Java has true arrays. This allows subscript checking to be performed.

1.1.6

Secure

Java is intended for use in networked/distributed environments. Toward that end, a lot of emphasis has been placed on security. Java enables the 2

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Secure

1.1.6

construction of virus-free, tamper-free systems. The JVM denes a sandbox where the user can safely and securely download and use Java classes.
Remote Class Signed Class Local Class

Bytecode Verifier

Core API

Class Loader

Security Core Java API Package Key Database Access Security Manager Controller

Operating System

Figure 1.1: The JDK 1.2 security architecture Security has been further beefed up with the release of JDK 1.2. The JDK 1.2 security architecture (see gure 1.1) is an extension of the JDK 1.1. Elements of the security architecture or sandbox comprises of the following: Bytecode verier The bytecode verier ensures that the Java class les follow the rules of the Java language. Core classes are not subjected to the bytecode veriers scrutinity. Class loader Loads classes that are not found in the CLASSPATH. Access controller The access controller allows or prevents access to the operating system. The access controller is only found in JDK 1.2. Security manager The security manager is the primary interface between the core API and the operating system. In JDK 1.2, the security manager works in tandem with the access controller. Security package The security package forms the basis of authenticating signed Java classes. Key database A database to store keys used by the security manager and access controller to verify the digital signature in signed class les.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

High Performance

1.1.10

The Java security architect, Li Gong presented1 the following 5 equations which is useful for understanding Java security. 1. security != cryptography 2. correct security model != bug free implementation 3. testing != formal verication 4. component security != overall system security 5. Java security != applet containment

1.1.7

Architecture Neutral

Java was designed to support applications on networks. In general, networks are composed of a variety of systems with a variety of CPU and operating system architectures. To enable a Java application to execute anywhere on the network, the compiler generates an architecture-neutral object le formatthe compiled code is executable on many processors, given the presence of the Java runtime system. The Java compiler does this by generating bytecode instructions which have nothing to do with a particular computer architecture. Rather, they are designed to be both easy to interpret on any machine and easily translated into native machine code on the y for performance.

1.1.8

Portable

Being architecture neutral is a big chunk of being portable, but theres more to it than that. Unlike C and C++, there are no implementation dependent aspects of the specication. For example, int always means a signed twos complement 32 bit integer, and float always means a 32-bit IEEE 754 oating point number.

1.1.9

High Performance

While the performance of interpreted bytecodes is usually more than adequate, there are situations where higher performance is required. The bytecodes can be translated on the y (at runtime) into machine code for the particular CPU the application is running on. The bytecode format was designed with generating machine codes in mind, so the actual process of generating machine code is generally simple. With the imminent release of the HotSpot JVM, Java performance will continue to gain ground against native applications.
1

see http://java.sun.com/javaone/sessions/slides/TT03/index.html.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

First Java Program

1.2

1.1.10

Multithreaded

Multithreading is a way of building applications with multiple threads. Unfortunately, writing programs that deal with many things happening at once can be much more dicult than writing in the conventional single-threaded programs. Java has a sophisticated set of synchronization primitives that are based on the widely used monitor and condition variable paradigm. By integrating these concepts into the language (rather than only in classes) they become much easier to use and are more robust.

1.1.11

Dynamic Language

The Java language was design to adapt to an evolving environment. For example, Java only loads classes as they are needed, even from across the network. Classes in Java also have a runtime representation. Classes are essentially self describing making it possible to dynamically link classes into a running system.

1.2

First Java Program

No programing language introduction is complete without an attempt at the hello world program. We will not be an exception to this rule. Lets take a look at a Java program.
1 2 3 4 5 6 7

// Customary hello world program import java.lang.*; public class HelloWorld { public static void main(String[] args) { System.out.println("hello world"); } } Here is a line by line commentary of the above program. line 1, This is a comment line. Comments are preceeded by a //. line 2, The import tells the Java complier that the program will be using routines in the java.lang library. Notice that Java program statements are terminated by ;. There are ofcourse exception to this rule. line 3, This line denes the actual program. The program name is HelloWorld. It must be save in a le called HelloWorld.java. The contents of this program is those within its curly brackets.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Java Development Kit

1.2.2

line 4, The main function is the entry point of this program. Parameters are passed into the program via args which is an array of String. line 5, main contains exactly one line of code and it prints out the hello world message. To compile the program, we invoke the Java compiler (javac) like so: javac HelloWorld.java Correct any errors reported by javac and recompile. A successful compilation will produce a HelloWorld.class le. Now you are ready to execute this program. Recall that class les contain byte code and can only be executed by the the JVM. To execute HelloWorld.class do the following java HelloWorld and the string hello world should be displayed.

1.2.1

Java Development Kit

To develope and deploy Java applications, you will need a compiler, a JVM, supporting libraries and perhaps documentations. Collectively these are known as the Java Development Kit or JDK. Javasoft provides a free reference implementation of the JDK on its website2 . The Javasofts JDK, which we will be using, containts the following: tools Tools include a Java compiler, a JVM, a debugger, a documentation extractor, a packaging utility call jar and others. library The standard library is found under the lib directory and is distributed as a ZIP le (classes.zip) documentations The documentations can be found under the docs directory. Use your browser and open docs/index.html. demonstrations Demo programs are found under demos. C header les These header les are mandatory for interfacing a Java program with a C program or vice versa. This is call Java Native Interface or JNI for short. We will not be covering JNI in this course. sources Yes, the JDK sources are available as part of the distribution.
2

see http://java.sun.com/products/jdk

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Java CLASSPATH

1.2.2

1.2.2

Java CLASSPATH

The Java tools needs to know where the JDK are located. To do this, these tools will reference the CLASSPATH environment variable. The directories and les in a CLASSPATH specication should be colon separated on UNIX and semicolon separated on Windows. You need to include atleast classes.zip and your current directory in your CLASSPATH specication. Figure 1.2 show a generic setting. On UNIX Bourne shell: # Assume JDK is located in /opt/jdk1.1 JAVA_HOME=/opt/jdk1.1 CLASSPATH=$JAVA_HOME/lib/classes.zip:. PATH=$JAVA_HOME/bin:$PATH export JAVA_HOME CLASSPATH PATH On Windows: rem set set set Assume JDK is located in c:\jdk1.1 JAVA_HOME=c:\jdk1.1 CLASSPATH=%JAVA_HOME%\lib\classes.zip;. PATH=%JAVA_HOME%\bin;%PATH% Figure 1.2: Environment settings Classes can be installed in one of 2 ways: 1. In a JAR (Java Archive) or a ZIP le. 2. Individual class les in a directory. If classes are packed in an archive (JAR or ZIP) le, the you need to include the archive le name in your CLASSPATH. Witness the classes.zip le in gure 1.2. If your class les are kept in a directory, you specify the directory that contains these class les. Again note the . in gure 1.2 which references your current directory.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Chapter 2

The Java Programming Language


2.1 Reserved Word

Java is a very small and compact language. There following is a list of Java keywords. These words are reserved any may not be used in any identiers (see section 2.3 on page 9). abstract boolean break byte case catch char class continue default do double else extends false final finally float for if implements import instanceof int interface long native new null package private protected public return short static super switch synchronized this throw throws transient true try void volatile while

Table 2.1: List of Java reserved words

2.2

Primitive Types

All data in Java belongs to a particular type. Java denes a numner of types known as primitive data types which are atomic in the sense that these types cannot be broken down into simpler types. These types can be classied into the following groups: 1. Integer, represented as twos complement 8

Identiers

2.3

byte 8 bits, 27 . . . 27 1 short 16 bits, 215 . . . 215 1 int 32 bits, 231 . . . 231 1 long 64 bits, 263 . . . 263 1 2. Floating point, IEEE 754-1985 float 32 bits, 3.4028E + 38 x 1.4023E + 45. double 64 bits, 1.79769E + 208 x 4.9406E 324. 3. Boolean (boolean), which has the value true or false. 4. Character (char), 16bit in Unicode, 0x0000 x 0xFFFF. 5. String (String), must be enclosed in double quotes . . . . 6. Arrays, which are proper types and not pointers. We will look at arrays in page 19. Integer types can be represented using decimal, octal or hexadecimal. Furthermore, if an integer value is immediately followed by the letter L or l indicates that the integer value is long. These forms are shown as follows: 5 indicates an int value 5L indicates a long value 05 the prex 0 indicates an octal value 0x5 the prex 0x indicates a hexadecimal value Similarly, you can indicate a oating point number to be either float or double by appending either the letter F or D to the number respectively.

2.3

Identiers

Identiers are used as meaningful and easy to remember names to members(variables), constants, methods, classes, etc. A valid identier is one that begins with either an letter, an underscore ( ), or a dollar ($) and is followed by any number of letters, underscores, dollars or digits. The following is a list of valid and invalid indentiers: thisLine, valid $money$, valid
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Unary Operator

2.4.4

toBeOrNotToBe, valid $12345, valid 123isAsEasyAsABC, invalid To declare and use and identier, we have to preceed it with a type. Lets declare an identier called foo and bar which are of type integer (int). We will also initialize bar with the value of 100. int foo, bar = 100;

2.4

Operators

If you are familar with either C or C++ operators then you will feel right at home with Java operators.

2.4.1

Binary Operators

Binary operators take two operands and returns a result. Please refer to table 2.2. Operator + / % Use op1 + op1 op1 op1 / op1 % op2 op2 op2 op2 op2 Result adds op1 to op2 subtracts op2 from op1 multiplies op1 by op2 divides op1 by op2 compute the remainder from dividing op1 by op2

Table 2.2: Binary operators

2.4.2

String Concatenation

Strings can be concatenated using the plus operator (+). String msg = "There are " + days + "in a leap year.";

2.4.3

Unary Operator

Unary operators changes the sign of a value or variable. The code snippet below illustrates this. 2 + -3 10

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Bitwise Operators

2.4.8

2.4.4

Increment and Decrement Operators

Increment and decrement operators can be used as short cut to increment or decrement a variable by one. There are two versions of these operator, a prex and a postx version. The prex version implies that the increment or decrement should be perform rst before any other operation while the postx version performs the increment or decrement after the entire expression has been evaluated. The code below
1

count = ++itemA + itemB--; can be rewritten as follows:

1 2 3

itemA = itemA + 1; count = itemA + itemB; itemB = itemB - 1;

2.4.5

Conditional Operators

These operator are used in boolean expressions and consists of logical and, or and not. Please refer to table 2.3. Operator && || ! Use op1 && op2 op1 || op2 !op1 Result op1 and op2 are both true either op1 or op2 is true op1 is false if its orignal value is true and vice versa

Table 2.3: Conditional operators

2.4.6

Relational Operators

Relational operators compares two operands and returns boolean values depending on the outcome of the evaluation. Please refer to table 2.4.

2.4.7

Bitwise Operators

The following operators are used to manipulate bit patterns. Javas bitwise operators are not as extensive as some languages like C/C++. Please refer to table 2.5.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

11

Assignment Operators

2.5

Operator > >= < <= == !=

Use op1 > op2 op1 >= op2 op1 < op2 op1 <= op2 op1 == op2 op1 != op2

Result true i op1 true i op1 true i op1 true i op1 true i op1 true i op1

is is is is is is

greater than op2 greater or equal to op2 less than op2 less or equal to than op2 equal to op2 not equal to op2

Table 2.4: Relational operators Operator >> << >>> & || ^ ~ Use op1 >> op2 op1 << op2 op1 >>> op2 op1 & op2 op1 || op2 op1 ^ op2 ~op1 Result shift bits of op1 right by a distance of op2 bits, sign preserving shift bits of op1 left by a distance of op2 bits, sign preserving shift bits of op1 right by a distance of op2 bits, non sign preserving bitwise AND op1 with op2 bitwise OR op1 with op2 bitwise exclusive OR op1 with op2 complements op1

Table 2.5: Bitwise operators

2.4.8

Conditional Expression Operator

We can use conditional expression operator to selectively evaluate portions of an expression. The syntax for conditonal expression operator is as follows: <conditional-expression>? <expr_1>: <expr_2> In the following example, count will either get the value 0 or an increment of 1 depending on whether its current value is greater than MAX.
1

count = (count > MAX)? 0: ++count;

2.4.9

Assignment Operators

These are short hand operators. They reduce the following expression op1 = op1 OPERATOR op2 to op1 OPERATOR= op2 Please refer to table 2.6.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

12

Comments

2.5.1

Operator += -= = /= %= <<= >>= >>>=

Use op1 += op2 op1 += op2 op1 = op2 op1 /= op2 op1 %= op2 op1 <<= op2 op1 >>= op2 op1 >>>= op2

&= |= =

op1 &= op2 op1 |= op2 op1 = op2

Result increment op1 by op2 decrement op1 by op2 multiply op1 by op2 and assign the result to op1 divide op1 by op2 and assign the result to op1 the remainder of the division is assigned to op1 left shift op1 by a distance of op2 and assign the result to op1, sign preserving right shift op1 by a distance of op2 and assign the result to op1,sign preserving right shift op1 by a distance of op2 and assign the result to op1, non sign preserving bitwise AND op1 by op2 bitwise OR op1 by op2 bitwise exclusive OR op1 by op2

Table 2.6: Assigment operators .

2.5

Flow Control Statements

Flow control statements determines the ow of our program. These statements can be classied into the following categories: Decision making if-else, switch-case Loops for, while, do-while Exceptions try-catch-finally, throw Miscellaneous break, contine, label: return We will dier the discussion of exceptions to chapter 8.

2.5.1

Comments

Comments are used to document the code. The correct use of comments aids the codes maintainability. There are three dierent types of comments. 1. Comment to the end of line using a double slash, // int counter = 0; // Used to hold the clock count
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

13

if-else Statement

2.5.3

2. Delimited comment used / and / to enclose the comments like so: count = item / from stock / + count; 3. Documentation comments is a variation of delimeted comments. All text between / .../ are treated as comments and can be extracted as documentation by the javadoc tool.

2.5.2

Semicolons, Blocks and Whitespace

Java is a context free langage unlike languages like COBOL where character position matters. Liberal use of what space such as tab, space and new lines are recommended to make the program readable. Java program statements are terminated with a semicolon (;). A block is a group of statements enclosed within a pair of curly brackets ({}). Statements within a block is treated as if it were a single statement. You do not need to terminate block statements with a semicolon. Variables dene within a block is not visible outside of the block. In the following example, count is not available outside the block.
1 2 3 4

{ int count; count = (count > MAX)? 0: ++count; }

2.5.3

if-else Statement

The if-else statement provides your programs with the ability to selectively execute other statements based on some criteria. The syntax of the if-else statement is: if <condition> <statement_1> [ else <statement_2> ] If the <condition> is evaluated to true, <statement_1> will be execute otherwise do <statement_2>. The following example illustrates the use of this statement:
1 2 3 4 5 6

char grade; int score; . . . if (score > 90) { grade = A;


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

14

switch-case Statement

2.5.4

7 8 9 10 11 12 13 14 15 16

} else if (score >= 80) { grade = B; } else if (score >= 70) { grade = C; } else if (score >= 60) { grade = D; } else { // Everybody below 60 fails grade = F; } Note that the else clause if option which means that if the condition is not met, an action is not taken. The following code snippet will only print out the message excellent! if the score is 90 and above:

1 2 3 4

. . . if (score > 90) System.out.println("excellent!"); . . .

2.5.4

switch-case Statement

Use the switch statement to conditionally perform statements based on some expression; the expression must evaluate to an integer type. You can think of switch as a compound if-else statement. If the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

int month; . . . switch (month) { case 1: System.out.println("January"); break; case 2: System.out.println("February"); break; case 3: System.out.println("March"); break; case 4: System.out.println("April"); break; case 5: System.out.println("May"); break;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

15

switch-case Statement

2.5.4

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

case 6: System.out.println("June"); break; case 7: System.out.println("July"); break; case 8: System.out.println("August"); break; case 9: System.out.println("September"); break; case 10: System.out.println("October"); break; case 11: System.out.println("November"); break; case 12: System.out.println("December"); break; default: System.out.println("Not a valid month"); } If month falls in the range of 1 and 12, then an appropriate month will be printed otherwise a Not a valid month message will be printed. The default acts as a catch all if month is not in the specied range. The break statements cause control to break out of the switch and continue with the rst statement following the switch. The break statements are necessary because case statements fall through. That is, without an explicit break control will ow sequentially through subsequent case statements. The default and break are optional; The general syntax of the switch statement is as follows: switch <condition> case <value_1>: <statement_1> . . . break; case <value_2>: <statement_2> . . . break; . . .
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

16

for Statement

2.5.7

[ default: <default_statement> ]

2.5.5

while Statement

Generally, a while statement performs some action while a certain condition remains true. The syntax of the while statement is: while <condition> <statement> That is, while <expression> is true, do <statement>. <statement> can be a single statement or a block. An example of the while statement is as follows:
1 2 3 4 5 6

. . . while (input.read() != -1) { count++; System.out.println("characters read = " + count); } . . .

2.5.6

do-while Statement

The do-while loop, which is similar to the while loop you have met earlier except that the expression is evaluated at the bottom of the loop: do { <statement> } while (<expression>); The do-while statement is a less commonly used loop construct in programming but does have its uses. For example, the do-while is convenient to use when the statements within the loop must be executed at least once.

2.5.7

for Statement

Use the for loop when you know the constraints of the loop (its initialization instruction, termination criteria, and increment instruction). The syntax of for loop is: for (<initialization>, <expression>, <increment>) <statement> The following example uses a for loop to sum all numbers in the range of 1 to 100.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

17

break, continue and Label Statement

2.5.8

1 2 3 4

int total = 0; for (int x = 1; x <= 100; x++) total += x; System.out.println("total = " + total);

2.5.8

break, continue and Label Statement

The following keywords are used to control program ows within loop statements. A label is used to identify a particular statement. Label names follows identiers (see section 2.3 on page 9) naming conventions. The syntax of a label is as follows: <label>:<statement> We have seeen the break statement within the switch statement; recall that the break causes the ow of control to jump out of the switch statement. Another form of the break statement causes ow of control to break out of a labeled statement. The break statement has the following syntax. The <label> is optional. If <label> is not present, break will break out of the closest loop. break [<label>]; While a break breaks out of a loop, a continue causes control to be transfered back to the loop for re evaluation. As with break, continue has the label and unlabelled form. continue [<label>]; Lets examine a code snippet to see break, contiune and label statements in action.
1 2 3 4 5 6 7 8 9 10 11 12 13 14

outer_loop: while (a > MAX) { if (testIt(a)) { a++; continue; // Note this } else { for (int j = 0; true; j++) { a = compute(j, a); if (a > j) { continue; // Note this } else if ((a > 0) && (a < j)) { continue outer_loop; // Note this } else { break outer_loop; // Note this }
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

18

Array

2.6

15 16 17 18

} } } System.out.prinln("All done!"); The explanation of the above code is as follows: line 4, This continue will cause the control to be transfered to the while loop in line 1. line 9, Since there are no label following the continue, control will go back to for in line 6. line 11, Returns to the while loop in line 1 line 13, The break exist both the inner for and outer while loops. Control will be transfered to line 18.

2.5.9

return Statement

A return is used to exit from a method. The general syntax of return is as follows: return [ <value> ]; A return with a value returns the value to the caller of the method that we are currently leaving.

2.6

Array

Like most programming language, Java allows you to collect and manage multiple values through an array. The following is a declaration of an integer array called intArray. int[] intArray; Note the square brakets ([]) indicates an array. Once you have declared an array, you have to allocate the array by specifying the size of the array. Java does not support compile time array allocation. To create an array we use the new keyword. We will now create intArray with 10 elements. The desired sized in placed within the square brackets. intArray = new int[10]; In general creating an array of any type has the following syntax: <type>[] <identifer> = new <type>[<size>];

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

19

Multidimensional Array

2.6.1

Every array has a property called length. length returns the number of elements in an array. length is not present when an array is not created.
1 2 3

int[] intArray = new int[100]; for (int i = 0; i < intArray.length; i++) intArray[i] = i; Another way of creating arrays is with an initializer which looks like the following: int[] intArray = {1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 0xa};

2.6.1

Multidimensional Array

Java also supports multidimensional arrays. Multidimensional arrays are really arrays of arrays. To declare a two dimensional array you use 2 pairs of [] like so: int[][] intArray = new int[10][10]; When allocating a multidimensional array, you do not have to specify the number of elements that are contained in each dimension. For example, the following
1 2 3 4 5 6

int[][] intArray = new int[10][]; for (int i = 0; i < intArray.length; i++) { intArray[i] = new int[i]; for (int j = 0; j < intArray[i].lenght; j++) intArray[i][j] = j; } initializes the array with the following values:

1 2 3 4 5 6 7 8 9 10

intArray[0] intArray[1] intArray[2] intArray[3] intArray[4] intArray[5] intArray[6] intArray[7] intArray[8] intArray[9]

= = = = = = = = = =

0 0, 0, 0, 0, 0, 0, 0, 0, 0,

1 1, 1, 1, 1, 1, 1, 1, 1,

2 2, 2, 2, 2, 2, 2, 2,

3 3, 3, 3, 3, 3, 3,

4 4, 4, 4, 4, 4,

5 5, 5, 5, 5,

6 6, 7 6, 7, 8 6, 7, 8, 9

You cannot create an inner array without rst creating the outer. This is legal: int[][] intArray = new int[10][];
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

20

Dening Methods

2.7.1

but not this: int[][] intArray = new int[][10]; Multidimenstional arrays can also be allocated and initialized with nested initializers. For example, rewriting the example in page 20 produces the following:
1 2 3 4 5 6 7

int[][] intArray = { {0}, {0, 1}, {0, 1, 2}, {0, 1, 2, 3}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} };

2.7

Dening Methods

Methods object oriented way of saying functions. A method has the general syntax: public <return_type> <name>([<type> param_1, . . .]) { <method_body> } The <return_type> is the type returned by the the method. Return types can be any valid Java type which includes all primitive types, arrays and references. To return a value we use return with the desired value when we wish to exit the method. Method parameters are passed by-value viz. any modication done on the formal parameter will not aect the actual parameter.
1 2 3 4 5 6 7

public void fooBar(int input) { input = 7; } . . . int fred = 0; foobar(fred); System.out.println("fred = " + fred); // fred = 0; Although we pass fred into fooBar() to be modied the value remains unchanged once fooBar() has completed execution.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

21

main Method

2.7.1

2.7.1

main Method

The main() method name is usually reserved for the main entry point of Java programs viz. the method that the JVM will call rst when we execute a Java program. A Java program without a main() will not run. The signature of main() is as follows: public static void main(String[] args) { . . .} The args is an arrays and is used to store command line parameters. If we execute the following line java FooBar 123 456 789 the following are found in args: args[0] = "123" args[1] = "456" args[2] = "789"

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

22

Chapter 3

Object Orientation
3.1 Why the Hype?

Object-orientation is a new technology based on objects and classes. It presently represents the best methodological framework for software designers to develop complex large scale systems. Objects provide a focus throughout analysis, design, and implementation by emphasizing the state, behavior, and interaction of objects in its models, providing the desirable property of seamlessness between activities. The use of objects distinguishes object-orientation from other techniques such as traditional structured methods (process-based: data and function are separate) or other techniques such as knowledge based systems (logic progamming, rules: Prolog) or mathematical methods (functional programming: ML, Lisp). The benets of object orientation are obvious; some of the benets are listed below: Faster development by breaking down complex systems to managable chunks. Increased quality. Easier maintance. Enhanced modiability to adapt to changes. Increase software reuse.

3.2
3.2.1

Object Concepts
What is an Object?

There are many denitions of objects. For this course, we dene an object as follows: 23

What is Encapsulation?

3.2.4

Object An entity which has state, behaviour and identity with a well dened role in a problem space. The key in object orientation is to be able to identify objects and how to partition entities in a problem space into objects. So how do you dene objects? In general, anything with a clearly dened boundry. You would for instance dene a car as an object or a road as an object since both have clearly dened boundry. But a car/road would not t our denation.

3.2.2

What is a Class?

A class is a template for objects. A class can be dened as follows: Class A class is a set of objects that share a common structure and a common behavior. How do class and object dier? Think of classes as programs and objects as processes; processes are dynamic copies of programs just as an object is a dynamic copy of a class. The act of creating an object from a class is called instantiating and the instantiated object is called an instance of a class. From gure 3.1, ObjectA is instantiated from ClassA and is said to be an instance of ClassA. ClassA
instantiate

ObjectA

Figure 3.1: Instantiation

3.2.3

What is a Property?

A property is a single public attribute. We can use properties to classify and object as well as to use properties to denote the current state of an object. Properties of a cat includes the following non exhaustive list: type of cat, size, color, etc.

3.2.4

What is Encapsulation?

One of the main diculties when dealing with complex software is the level of implementation details. Very often we are overwhelmed by them that we do not know where to begin. Another diculty with large software is that often a minute change to one part of the system requires changes to every other parts. This is because we implement parts of the system based on the implementation of another part. The object oriented way of tackling this problem is encapsulation; hiding what is not necessary so that we can concentrate on what is the issue at hand:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

24

What is Inheritance?

3.2.7

Encapsulation The principle of hiding design or implementation information that are not relevant to the current object.

3.2.5

What is Abstraction?

While encapsulating is hiding away details, abstract is ignoring them altogether. Abstraction The principle of ignoring those aspects of a subject that are not relevant to the current purpose in order to concentrate more fully on those that are. We use abstraction everyday to help us focus on a particular subject. Mention the word car and most of use will have the mental image of a metalic body with wheels that travels on roads. We may not have imagined a driver or even the make and color of the car. We have abstracted the notion of a car to its purest form ignoring other details.

3.2.6

What is Inheritance?

Inheritance is a relationship between classes where one class is the parent class of another. Inheritance can be dened as: Inheritance Properties or characteristics received from an ancestor. Sometimes, inheritance is also know as an is-a relationship. Inheritance encourages reuse and simplies code development. Consider the following class hierarchy shown in gure 3.2. Tiger Cat Lion Figure 3.2: The cat class hierarchy Besides the is-a relationship, inheritance can also have the notion of specialization. In gure 3.2 we see that Hobbes1 is a specialization of tiger; it is a stu tiger to be exact.
For those who do not know what Hobbes is should buy a copy of SCMP and check out the comics section. You will not regret it.
1

Hobbes

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

25

Objects and Class

3.3.1

3.2.7

What is Polymorphism?

Polymorphism is the ability of an object to assume many dierent forms. The word polymorphism is a derived from two words: poly which means many and morph which means forms. Refering to gure 3.2, we see that a cat object can also be a lion object or a tiger object since cat is the lowest common denominator class of the two.

3.3
3.3.1

Expressing Objects with Java


Objects and Class

We know that objects are instantiated from classes so lets look at dening a class in Java. In Java, a class denition has the following syntax: public class <class_name> { <member_declaration> <methods_declaration> } Classes can contain properties usually modelled by member variables. These are also declared as part of the class body. The following is an Account class with name, account number, account balance and overdraft as properties. Account also has the deposit(), withdrawal() and transfer() method. This class must by save in a le called Account.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

public class Account { public String name; public int acctNo; public float balance; public boolean overdraft; public void deposit(float amt) { if (amt >= 0) balance += amt; } public void withdrawal(float amt) { if ((amt <= balance) && (amt >= 0)) balance -= amt; } public void transfer(Account from, float amt) { if (amt < 0) return; from.withdrawal(amt); deposit(amt); } }
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

26

Constructors and Destructors

3.3.2

To instantiate an Account object we use new. In the following code, we open an account for Fred.
1 2 3 4 5 6 7

Account fred; . . . fred = new Account(); fred.name = "Fred"; fred.acctNo = 12345; fred.balance = 100F; fred.overdraft = true; line 1, Declare an Account variable. Note that Account is not a primitive type (see section 2.2). We call type type of variable reference type. The initial value of reference type is null. line 3, Instantiate an object call fred. line 4 to 7, Assign values to object fred properties.

3.3.2

Constructors and Destructors

Constructors are useful for initializing objects before they are being used. Constructors are special purpose methods; a constructor is only used during instantiation to initialize the object and is never used again. A constructor must follow the following rules: 1. A constructors name must be the name of the class. 2. A constructor does not have any return type. We can write a constructor for Account as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13

public class Account { private String name; private int acctNo; private float balance; private boolean overdraft; public Account(String n, int no) { name = n; acctNo = no; balance = 100F; overdraft = false; } . . . } Now using the constructor, we can rewrite the code in page 27 as follows:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

27

Constructors and Destructors

3.3.2

Account fred = new Account("Fred", 12345); Every object has a default constructor if one is not provided. The default constructor takes no argument and has an empty body. A default constructor has the following signature: public <class_name>() { } Once you created your own constructor, you lose the the default constructor. Destructors are the exact opposite to constructors. Destructors are called before the object is grabage collected and its memory released. Sometimes an object may hold other objects like a network connection, or a database record or an opened le. These must be properly closed so that the database record is in a consistend state for instance. The garbage collect cannot do this for us. We must perform these in the destructors. In Java, a destructors is known as a nalizer. Finalizers are optional; if they are not present in an object, no nalization will be done. To add a nalizer to an object simply add the following method: protected void finalize() { // perform clean up } Lets add a nalizer to Account class. Our nalizer will check if an account object has an savings. If balance is not 0, we will save the object otherwise we will discard the object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

public class Account { private String name; private int acctNo; private float balance; private boolean overdraft; public Account(String n, int no) { . . . } . . . protected void finalize() { if (balance <= 0) { // save this account } } } There are some additional things to be aware about nalizers: A nalizer is invoked before the the JVM garbage collects the object. 28

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Encapsulation

3.3.3

The JVM may exit without garbage collecting objects; so objects with nalizer may never be called. the JVM makes no guarantees about when garbage collection occurs or the order of the object will be collected. Therefore we cannot know the time or order of the nalizers will be invoked.

3.3.3

Encapsulation

Examining the code snippet on page 27 , we see that we need to know Account internals to use it; furthermore if we decide to change the account name from name to accountName we will have to change every reference to it. Clearly we need to hide the implementation details from view. We really need a way of manipulating the properties without knowing how it is done. We use encapsulation to achieve this. To encapsulate properties we use getter and setter methods. The getter and setter methods has the following pattern: public void set<PropertyName>(<PropertyType> value) { . . . } public <PropertyType> get<PropertyName>() { . . . } If the <PropertyType> is boolean, the getter method is replaced with the following method signature: public boolean is<PropertyName>() { . . . } This type of property is know as boolean property. Properties with the pair set/get is known as a read-write property while properties with only a set or a get is know as a write only and read only properties respectively. Lets rewrite Account so that we access its properties through getter and setter instead of directly manipulating the member variables.
1 2 3 4 5 6

public class Account { private String name; private int acctNo; private float balance; private boolean overdraft; . . .
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

29

Method and Member Visibility

3.3.4

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

public void setName(String n) { name = n; } public String getName() { return name; } public int getAccountNumber() { return acctNo; } public float getBalance() { return balance; } public void setOverdraft(boolean b) { overdraft = b; } public boolean isOverdraft() { return (overdraft); } } name and overdraft are read/write property while acctNo and balance are read only since we have not provided setter. It is important to recognize what we have done. We have hid implementation details viz. the member names in this case, from the outside world. To reinforce this notion, we changed all member visibility from public to private. A private means that these members are only accessable within the Account class. The benet we got is: 1. Manipulating properties with setName("Fred") and getName() is much more meaningful than this: fred.name = "Fred"; 2. Changing implementations eg. renaming the variables, will not require massive global changes.

3.3.4

Method and Member Visibility

Encapsulation hides a class internal details for the outside world; if we were to provide getter and setter methods but leave Account class member declaration as public as in page 3.3.1, then we leave our members exposed. This is because public allows virtually any one to access the member. By using private, we have eectively closed all members to the outside world. By doing this, we are enforcing encapsulation. Java provides the following visibility keywords which can be used on members or methods:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

30

Inheritance

3.3.5

public A method or member that is declared public is accessible from any class. protected A method or member that is declared protected is accessible only to its subclass and the class itself. We will examine the notion of subclass in section 3.3.5 on page 31. private A private method or member of a class is only accessible from within that class. Other classes including subclass cannot access this member or method. Table 3.1 summarizes the previous points: Keyword public protected private Self Yes Yes Yes Subclass Yes Yes No Others Yes No No

Table 3.1: Method and member accessibility The following are some tips on using visibility scopes. There is no reason why you should declare any member as public unless performance is a big issue. You should always encapsulate members with public setter and getter methods. Standard methods that manipulate your class should be made public. If you have written a class and you want to perhaps give control of your class members and methods to its subclass but not anyone else, then declare these as protected. Declare your members and methods as private if they are used only within your class.

3.3.5

Inheritance
The syntax for

Java uses the keyword extends to denote inheritance. extends is as follows: public <class_name> extends <parent_class_name> { . . .

The parent class is also know as superclass and the child class is sometimes called a subclass. We will now reuse Account to dene SavingAccount and FixedDepositAccount. We want FixedDeposit to have the following additional characteristics:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

31

Inheritance

3.3.5

FixedDeposit term to denote the duration of the deposit. interest is the interest rate for term. SavingAccount Nothing special about this class. Just an is-a relationship. The following code snippet implements this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

public class FixedDepositAccount extends Account { private Date term; private float interest; public FixedDepositAccount(String n, int acct) { super(n, acct); term = null; interest = -1F; } public void setTerm(Date t) { // We only allow to set the term once if (term == null) term = t; } public Date getTerm() { return (term); } public void setInterest(float i) { if (interest == -1F) interest = i; } public float getInterest() { return (interest); } } public class SavingAccount extends Account { public SavingAccount(String n, int acct) { super(n, acct); } } When we extends a class, we inherit all visible members and methods. Visible members and methods are those marked with either public or
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

32

Inheritance

3.3.6

protected. We will look into visibility rules in more detail in section 3.3.4 later in this chapter. Java only supports single inheritance. So what has FixedDepositAccount and SavingAccount inherited? A short answer would be all the getter and setter methods as well as deposit(), withdrawal() and transfer(). What have they not inherited? The private member variables. But notice that we can still access these variables via the appropriate getter and setter methods. Accounts constructor; we therefore need to provide constructors for our two child classes. What is the super() in line 6 and 14. super is a keyword in Java. It references the parent class. You can think of super as a .. in your DOS le system. Similary this refers to the current object. To understand line 6 and 14 we need to delve a little more into inheritace. Object and Constructor Chaining All Java objects inherit Object implicitly. The only exception to this rule is the Object itself. So if we write public class Account { the Java compiler will modify the declaration to this: public class Account extends Object { This is to ensure that every object have all the necessary methods like clone(), notify(), equals(), etc. Please refer to the documentation for more information. We also know that every object has a constructor. So when an object is instantiated its constructor gets called. But before we can create the object itself the JVM must ensure that the parent is create before the child. To do this the Java compiler will insert a super() as the rst line in every constructor. This ensures that the parent constructors gets called. This is know as constructor chaining. Note that the compiler will only chain to the default constructor of the parent class. This works well if the parent class has a default constructor. But Account does not have a default constructor! We have replace it with our own constructor. In this case we have to do the chaining ourself. The super(n, acct); is the link to our parent class.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

33

Polymorphism

3.3.6

3.3.6

Polymorphism

Like most object oriented programming language, Java allows you to hold an object with its parent type variable (polymorphic). So we can do this: Account fred = new SavingAccount("Fred", 12345); Subsequently when we use fred, the JVM needs to dynamically lookup what fred refers to. Although this incur a small overhead but the benet far outweighs the performance hit. Because you can use a parent object reference to hold a child object, we need to know exactly what is the actual object. We can use the instanceof operator for this purpose. instanceof will return true if the object on the left hand side of the operator is an instance of the right hand side class. Consider the following:
1 2 3

FixedDepositAccount foo = new FixedDepositAccount("Foo", 67890); SavingAccount fred = new SavingAccount("Fred", 12345); Account acct = fred; Using instanceof of fred and foo, we get the following results: acct instanceof SavingAccount, true fred instanceof SavingAccount, true fred instanceof Account, true because Account is the parent class of SavingAccount foo instanceof SavingAccount, false Casting refers to converting from one type to another. In C, a char * can be cast to almost anything. In Java, casting goes through strict runtime checks to ensure that you are not violating security rules. The general syntax for casting is as follows: <type_a_ref> = (<type_a_class>)<type_b_ref>; Casting is only permitted between equvalent primitive types eg. from int to long or from float to double. If can loose precision if you cast from a wider type to a narrower type eg. from long to short. Casting between objects is much more interesting. In this case, we can have three possible situations: 1. From a child class to a parent class. 2. From a parent class to a child class. 3. Between siblings.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

34

Overriding

3.3.7

FixedDepositAccount Account SavingAccount Figure 3.3: Account class hierarchy Lets look at the Account hierarchy again as show in gure 3.3. We have the following objects:
1 2 3

FixedDepositAccount fixed = new FixedDepositAccount(...); SavingAccount save = new SavingAccount(...); Account acct; Casting an object can be divided into the following three cases: 1. Assigning a subclass to a superclass (up cast) can be done with a simple assigment. acct = save; 2. Assigning a superclass to a subclass (down cast). This requires the cast operator otherwise it would not compile. Furthermore the cast is check during runtime to ensure that what we are casting to is the correct type. save = (SavingAccount)acct; The JVM will check acct during runtime to make sure that acct is actually referencing a SavingAccount object. Otherwise you will get a ClassCastException error. 3. Casting between siblings is illegal and will never compile. The following is illegal. save = (SavingAccount)foo;

3.3.7

Overriding

When a subclass denes a method with the exact same name and signature as a method in its super class it is said to override the superclass method. Overriding is used to customize a superclass method. FixedDepositAccount does not allow withdrawal until the term matures. But since we have extended Account which allows withdrawal. We must therefore modify the behaviour of withdrawal(). We will override this method in FixedDepositAccount.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

35

Overloading

3.3.8

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

public class FixedDepositAccount extends Account { private Date term; private float interest; public FixedDepositAccount(String n, int acct) { super(n, acct); term = null; interest = -1F; } // setter and getter methods for term and interest . . . public void withdrawal(float amt) { Date today = new Date(); // If mature then we allow withdrawal if (today.after(term)) super.withdrawal(amt); } } Notice that in line 12, we override withdrawal() so that it will check if the term has matured. If it has not, then we will not do anything; but if it has we then allow the withdrawal. Line 16 calls the superclass withdrawal() by prexing it with a super. We can override override superclass methods and members. To overriding a member, declare a member with the same name and type in the subclass. Similarly you can access the overridden superclass member by prexing a super.

3.3.8

Overloading

Overloading is commonly used in Java to dene a number of related methods with the same name but dierent signature. Overloading allows us to tailor a method to dierent use as we shall see in withdrawal() method. You can also overload constructors. Armed with overloading, We will now add more capabilities to FixedDepositAccount. Add an extra constructor such that if no account number is give, it will automatically assign one. Add an additional withdrawal() that withdraws the entire amount. We now can withdraw the entire amount or portion of our balanance depending on the method we use. The code to implement the new functionality is as follows:

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

36

super and this

3.3.9

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

public class FixedDepositAccount extends Account { . . . public FixedDepositAccount(String n, int acct) { super(n, acct); term = null; interest = -1F; } public FixedDepositAccount(String n) { this(n, Math.abs((new Random()).nextInt())); } . . . public void withdrawal(float amt) { . . . } public void withdrawal() { withdrawal(getBalance()); } The following is an explanation of the above code snippet: line 9, This is the overloaded constructor. You only need to supply the account name. line 10, Notice that we use a random number generator to generate an account number. Recall that we use super( . . . ) in page 33 to chain the superclass constructor. Similarly we use this( . . . ) to invoke a constructor in the current class. Again this() must be the rst line in a constructor. line 16, 17, This withdrawal() method withdraws the entire balance from the account. We use getBalance() which we have inherited from Account to get the full balance. Now using withdrawal(float amt) method, we withdrawal the entire amount. Of course the withdrawal is subject to the usual checks.

3.3.9

super and this

We have seen the use of super and this over the last few sections. Lets summarize what we have learnt: 1. Invoking superclass constructor. Use super( . . . ). 2. Invoking current class constructor. Use this( . . . ). 3. Invoking an overridden method or member. Use super.<method>. 37

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

final Keyword

3.4

4. Refering to current class. Use this. like this.<method> or just this which refers to the current instance. The this keyword can also be used to disambiguate the context. Consider the following code:
1 2 3

public void fred(int count) { this.count = count; } The this.count refers the the instance member while count refer to freds formal parameter. Actually, all instance methods and members have an implicit this preceeding them.

3.4

final Keyword

You can use the final keyword on classes, methods and members. A class declared with the final keyword cannot be subclassed. A nal class is declared as follows: public final class Fred { . . . A nal method cannot be overriden by subclasses. This is to prevent subclass from changing the functionalities of your method. For example, you have a method that calculates interest retes; to prevent users form arbitrarily changing your interest rate calculation, you declare the method as nal. Below is a code snippet from FixedDepositAccount which we have looked at previously. If we do not want others to override our withrawal() methods, we add the final keyword as below. Note that the final keyword does not alter the signature of the method.
1 2 3 4 5 6 7 8 9 10

public class FixedDepositAccount extends Account { . . . public final void withdrawal(float amt) { . . . } public final void withdrawal() { withdrawal(getBalance()); } . . . A final member is a member whose values you cannot change once you have assigned a value to it. If we have the following declaration: public final double PI = 3.414;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

38

static Keyword

3.5

then PIs value cannot be changed. If we have a final member whose type is an object like so: public final Account fred = new Account("Fred", 12345); then we cannot change the member itself but we can change the content of the member. So while reassigning a value to fred is not permissible, fred = new Account("Barney", 23456); call a method in Account to modify its member is permissible. Therefore the following is permissible: fred.withdrawal(100F); fred.deposit(200F); When you declare a final member, you have to provide an initializer to the member. If you decide to declare a final member without an initialize, then you will have to initialize that final member in the class constructor, for instance: public class OverdrawnException extends Exception { private final String name; private final int acctNo; private final float balance; private final float withdrawAmount; private final Date date; public OverdrawnException(Account acct, float amt) { super("Overdrawn"); name = acct.getName(); acctNo = acct.getAccountNumber(); balance = acct.getBalance(); withdrawnAmount = amt; date = new Date(); } . . . If you dont, the Java complier will complain this as an error.

3.5

static Keyword

In most programming language, you have the concept of global variables; a global variable is visible from every part of a program. Changes to a global variable can be seen globally. You can declare a member in a class to be global by adding a static keyword in its declaration.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

39

static Keyword

3.5

Lets take a look at what a non-global member in Java terms rst before we look at what a global member is. Consider the following simple class which consists of 1 member: public class Foo { public String name; } Now we instantiate 2 instances of this class and perform some operations on them;
1 2 3 4 5 6

Foo fred = new Foo(); Foo barney = new Foo(); fred.name = "Fred Flintstone"; barney.name = "Barney Rubble"; System.out.println("fred = " + fred.name); System.out.println("barney = " + barney.name); What do you expect the output from lines 5 and 6 to be? The answer should come as no suprise; Fred Flintstone (line 5 ) and Barney Rubble (line 6 ). The name member is known as an instance member because if we change this member, the change will only aect that particular instance viz. local to that instance. So, if we assigned Fred Flinstone to fred.name, then that assigment should only aect fred. Similarly for barney. Now consider adding static to Foo like so: public class Foo { public static String name; } If we were to run the previous sequence of statement again, both the output will be Barney Rubble. What is going on here? The reason is because when we declare any member with static, we make that member to be a class member ; as the name implies, all instance references 1 copy of this member. So if an instance modies a class member, its modication is visible to every other instance of the same class. This is an extremely good approach to sharing information between instance. But you have to ensure that 2 or more instances do not modify a class instance simultaneously. This can potentially cause data corruption. We will address this issue in section 12.5. You do not have to instantiate an instance of a class to use its class member. We can reference a class member by preceeding the members name with the its class name; therefore, if we want to reference name, we can use the following method: Foo.name = "Fred Flintstone"; We can combine final and static to declare constant; for example, to declare a constant, we can declare it in the following way:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

40

static Keyword

3.5

public class Circle { public static final double PI = 3.414; } So you can use PI in the following manner: double circumference = 2 * Circle.PI * radius; In fact, constants declared in the following manner throughout the JDK. For an example, see the Double class in java.lang package. Similarly, you can declare class methods; a class method is declared with the static keyword. The following is a class method declaration:
1 2 3 4 5 6

public class Circle { public static final double PI = 3.414; public static double circumference(double radius) { return (2 * PI * radius); } } There are a few points that you need to remeber about class method: 1. All variables and members used in a class method are assumed to be declared with static. If you try to use an instance inside a class method, the compiler will ag it as an error. The following example illustrates this:
1 2 3 4 5 6

public class Circle { public final double PI = 3.414; public static double circumference(double radius) { return (2 * PI * radius); } } Line 4 is an error because PI in line 2 is declared without a static.

2. You cannot use this inside a class method. this refers to an instance; since a class method is not in an instance, therefore using this would cause the Java compiler to complain. To use a static method, you do not have to instantiate the class; as with class member, just preceed the method name with its class name. Therefore, I can use circumference() in the following way: double myCircle = Circle.circumference(2.1); Can you explain why when we print Hello World to the console we perform the following: System.out.println("Hello World"): 41

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Chapter 4

Abstract Class and Interface


4.1 Abstract Class

Abstract classes are incomplete classes. There are incomplete because when writing the class the behaviour of a method cannot be determine.

4.1.1

Dening an Abstract Class

To declare a class to be abstract, we do the following: 1. Denote the class as abstract with the abstract keyword in the class declaration. 2. Mark unimplemented methods with the abstract keyword. Abstract methods do not have body. Abstract can contain non abstract methods. The general syntax for declaring an abstract class is as follows: public abstract class <class_name> { public abstract void <method_name>(...); } To use an abstract class, we extends it and override those abstract methods by providing them with implementation. Note also the following points about abstract classes: If a subclass does not implement all abstract methods of its superclass, the subclass itself is an abstract class. You cannot instantiate an abstract class. Consider the Account class from the previous chapter. We want to add printing capabilities to Account. Obviously the amount of information printed out by dierent Account subclass are dierent, so when we 42

Dening an Abstract Class

4.1.1

cannot implement the printing at Account level. But we do want to point out to anyone inheriting Account that they do need to consider printing. We therefore declare a method called printAccount() as abstract. The following code does this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

public abstract class Account { private String name; . . . public Account(String n, int no) { . . . } public abstract void printAccount(); public void deposit(float amt) { . . . } public void withdrawal(float amt) { . . . } public void transfer(Account from, float amt) { . . . } } We declare the Account class as abstract by denoting it with abstract keyword in line 1. In line 7, we mark printAccount() as abstract. Abstract methods do not have a method body. We will now have to rewrite FixedDepositAccount and SavingAccount to implement printAccount().

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

public class FixedDepositAccount extends Account { . . . public FixedDepositAccount(String n, int acct) { super(n, acct); } public void printAccount() { System.out.println("Account name = " + getName()); System.out.println("Amount deposited = $" + getBalance()); System.out.println("Mature on = " + getTerm()); System.out.println("Interest rate = " + getIneterest() + "%"); } . . . } public class SavingAccount
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

43

Interface

4.2

17 18 19 20 21 22 23 24 25

extends Account { public SavingAccount(String n, int acct) { super(n, acct); } public void printAcount() { System.out.println("Account name = " + getName()); System.out.println("Current balance = $" + getBalance()); } } Lines 7 and 21 implement the abstract method printAccount(). When you implement an abstract method you drop the abstract keyword.

4.2

Interface

What is an interface? It is a point where two entities meet. Lets consider a Walkman. A Walkman has a play button. We know that when we press it, the Walkman will play whatever tapes happens to be in it. The Play button is therefore an interface between you and the Walkmans internal machinery. Besides the play button, the Walkman has other buttons such as forward, backward, stop, etc. These buttons constitute a cassette players interface. When you purchase a cassette player, you would expect these buttons (functions) to be present. Any cassette player with any one of these functions missing would deemed incomplete.

User

I N T E R F A C E

Provider

Figure 4.1: Concept of an interface The Walkman example highlights the following point:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

44

Implementing an Interface

4.2.2

There are two sides in every interface. One is the provider, what services it provides. The other is the user. The interface hides the implementation from the user. Your focus is on the interface, not the implementation. An interface guarantees all functions are complete. Figure 4.1 illustrates the concept of an interface.

4.2.1

Dening an Interface

In Java, an interface is a set of methods under a name. These methods are very much like abstract methods in that they have no implementation. To implement an interface we use the interface keyword. An interface has the following syntax: public interface <interface_name> [ extends <interface_1>, ...] { <method_1>; <method_2>; . . . } Optionally an interface can inherit any number of super interfaces via the use of extends keyword.

4.2.2

Implementing an Interface

Classes can implement any number of interface. To implement an interface, classes use the keyword implements in the class declaration. A class that implements an interface must provide implementation for all its methods and methods in its super interface. The syntax is as follows: public class <class_name> implements <interface_0>[, <interface_1>, ...] { . . . } We will now add interest calculation to Account. We are well aware that interest rate uctuates and the formula for calculating interest rates changes. We cannot hardcode this calculation into Account; otherwise we need to recompile everytime the interest calculation changes. We can use an interface to overcome this problem. This is how we do it: 1. Dene an interface call Interest. This interface has one single method called calculate() which performs interest calculations.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

45

Implementing an Interface

4.2.2

2. Add a method in Account that accepts an Interest interface. Call calculate() when we want to calculate interest. The Interest looks like this:
1 2 3

public interface Interest { public float calculate(float amt); } calculate() takes in the amount of money that you want to calculate the interest on and returs the interest on the input amount. You have to save this interface in a le called Interest.java just like a class. Lets implement two interest which we will call GenerousRate and TightRate.

1 2 3 4 5 6 7 8 9 10 11 12 13

public class GenerousRate implements Interest { public float calculate(float amt) { return (amt * 0.5F); } } public class TightRate implements Interest { public float calculate(float amt) { return (amt * 0.01F); } } Notice the way the Interest interface is dened and implemented: Interface and its implementation are totally separate. For 1 interface, multiple implementations are possible. In our Interest example, there are two implementations. We now turn our attention to Account. The listing is as follows:

1 2 3 4 5 6 7 8 9 10

public abstract class Account { private String name; . . . private Interest interest; public Account(String n, int no) { name = n; acctNo = no; balance = 100F; overdraft = false; interest = null;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

46

Dierences between Abstract Class and Interface

4.3

11 12 13 14 15 16 17 18 19 20 21 22 23

} public abstract void printAccount(); public void setInterestCalculation(Interest i) { interest = i; } public float calculateInterest() { if (interest != null) return(interest.calculate(getBalance())); else return(0F); } . . . } Comments on the above code: line 13 to 15, We dene a method to set interest calculation. Notice that we use interest to hold an Interest interface. line 16 to 21, When we invoke this method, Account will check if an interest calculation has been set. If it has then we call the calculate() method, otherwise we return 0. The entire key to interface hinges on this: we reveal an objects interface (methods therein) without relying on any one class (line 18 ). The JVM will lookup the actual implementation during runtime viz. at the point time of invocation. Lets look at somemore code to see how all these works:

1 2 3 4 5

SavingAccount fred = new SavingAccont("Fred", 12345); fred.setInterestCalculation(new TightRate()); System.out.println("tight = " + fred.calculateInterest()); fred.setInterestCalculation(new GenerousRate()); System.out.println("generous = " + fred.calculateInterest());

4.3

Dierences between Abstract Class and Interface

Although abstract classes and interface provide similar features, the are in fact quite dierent. Table 4.1 list of their dierences.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

47

Dierences between Abstract Class and Interface

4.3

Abstract Class You extends an abstract class. Can only subclass 1 abstract class. You are allowed to selectively override any abstract methods. Static in the sense that it is compiled into a class.

Interface You implements and interface. You can implement more than 1 interface. You must implement all the methods in it. Dynamic because you can change an interfaces implementation at runtime.

Table 4.1: Dierences between abstract class and interface

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

48

Chapter 5

Building GUIs with AWT


The graphical user interface (refer to GUI henceforth) is an important part of the modern day application. Like most programming libraries, the JDK allows you to develop GUI front ends by using the Abstract Windowing Toolkit or more commonly know as the AWT.

5.1

Component and Container

The basic GUI building blocks in AWT are Components and Containers. Components is the base class of all AWT widgets (eg. Button, List, Choice, etc.). We will use the word component to refer to AWT widgets collectively and Component to refer specically to the class. As the base class, Component provides the following methods which are common across all components: public void paint(Graphics g) Called when the Container needs to update the GUI. Control will be passed to this method for drawing. Graphics is an abstraction of the screen. We will look at Container and Graphics shortly. public void repaint() Force an update of the component; call this method when you need to the component to be redrawn. public void setBackground(Color c) Sets the background color of a component. public void setForeground(Color c) Sets the foreground color of a component. public void setEnabled(boolean b) Enable or disable a component. A disabled component is greyed out and is not usable; eg. a disabled Button cannot be pressed.

49

Component and Container

5.1

public void setFont(Font f) Sets the font for this component. We will look at manipulating fonts in section 5.6.1. public void requestFocus() Set the mouse focus to this component. The above is not an exhaustive list of all methods; please consult the the Component API documentation. The Container class, as the name implies acts as a container for components. To built a GUI, you add components in to Containers. Here is the catch: since Containers are themselves subsclass of Component, you can add Containers to Containers! Using this technique, we can build very complex GUIs. The following are important methods in Container class: public Component add(Component c) Adds a component to a Container. public Component add(String name, Component c) Adds a component to a name location in Container. public void setLayout(LayoutManager mgr) Sets a LayoutManager to the Container. We will look at LayoutManager in section 5.2. Lets take a birds eyeview of building a simple GUI with Components and Containers before we proceed further.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

import java.lang.*; import java.awt.*; public class AFrame { public AFrame() { Button button = new Button("Press Me!"); button.setBackground(Color.red); button.setForeground(Color.white); Frame frame = new Frame("A Frame"); frame.setLayout(new FlowLayout()); frame.setBackground(Color.yellow); frame.add(button); frame.setSize(200, 100); frame.setVisible(true); } public static void main(String[] args) { AFrame aFrame = new AFrame(); } }
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

50

LayoutManager

5.2.1

The GUI produced by the previous code snippet is shown in gure 5.1.

Figure 5.1: GUI produced by AFrame.java An explanation of AFrame.java is as follows: line 2, Unlike java.lang package which is imported by default, you have to import java.awt package manually otherwise your program will not compile. line 5, Recall Button is a subclass of Component. We create an instance of Button. line 6 and 7, Sets buttons foreground and background color. line 8, We create an instance of Frame which is a subclass of Container. line 9, Set the layout or arrangement viz. how components in frame will be arranged. line 11, Add the button to the frame for display. line 12, Sets the frames size for display. The width and height is 200 by 100 pixel respectively. line 13, Finally, when all the setup is completed, we display the frame by setting its visible property to true.

5.2

LayoutManager

The layout of components within a Container is done by LayoutManager. Before you add any components to a Container you need to set the layout or arrangement of the Container using setLayout() method. The LayoutManager controls the placement of components according to its the LayoutManagers layout policy. The LayoutManager controls all aspect of a component in the Container: its placement, size, orientation, etc. The JDK comes with 5 LayoutManagers. We will examine only the following 3: FlowLayout, GridLayout and BorderLayout.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

51

FlowLayout

5.2.2

5.2.1

FlowLayout

FlowLayout arranges components in a Container like sentences in a page; from left to right and from top to bottom. FlowLayout will try to t as many components on a line as possible before moving them to the next. When you resize a Container, FlowLayout will rearranges the components according to the new Containers size. The following code adds 3 Buttons to a FlowLayout managed Container.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

import java.lang.*; import java.awt.*; public class FlowLayoutEx { public FlowLayoutEx() { Frame frame = new Frame("FlowLayout"); frame.setLayout(new FlowLayout()); frame.add(new Button("One")); frame.add(new Button("Two")); frame.add(new Button("Three")); frame.setSize(200, 100); frame.setVisible(true); } public static void main(String[] args) { FlowLayoutEx ex = new FlowLayoutEx(); } } Notice that in line 6, we set the FlowLayout to be the layout of the frame. All subsequent add (line 7 to 9 ) will be under FlowLayouts control. When you run FlowLayoutEx program, you will see the frame as shown in gure 5.2. When the frame is resized, the buttons will adjust to accomodate the new size.

Figure 5.2: Container managed by FlowLayout

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

52

BorderLayout

5.2.3

5.2.2

GridLayout

The GridLayout devides the Container into a speciced number of rows and columns, very much like a matrix. Components in the grid are arranged from left to right and from top to bottom. The size of each cell is the size of the largest component in the grid. The following code snippet creates a 3 rows by 2 columns grid.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

import java.lang.*; import java.awt.*; public class GridLayoutEx { private String[] label = {"A", "B", "C", "D", "E", "F" }; public GridLayoutEx() { Frame frame = new Frame("GridLayout"); frame.setLayout(new GridLayout(3, 2)); for (int i = 0; i < label.length; i++) frame.add(new Button(label[i])); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { GridLayoutEx ex = new GridLayoutEx(); } }

Figure 5.3: Container managed by GridLayout Do not over or under add() a GridLayout managed container; otherwise the layout of your component will be unpredictable.

5.2.3

BorderLayout

BorderLayout divides a container into 5 region. They are north, south, east, west and center. When you add a component to a BorderLayout managed container, you need to specify the region or your component will not show up when you display the container. The following code adds 5 buttons to the 5 region in BorderLayout.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

53

BorderLayout

5.3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

import java.lang.*; import java.awt.*; public class BorderLayoutEx { public BorderLayoutEx() { Frame frame = new Frame("BorderLayout"); frame.setLayout(new BorderLayout()); frame.add(new Button("North"), BorderLayout.NORTH); frame.add(new Button("South"), BorderLayout.SOUTH); frame.add(new Button("East"), BorderLayout.EAST); frame.add(new Button("West"), BorderLayout.WEST); frame.add(new Button("Center"), BorderLayout.CENTER); frame.setSize(200, 200); frame.setVisible(true); } public static void main(String[] args) { BorderLayoutEx ex = new BorderLayoutEx(); } } Note lines 7 to 11; we specify the region the component is to be added. The rst parameter of add() is the component itself, in this case a Button while the second is the position. The result of BorderLayoutEx is shown in gure 5.4.

Figure 5.4: Container managed by BorderLayout

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

54

Using Panel in Complex GUIs

5.3.1

5.3

Container

Components cannot exist in limbo. They must be added to containers. The JDK java.awt. package have dierent containers; they are 1. Panel Panels are your basic component laying containers. They can be added to Frames, Dialog, ScrollPane and Panel. 2. ScrollPane These are scrollable Panels. 3. Frame This is a top level application window. 4. Dialog These are temporary frames for prompting users and for displaying messages. We will examine Panel and Frame in greater detail in the following section. ScrollPane and Dialog are variations of Panel and Frame and will be left as an exercise for the reader.

5.3.1

Using Panel in Complex GUIs

Panels allow us to create very sophisticated GUIs. Although Panels themselves are not part of the visible GUI, they provide layout control as we will shortly see. Consider the calculator in gure 5.5.

Figure 5.5: A Java calculator Although the layout of the caluclator does not resemble any of those that we have seen, it is in fact done by cleverly combining 2 layouts and 2 Panels. The code to create the calculator is show below:
1 2 3 4

import java.lang.*; import java.awt.*; public class Calculator extends Frame {


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

55

Frame

5.3.2

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

private String[] label = {"1", "2", "3", "+", "4", "5", "6", "-", "7", "8", "9", "*", "C", "0", "=", "/"}; public Calculator() { super("Calculator"); Panel numberPad = new Panel(); numberPad.setLayout(new GridLayout(4, 4)); for (int i = 0; i < label.length; i++) numberPad.add(new Button(label[i])); Panel base = new Panel(); base.setLayout(new BorderLayout()); TextField display = new TextField("0", 10); display.setBackground(Color.black); display.setForeground(Color.green); base.add(display, BorderLayout.NORTH); base.add(numberPad, BorderLayout.CENTER); add(base, BorderLayout.CENTER); pack(); } public static void main(String[] args) { Calculator calc = new Calculator(); calc.setVisible(true); } } The key to the calculator application lies in line 20. line 10 to 13, Creates a Panel call numberPad. This is our calculators keypad. line 14 and 15, We instantiated another Panel and set this to BorderLayout. line 19, We add the display which we created in line 16 to the north of base line 20, Add the numberPad to the center of base. You can add as much layers of Panel as you like to create the desire layout. A good habit to acquire when developing GUI using AWT is to rst draw the GUI. Study the GUI and decide on the most optimal way to construct the GUI. The default layout of Panel is FlowLayout.

5.3.2

Frame

All components must be added to a Frame to be visible. Frames are standalone componets; unlike Panel a Frame cannot be added to another Frame
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

56

Button

5.4.1

even though it is a subclass of Container. The class Frame has the following 2 constructors: public Frame() Creates an instance of a Frame without a title. public Frame(String title) Creates an instance of a Frame with the specied title. Frames by default are BorderLayout managed. To display a Frame you have to perform the following steps: 1. Set the size of the Frame. You can do this by either using the setSize() or pack() method. pack() tells the Frame to use the smallest possible size to display its components. 2. Make the Frame visible. This is done by calling setVisible() with true. If you forgot to set the window size, the Frame will not be visible even though you callsetVisible(). visible. Other useful methods include: public void setTitle(String title) Sets or changes the Frames title. public void hide() Hides the Frame. This is equivalent to calling setVisible() with false. public void setMenuBar(MenuBar mb) Sets a menu bar to the Frame. We will look at menus and menu bars in section 5.8. public void setResizable(boolean b) Allows the Frame to be resized. public void dispose() Before discarding a Frame instance, it is always good to call this method. dispose() frees all resources associated with the Frame instance.

5.4
5.4.1

AWT Components
Button

To create a button we us the Button class. The Button allows label but no images to be display on its face. The following code creates a button with the label Press Me!.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

57

CheckboxGroup

5.4.4

Button button = new Button("Press Me!");

5.4.2

Checkbox

Checkbox provides a simple two state button. Figure 5.6 is created by the following code:
1 2 3 4 5

Panel panel = new Panel(); panel.setLayout(new GridLayout(1, 3)); panel.add(new Checkbox("Adidas")); panel.add(new Checkbox("Nike")); panel.add(new Checkbox("Reebok"));

Figure 5.6: A Checkbox component Checkbox has the following 2 constructors: public void Checkbox(String label) Instantiate a Checkbox with label. public void Checkbox(String label, boolean state) Instantiate a Checkbox setting the state of the newly created Checkbox to state. The default state is false.

5.4.3

CheckboxGroup

Checkboxes may optionally be part of a CheckboxGroup which simulates radio button behaviour. A Checkbox can specify a CheckboxGroup object when instantiated. When a Checkbox within a CheckboxGroup is selected, the CheckboxGroup ensures that the previously selected Checkbox becomes unselected. The previous Checkbox example can be converted to a CheckboxGroup as follows:
1 2 3 4 5 6

CheckboxGroup cbg = new CheckboxGroup(); Panel panel = new Panel(); panel.setLayout(new GridLayout(1, 3)); panel.add(new Checkbox("Adidas", false, cbg)); panel.add(new Checkbox("Nike", true, cbg)); panel.add(new Checkbox("Reebok", false, cbg));

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

58

List

5.4.5

5.4.4

Choice

This class represents a drop down list. A Choice is shown in gure 5.7.

Figure 5.7: A Choice component The add() method adds an item item with the specied label to Choice. The following illustrates its use:
1 2 3 4

Choice choice = new Choice(); choice.add("Adidas"); choice.add("Nike"); choice.add("Reebok"); Other methods in Choice includes: public String getSelectedItem() Returns the label of the selected item. public int getItemCount() Returns the number of items in a Choice. public void remove(String label) Removes an item. public void select(String str) Programatically selects an item from a Choice.

5.4.5

List

A List component is used to display a list of strings. The List is scrollable if necessary. The List constructor takes two optional arguments that species the number of visible rows in the list window and whether multiple selection in the List is possible.
1 2 3 4

List list = new List(4, true); list.add("Adidas"); list.add("Nike"); list.add("Reebok");


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

59

Label

5.4.7

creates a List with 4 visible items and allows multiple selection as shown in gure 5.8.

Figure 5.8: A List component Other userful methods in this class are: public void add(String item) Adds an item to the List. public void remove(String item) Removes an item from the List. public int getItemCount() Returns the number of items in the List. public String getSelectedItem() Returns the selected item. public String[] getSelectedItems() Returns all selected items if multiple selection is enabled. public void select(int index) Programatically selects an item at position index. public void deselects(int index) Programatically deselects an item at position index. public void makeVisible(int index) Makes the item at position index visible from the List window. public void setMultipleMode(boolean b) Enable/disable multiple selection mode for the List.

5.4.6

Label

Label as the name implies are a single line of text used for labelling components. A label is created as follows:
1

Label label = new Label("Name:");


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

60

TextArea

5.4.8

5.4.7

TextField

These are single editable line of text. TextField is normally used in forms type applications where the user is required to ll in their personal details. The following creates an entry to solicit a users name:
1 2 3

Panel panel = new Panel(); panel.add(new Label("Name: ")); panel.add(new TextField(30)); The code produces the gure 5.9.

Figure 5.9: A TextField component Other interesting TextField methods are: public void selectAll() Highlights the text in the TextField. public void setEditable(boolean b) A bit like setEnabled() in Component. This method makes the TextField either editable or uneditable. public void setText(String msg) Set the text msg in the TextField. public void setCaretPosition(int index) Sets the caret at the specied position. public void setEchoChar(char c) Displays the character c in the TextField for any characted typed into the TextField. This is normally used for password entry.

5.4.8

TextArea

This class displays and allows you to edit multi line text. The methods appendText(), insertText() and replaceText() provides various techniques for specifying text to appear in the TextArea. You can also use the above mentioned TextField methods with TextArea. The following code creates a TextArea that is 40 columns wide and 5 rows high.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

61

repaint() Method

5.5.3

TextArea textArea = new TextArea(5, 40); Horizontal and vertical scrollbars will automatically appear when required.

5.5

The AWT Paint Cycle

We have seen how most of the components behave. A question arises: what if we want to customize components? How do we add wall paper to a Panel or an image to a Button? To be able to do these and more, we must rst understand the AWT paint cycle. The key to customization lies in the following methods: paint(), update() and repaint(). The previously mentioned 3 methods are part of Component. Recall that Component is the superclass of all components (widgets). When a component is rst displayed, the JVM will call the paint() method of each component in a Frame; these components draw themselves on the screen. The components will redraw themselves again when either one of the following 2 events occurs: Exposure This happens when we resize a frame, an overlapped portion of the frame is exposed or whenever the JVM thinks that the component requires to redraw itself on the screen to update its appearance. In this case, the JVM will call paint() directly. Forced When a program wants to update the screen eg. an animated button needs to draw the next image. The program can call repaint() to force a redraw.

5.5.1

paint(Graphics) Method

The paint() method handles most of the painting of the components. The signature for paint() is as follows: public void paint(Graphics g) The Graphics parameter is an abstract representation of the screen. Whatever we draw on Graphics will appear on the screen. We will look at Graphics in section 5.6 in page 63. To customize a component, we override this paint() method.

5.5.2

repaint() Method

Sometimes we want to update a component on the window or the entire window all together. We invoke repaint() which schedules a cal to update(). The following sequence of method cals occurs: repaint() update() paint()
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

62

Graphics

5.6.1

5.5.3

update(Graphics) Method

update() is an intermediate method call when we call force a redraw but is never involved on an exposure update. Why is this so? The reason is to allow more control over the painting process. The default behaviour of update() is as follows: 1. Clears the component by lling the component with its background color. 2. Calls paint() with Graphics that it receives. For most application, the default behaviour is sucient. But if you are doing animation, developing arcade style games or anything along those lines you might want to override update(). The reason is twofold: rstly, it is quite inecient to redraw the entire screen; and secondly excessive clearing and redrawing will cause the screen to icker. We will not look into this subject any further.

5.6

Graphics

The Graphics is an abstraction of the screen. What you draw on Graphics will appear on the screen. The Graphics class provides some highlevel drawing methods like drawing lines, ovals, etc. The following is a partial list of methods. public void drawString(String str, int x, int y) Draws str on (x, y). Note that (x, y) refers to the lower left corner of the str instead of the the normal upper left corner. public void drawLine(int x_1, int y_1, int x_2, int y_2) Draws a line from (x_1, y_1) to (x_2, y_2). public void drawOval(int x, int y, int width, int height) Draws an oval. public void drawRect(int x, int y, int width, int height) Draws a rectangle. public void setColor(Color c) Sets the color for the drawing pen. public void setFont(Font f) Sets the font to be used.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

63

Using Font

5.7

5.6.1

Using Font

JDK 1.1 supports the following font types: Serif, SansSerif, Monospaced, Dialog and DialogInput. Besides the JDK 1.1 include fonts, you can also use the fonts installed on the native operating system. The following code snippet gets a list of installed fonts from the operating system which you can then use.
1 2

Toolkit toolkit = Toolkit.getDefaultToolkit(); String[] installedFonts = toolkit.getFontList(); The Font class has the following constructor: public Font(String name, int style, int size) name This is the name of the font. You can either use the JDK 1.1 default font or query the operation system for the required font. style The style can be one of the following: PLAIN, BOLD or ITALIC or the sum of BOLD and ITALIC. size The size of the font. The following creates a Monospaced, 20 point font. The font style is bold and italicised. The output is shown in gure 5.10.

1 2 3 4

public void paint(Graphics g) { g.setFont(new Font("Monospaced", Font.BOLD + Font.ITALIC, 20)); g.drawString("Hello World!", 10, 100); }

Figure 5.10: An example of using font

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

64

Customizing Components

5.7

5.7

Customizing Components

We have seen all the required pieces of Java techology to allows us to customize components. Lets put these dierent pieces together and see how it all ts together. We will create a customize Panel by giving it a wallpaper of squares as shown in gure 5.11.

Figure 5.11: PrettyPanel with a button The code to create PrettyPanel is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

import java.lang.*; import java.awt.*; public class PrettyPanel extends Panel { public PrettyPanel() { setBackground(Color.black); } public void paint(Graphics g) { Dimension size = getSize(); g.setColor(Color.red); int count = size.width/10; for (int i = 1; i < count; i++) g.drawLine(i*10, 0, i*10, size.height); g.setColor(Color.blue); count = size.height/10; for (int i = 1; i < count; i++) g.drawLine(0, i*10, size.width, i*10); } } The explanation is as follows: line 2, Do not forget to import the java.awt package.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

65

MenuBar

5.8.2

line 5, Since constructors are not inherited, we provide a constructor. line 6, Just for fun, we set the default background to black. line 8, We override the paint() method to provide our own drawing. line line 9, Get the size of the Panel. We need to know the size so that we can draw the appropriate number of lines. Remember, Graphics represents the display area of PrettyPanel and getSize() gives us this area. line 10 to 13, We draw red vertical lines, 10 pixel apart. line 14 to 17, We draw blue horizonal lines, 10 pixel apart. Using PrettyPanel is like using your regular Panel; consider the following code:
1 2

PrettyPanel pretty = new PrettyPanel(); pretty.add(new Button("Press Me!"));

5.8

Adding Menus to Frame

You can set menus to frame. These are the normal pulldown menus that appear on top of a frame. To add a menu to a Frame, you need to perform the following steps: 1. Add a MenuBar to a Frame by calling setMenuBar() method. Only 1 MenuBar instance can be added to a Frame. 2. Add Menus to MenuBar. These will appear as labels on the MenuBar. 3. Add MenuItem and/or CheckboxMenuItem to Menu. 4. You can optionally add a Menu within a Menu. The former becomes a submenu of the latter.

5.8.1

MenuBar

A MenuBar is required before you can create menus. The following code adds a MenuBar to a Frame:
1 2 3

Frame frame = new Frame("Menu"); MenuBar menuBar = new MenuBar(); frame.setMenuBar(menuBar);

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

66

MenuItem and CheckboxMenuItem

5.9

5.8.2

Menu

Menus are labels that are displayed on the MenuBar. We will now add 2 menus, File and Edit to a MenuBar. The following code is a continuation of the example form the previous section.
1 2 3 4 5 6

Menu fileMenu = new Menu("File"); Menu editMenu = new Menu("Edit"); Menu aboutMenu = new Menu("About"); menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.setHelpMenu(aboutMenu); Line 6 adds a help menu to the MenuBar. At the name implies, a help menu provides help, information and the usual about dialog box. You can only set one help menu per MenuBar instance.

Figure 5.12: An example of a Frame with a MenuBar

5.8.3

MenuItem and CheckboxMenuItem

MenuItem and CheckboxMenuItem are the leaf node in Menu. Typically, MenuItem and CheckboxMenuItem would represent the actual functions that we wish to provide to the user. MenuItem and CheckboxMenuItem must be added to Menu. We now add Save, Save As. . . , Open and Exit to File menu. Again, the code continues from the previous example.
1 2 3 4 5

fileMenu.add(new MenuItem("Save")); fileMenu.add(new MenuItem("Save As...")); fileMenu.add(new MenuItem("Open")); fileMenu.addSeparator(); fileMenu.add(new MenuItem("Exit")); The addSeparator() on line 4 adds a line to the menu; see gure 5.13.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

67

AWT Events

5.9

Figure 5.13: A menu with MenuItems

5.9

AWT Events

We have seen how easy it is to use AWT components to create GUI. But our GUI is still deviod of life. The GUI does not know how to respond to mouse clicks. For that we need to understand events which we will examine in the chapter 7.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

68

Chapter 6

Inner Class
The JDK 1.1 introduces a number of new features to the Java language; one of these is inner classes. Inner classes are simply classes within classes. There are 4 categories of inner classes: Nested top level classes and interfaces Classes and interfaces dened with the static keyword. Cannot access containing class methods and members. Member class Member classes are classes without the static keyword. Member class can access containing class methods and members, even private denoted ones. Local class Local classes are like member classes except that they are dened within a block, typically within a method. Hence they are only visible within a block. Anonymous class These are nameless classes. They are dened and used only once. Of the 4 types of inner classes mentioned above, we personally feel that member classes and anonymous classes are the most useful. As such we will be focusing our attention on them. The remainding 2 inner classes are variations of member and anonymous class.

6.1

Member Class

Member class are particularly useful for dening utility or helper classes. Prior to JDK 1.1, helper classes must be dened as a separate top level class. These helper class are not very closely related to the main class in the sense that they cannot access the main class internal methods and members. You have to resort to parameter passing. This can sometimes be very crumblesome. Member classes overcomes these diculties. When using member class, you have to remember the following: 69

Member Class

6.1

An instance of a member class must always be associated with an instance of its containing class. A member class can access its methods and members as well as those of its containing class, including private methods and members. We will demonstrate the use of member class by adding the following features to the Account on page 43 so the it performs the following: When a subclass of Account is instantiated, it will install a default interest calculation method. A user can install its own interest rate calculation method via the setInterestCalculation() method. The new Account class looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

public abstract class Account { private String name; private float balance; . . . private Interest interest; public class DefaultRate implements Interest { public float calculate(float amt) { return (balance * 0.1F); } } public Account(String n, int no) { interest = new DefaultRate(); } . . . public void setInterestCalculation(Interest i) { interest = i; } public float calculateInterest() { return (interest.calculate(getBalance())); } . . . } The explanation for the above code is as follows: line 6 to 11, This is the member class. Notice that declaring a member class is just like dening a regular top level class. Our inner class is called DefaultRate and it implements the Interest interface. 70

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Referencing Containing Class Members

6.1.1

line 9, Since member class can access its containing class members, we directly access balance to calculate the interest ignoring amt. line 13, When we instantiate Account we set the interest member with an instance of DefaultRate. When do we use member class? We oer the following guidelines: When you require a helper or a utility class. You are dening a class and this class is of no use to any other classes except one. Two classes have interwind relationship. Compiling Account will now produce not 1 but 2 classes. In addition to the Account.class, you will also nd the class Account$DefaultRate.class which is the inner class.

6.1.1

Referencing Containing Class Members

Recall that all instance methods and members are implicitly prexed by a this. Does this apply for inner classes as well? Lets take a look again at line 9 of Account which is reproduced below: return (balance * 0.1F); If we follow the usual rule and rewrite the above to this return (this.balance * 0.1F); we would get an error message. This is because this refers to the current instance and the current instance which is an instance of DefaultRate does not contain a member called balance. balance is actually the containing class member. So using this would be wrong. To access the containing class member, we use a variation of the this. The syntax is <class_name>.this where <class_name> is the name of the containing class. Line 9 would look like the following when written in its entire form: return (Account.this.balance * 0.1F); The following class further illustrates this concept:
1 2 3 4 5

public class Outer { public String msg = "outer"; public class Inner { public String msg = "inner"; public class InnerInner {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

71

Instantiating Member Classes

6.2

6 7 8 9 10 11 12 13 14 15

public String msg = "innerinner"; public void print() { System.out.prinln("msg = " + msg); System.out.prinln("msg = " + InnerInner.this.msg); System.out.prinln("msg = " + Inner.this.msg); System.out.prinln("msg = " + Outer.this.msg); } } } } The output will be msg msg msg msg = = = = innerinner innerinner inner outer

6.1.2

Instantiating Member Classes

Instantiating member classes presents a few problems. Remember that a member class must always be associated with its containing instance. So we have to instantiate the containing class rst before creating the member class. The syntax for instantiating a member class is <containing_instance>.new. The following code snippet creates an instance of all member class in Outer:
1 2 3 4

Outer outer = new Outer(); Outer.Inner inner = new outer.new Inner(); Outer.Inner.InnerInner innerInner = new inner.new InnerInner(); innerInner.print(); Note the following about instantiating member classes. line 1, Creates an instance of Outer rst. line 2, Creates an instance of Inner within outer. line 3, Creates an instance of InnerInner within inner. In the new, we name the class to be created relative to the instance that is going to contain it like so inner.new InnerInner() and not inner.new Outer.Inner.InnerInner() 72

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Anonymous Class

6.2

6.2

Anonymous Class

Anonymous classses are classes without names. Unlike member class where you have to dene and instantiate the class to be used; anonymous classes combine these 2 steps into 1. Lets take a look at an example of anonymous class before discussing in greater detail. We rewrite (again!) Account using anonymous class instead of member class as in page 70.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

public abstract class Account { private String name; private float balance; . . . private Interest interest; public Account(String n, int no) { . . . interest = new Interest() { public float calculate(float amt) { return (balance * 0.1F); } }; } . . . } The lines between 8 and 12 inclusive denes an anonymous class. Anonymous classes can be dened in 2 ways: new <class_name>([<type> <param_1> ...]) { <class_body> } or new <interface_name>() { <interface_body> } When you are creating an anonymous class using an interface, remember you are implementing the interface so you must implement all the interfaces method. The anonymous class syntax combines class denition with instantiation. Anonymous classes cannot have constructors. However if you really need to initialize the class you can resort to instance initializers. Refer to the following section. When do you use anonymous class? You would use them if
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

73

Instance Initializers

6.2.1

A class has a very short body. Only one instance of the class is needed. The class is use immediately after being dened.

6.2.1

Instance Initializers

Instance initializers are arbitrarily blocks of code in a class denition. They are executed after the superclass constructor but before the class constructor. If a class has multiple instance initializers, then they are executed in the order in which they appear. We add an instance initializer to the previous anonymous class example. The anonymous class calculates the interest rate to use based on the month; only the constructor is shown:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

. . . public Account(String n, int no) { . . . interest = new Interest() { private float monthlyRate[] = { 0.1F, 0.07F, 0.1F, 0.1F, 0.1F, 0.12F, 0.1F, 0.1F, 0.08F, 0.1F, 0.1F, 0.05F }; private float rate; { rate = monthlyRate[ (Calendar.getInstance()).get(Calendar.MONTH)];} } public float calculate(float amt) { return (balance * rate); } } } . . . The instance initializer (lines 7 to 10 ) gets the appropriate interest rate according to the month when the anonymous class is instantiated.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

74

Chapter 7

Handling AWT Events


You are asked to develop a forms front end, much like the one shown in gure 7.1. Your front end has to be generic as lots of existing and new yet undevelop applications will be using it. It must be modular and allows on-the-y customization; eg. one application uses the the HKID eld as an index into a database to verify the input data while another runs an hash function to verify its correctness. More importantly, if any of the underlying information changes, the form must be able to update itself.

Figure 7.1: A typical GUI front end None of these requirements presents an insurmountable programming challange. If you were to write the code that handles each requirement, it would add signicant coding to your overall eort. The way to tackle it would be to implement the said GUI using the model/view/controller (MVC for short) introduced in Smalltalk-80. We will begin this section by looking at the MVC architecture and how 75

The Model

7.1.1

they inuence the desgin of AWT. We will then move on to handling AWT events and eventually creating our very own custom events.

7.1

The MVC Architecture

The MVC was designed to reduce the overall programming eort by delegating specic responsibilities to the model, view and controller. Figure 7.2 illustrates the MVC architecture.

View

Controller

View

Model

Controller

View

Controller

Figure 7.2: The model/view/controller architecture The MVC has the following benets: Encourages the application to be data (model) centered instead of user interface (view) centered. A well dened separation between components of a program. Each component can be written separately. Each component has a well dene set of API. Future enhancements and components written to the API can replace either the model, view or controller. The binding between the 3 components are dynamic and occurs at runtime.

7.1.1

The Model

The model is an object that represents the data. This can be a le, a matrix stored in an array or records held a database. The model has no specic
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

76

Identifying Source, Listener and Event Objects

7.2.2

knowledge of either its views or controllers. If the model changes, the model is responsible for notifying its view. If a particular piece of data requires validation for example, the model is responsible for invoking the appropriate controller.

7.1.2

The View

Views are the faces of the model. A records data can be represented by dierent views. Examples of views include the traditional window GUI, a HTML page, LED displays or your humble text. The view is also responsible for representing the data in dierent forms: as a graph, a pie chart, etc.

7.1.3

The Controller

The controller provides a means of interacting with the model. Validating date elds and checking the existance of records are just 2 examples of what you can do with controllers.

7.2

Events

Events are messages sent from one object to another, notifying the recipient that something interesting has happened. Events are key to implementing MVC in Java. A view might sent out an event, notifying the model that the user has changed portion of the data. The model, on receiving the event, res another event to the various controllers to validate the new data, for example. When the controllers have completed their job, the model saves the data into a database. Components sending the event, the event source, is said to re the event; the components receiving the events are called event listeners. Events can be single or multicast.

7.2.1

The AWT Event Model

The current AWT event model, introduced as part of the revamped AWT in JDK 1.1, is called the delegate model. It is comprised of event listeners, event sources and event objects. The delegate model works as follows: an event source sends a notication to the listener and passing to the listener an event object. Event listeners must register themselves with event sources in order to receiving notications. Figure 7.3 is a diagram of this interaction.

7.2.2

Identifying Source, Listener and Event Objects

The entire AWT is based on the delegate model. Capturing a Button pressed or a mouse clicked require us to register and capture the correct event.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

77

Identifying Source, Listener and Event Objects

7.2.2

Event Source Register event listener Event Listener

Fire event

Event Object

Figure 7.3: Event source and listeners As it turns out, the delegate model follows a certain naming convention, very much like the getter/setter method for encapsulating properties. The naming convention are as follows: Event source Event sources provides registration and deregistration for listeners. To identify them look for the following signature: public void addXXXListner(XXXListener) public void removeXXXListner(XXXListener) Event listener Look for classes that implements the XXXListener interface. These are listeners for the XXX event. Event object An event object has the following name XXXEvent. Every method in the XXXListener interface will have this one and only object as parameter. Lets look at a concrete example: to capture a Button click, we must capture the action event. Using the above mentioned naming conventions, we know the following: Action event sources are those objects which have the following methods:

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

78

Event Listener Interface

7.2.3

public void addActionListener(ActionListener l) public void removeActionListener(ActionListener l) Action event listeners are those objects which implements the ActionListener interface. ActionEvent is the event object passed from the event source to the event listener. The table 7.1 show a list of all AWT events.
Component Button List MenuItem TextField Scrollbar Component Listener Interface ActionListener Listener Methods public void actionPerformed() Event Class ActionEvent

AdjustmentListener ComponentListener

Container Component Checkbox CheckboxMenuItem Choice List Component

ContainerListener FocusListener ItemListener

public public public public public public public public public public

void void void void void void void void void void

adjustmentValueChanged() componentHidden() componentMoved() componentResized() componentShown() componentAdded() componentRemoved() focusGained() focusLost() itemStateChanged()

AdjustmentEvent ComponentEvent

ContainerEvent FocusEvent ItemEvent

KeyListener

Component

MouseListener

Component TextComponent Window

MouseMotionListener TextListener WindowListener

public public public public public public public public public public public public public public public public public public

void void void void void void void void void void void void void void void void void void

keyPressed() keyReleased() keyTyped() mouseClicked() mouseEntered() mouseExited() mousePressed() mouseReleased() mouseDragged() mouseMoved() textValueChanged() windowActivated() windowClosed() windowClosing() windowDeactivated() windowDeiconified() windowIconified() windowOpened()

KeyEvent

MouseEvent

MouseEvent TextEvent WindowEvent

Table 7.1: AWT event list

7.2.3

Event Listener Interface

The event listener interface is an interface that contains methods denoting specic events. These methods are called event handling methods. When
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

79

A Button Example

7.2.6

an event occurs, the appropriate event method is invoked. Related event handling methods are grouped into a single interface eg. the MouseListener interface contains the following methods: mousePressed(MouseEvent) mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mouseReleased(MouseEvent) An object that is interested in listening for mouse events will have to implement this interface. Notications are made by invoking specic methods in the MouseListener interface; for example when a mouse button is pressed, the event source will call mouseClicked() method.

7.2.4

Event Source

Event sources are objects that re events. As noted earlier, event sources provides events registration and deregistration methods. A listener calls the appropriate addXXXListener() to register itself as a listener and removeXXXListener() to stop receiving events notication. Multiple listeners can register with a source to receive event notications.

7.2.5

Event Objects

Event objects encapsulates all information pertaining to a specic event. In the case of a mouse event, the MouseEvent object would contain information like the number of mouse clicks and the position of the mouse pointer when the event occur. Event objects are passed from the source to the listener via the event handling methods. All AWTs event objects are subclass of AWTEvent in the java.awt.event package. The getSource() method which all XXXevent inherits returns the event source object. So if a reference fredButton res a ActionEvent, then getSource() will return a reference to fredButton.

7.2.6

A Button Example

The AWT does not have a rollover button. We will use AWT event to implement just such a button. The rollover button will do the following: when the mouse pointer moves over the surface of the button, the buttons background will turn red while the buttons label will be black. When the mouse pointer leaves the button, the buttons color is reversed. The steps are as follows: 1. The RolloverButton is a subclass of Button.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

80

A Button Example

7.2.6

2. The listener must implement the MouseListener. 3. Register the listener with the event source which in this case is the RolloverButton. 4. Perform the necessary color change in the event handler methods. The code is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

import java.lang.*; import java.awt.*; import java.awt.event.*; public class RolloverButton extends Button implements MouseListener { public RolloverButton(String label) { super(label); mouseExited(null); addMouseListener(this); } public void mouseEntered(MouseEvent mEvt) { setBackground(Color.red); setForeground(Color.black); } public void mouseExited(MouseEvent mEvt) { setBackground(Color.black); setForeground(Color.red); } public void mouseClicked(MouseEvent mEvt) { } public void mousePressed(MouseEvent mEvt) { } public void mouseReleased(MouseEvent mEvt) { } } The explanation for the RolloverButton class is as follows: line 3, To capture AWT events, you must import java.awt.event. line 5, We reuse Button class since RolloverButton is essentially a Button. line 6, We must implement MouseListner interface to capture mouse events. line 9, Our specication is that when the mouse pointer leaves the button, the button returns to a black background and red foreground color pair. Here, we fake a mouseExited() event to set the initial button behaviour. line 10, We register with RolloverButton that we are interested in receiving mouse events. Notice that we are adding ourself as a listener viz. we are the source as well as the listener.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

81

Another Button Example

7.2.7

line 12 to 22, These methods are part of the MouseListener interface and must be implemented by RolloverButton. We are only interested in mouseEntered() and mouseExited() method. The others have empty method bodies. When the mouse enter an instance of RolloverButton, mouseEntered() will be invoked. In mouseEntered(), we set the background color to red and the foreground to black. Similarly, when the mouse leaves the button instance, mouseExited() is invoked. Figure 7.4 shows 2 instances of RolloverButton, one with a rollover face.

Figure 7.4: A RolloverButton example

7.2.7

Another Button Example

The following code prints out hello world whenever the RolloverButton is pressed. Note the use of anonymous class to creates a listener in line 9 to 13.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

import java.lang.*; import java.awt.*; import java.awt.event.*; public class ButtonEventEx extends Frame { public ButtonEventEx() { super("Button Event"); RolloverButton button = new RolloverButton("Press Me!"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent aEvt) { System.out.println("hello world!"); } }); setLayout(new FlowLayout()); add(button); pack(); } public static void main(String[] args) {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

82

Creating Customized Events

7.3.1

19 20 21 22

ButtonEventEx ex = new ButtonEventEx(); ex.setVisible(true); } }

7.3

Creating Customized Events

How do we create and re our own events? Short answer: not very dicult. Remember, events in the JDK 1.1 are red by invoking specic methods in the listener objects. The steps for creating events is as follows: Dene the event object class This is the event object that is passed from the source to the listener. The event class must be a subclass of EventObject which is found in package java.util. Call your event class XXXEvent according to the JDK naming conventions. Dene the listener interface This is the interface that all listener must implement. Dene an interface that extends EventListener interface which is in package java.util. The EventListener interface is a marker interface. The event interface is call XXXListener according to the JDK naming conventions. Every method in the listener interface denotes a specic event. Each method should return void and take one parameter, which is the event object. An example of an event handler method is as below: public void someMethod(XXXEvent xEvt); Dene the event source This could be any class that is ring the event. Add event registration and deregistration method. These methods has the following forms: public void addXXXListener(XXXListener l) public void removeXXXListener(XXXListener l) For each event handler method in the listener interface, dene an event ring method. These ring method is responsible for instantiating the event object and calling the appropriate event handler method.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

83

AccountListener Interface

7.3.3

7.3.1

Account Class Again

We will now add additional features to Account class which will allow any instance of Account to re an event whenever we deposit or withdraw from it. The event will contain the amount deposited or withdrawn.

7.3.2

The AccountEvent Object

Information pertaining to a particular transction in Account is encapsulated in the AccountEvent an event object. The event object will have a read only property called amount which gives the amount deposited or withdrawn. The code for AccountEvent is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13

import java.lang.*; import java.util.*; public class AccountEvent extends EventObject { private float amount; public AccountEvent(Object src, float amt) { super(src); amount = amt; } public float getAmount() { return (amount); } } The explanation is as follows: line 2, Remember to import java.util package because the EventObject class is in it. line 6, The constructor takes 2 parameters; src is the event source and amt is the amount deposited or withdrawn. line 7, Calls the superclass constructor and sets the event source. line 10 to 12, Getter for the amount properties. Notice that this is a read only property. This is because event objects are considered immutable so there are no setter methods.

7.3.3

AccountListener Interface

The AccountListener interface contains event handling methods that will be called when a specic event occurs. Java convention dictates that these event handling methods be dened in an interface that is a subclass of

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

84

Event Listener Registration/Deregistration

7.3.4

EventListener. A class that wants to handle any of the events dened in the AccountListener interface should implement that interface. So the AccountListener interface looks like this:
1 2 3 4 5 6 7

import java.lang.*; import java.util.*; public interface AccountListener extends EventListener { public void moneyDeposited(AccountEvent aEvt); public void moneyWithdrawn(AccountEvent aEvt); }

7.3.4

Event Listener Registration/Deregistration

Inorder for listeners to be notied of potential events, the event listeners have to register themseleves the the event source. The event source class, Account in this case, must provide registration and deregistration method. The account event registration and deregistration method has the following signature: public void addAccountListener(AccountListener l) public void removeAccountListener(AccountListener l) The required modication to Account class is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

import java.lang.*; import java.util.*; public abstract class Account { private String name; private float balance; private Vector listeners; . . . private Interest interest; public Account(String n, int no) { . . . listeners = new Vector(); } public void deposit(float amt) { if (amt >= 0) { balance += amt; fireMoneyDeposited(new AccountEvent(this, amt)); } } public void withdrawal(float amt) { if ((amt <= balance) && (amt >= 0)) {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

85

Event Listener Registration/Deregistration

7.3.4

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

balance -= amt; fireMoneyWithdrawn(new AccountEvent(this, amt)); } } public void addAccountListener(AccountListener l) { listeners.addElement(l); } public void removeAccountListener(AccountListener l) { listeners.removeElement(l); } private void fireMoneyDeposited(AccountEvent aEvt) { Vector tmpV; synchronized(listeners) { tmpV = (Vector)listeners.clone(); } for (int i = 0; i < tmpV.size(); i++) { AccountListener l = (AccountListener)tmpV.elementAt(i); l.moneyDeposited(aEvt); } } private void fireMoneyWithdrawn(AccountEvent aEvt) { . . . } . . . } Before we discuss the lengthy code, we must remember that Account class is an event source. As an event source, Account must do the following: 1. Provide event registration method. 2. Provide event deregistration method. 3. Notify listeners when an event has occured. Now lets turn our attention to the code; the following lines are noteworthy: line 6, 11, The Vector class a dynamic array which can be used to store arbitarily objects. We will use an instance of Vector called listeners to keep references of listeners who have registered with us to receive event notication. Refer to your JDK documentation for details on Vector class. It is under java.util package. line 23 to 25, This is the event registration method. When a listener register with us, we keep a reference of the listener interface. All listener are kept in the listeners.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

86

Event Listener Registration/Deregistration

7.3.4

line 26 to 28, This is the event deregistration method. Listeners can choose to stop receiving events by calling this method. We remove the previously registered listener from listeners. line 16, After someone has deposited some money, we need to notify our listeners. To do this we call fireMoneyDeposited() method which does the notication. We also need to pass an event object along with the notication. The event object consist of the source of the event (this) and the amount that is deposited (amt). line 31 to 33, Before we notify our listeners of an event we rst make a copy of the listeners. This is to prevent new listeners being added while we are notifying the listeners. The synchronized keyword is to lock listeners while being copied. We will look at synchronized in chapter 12. line 34 to 36, In these 3 lines we go through every listener that have been registered with us and we call the its fireMoneyDeposited() method thus notifying it of the deposit event. The gure 7.5 illustrates this entire process: A word of caution: event notication is a method invocation; control is passed from the event source to the listener. The listener should not hold the control. It should return the control to the source as soon as possible, otherwise you will holdup event delivery to other listeners.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

87

Event Listener Registration/Deregistration

7.3.4

public void addAccountListener(AccountListener l)

Register listener

Event Source Fire event listeners

AccountEvent

Event Listener

Reference listeners

public class Fred implements AccountListener { public void moneyDeposited(AccountEvent aEvt) { ... } public void moneyWithdrawn(AccountEvent aEvt) { ... } }

Figure 7.5: Notifying listeners

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

88

Chapter 8

Exceptions
Program bugs and program crashes are invevitable unless you plan to continue writing hello world for the rest of your life. Defensive programming assumes all codes to be erraneous and proceeds to inter spread error checks to normal code. Sometimes you can spend more coding time on checking and handling errors than actually writing your program.

8.1

Exceptions

Javas way of defensive programming is via the use of its exceptions mechanism. Exceptions has the following two benets: They allow you to separate error handling code from normal code. You can determine exactly who handles an error. There is a well dened chain of responsibility; exception travel up the method call stack until it is handled by one of the method. Unhandle exception are captured and reported by the JVM. An exception is an event thrown either by the JVM or by your code to indicate that an error has occured. We will begin our exception discussion by learing how to glean useful information for exception stacks. We will then proceed to looking at Java exception mechanism; we will end this chapter by creating our very own exceptions.

8.1.1

An Exceptional Example

Lets look at the following code


1 2 3 4

import java.lang.*; public class ExceptionEx { public String[] msgs = {"bonjour", "chou san", 89

Exception Objects

8.1.2

5 6 7 8 9 10 11 12 13 14 15

"selamat pagi" }; public void doit() { int i = 0; while (i < msgs.length) System.out.println(msgs[++i]); } public static void main(String[] args) { ExceptionEx ex = new ExceptionEx(); ex.doit(); } } which will generate the following exception stack example when executed:

1 2 3 4 5

chou san selamat pagi java.lang.ArrayIndexOutOfBoundsException: 3 at ExceptionEx.doit(ExceptionEx.java:9) at ExceptionEx.main(ExceptionEx.java:13) The stack consists of 2 parts, line 3 indicates the type of exception that has occured and lines 4 and 5 shows the methods on the call stack. The exception type is ArrayIndexOutOfBoundsException which occurs in doit() method at line 9 in the ExceptionEx.java le. Notice how the exception travels from doit() to main() until it reaches the JVM which prints out the exception. Examining line 9 we nd the oending line: System.out.println(msgs[++i]); Apparently the programer should have written i++ instead of ++i which resulted in the exception.

8.1.2

Exception Objects

A exception object is an object that represents the state of the exception. When an exception occurs the JVM captures the environment at that instance. This captured information is the exception object. You can examine the exception object to nd out what is wrong and recover from it. All exception objects are subclass of Exception1 and has the following methods: public void printStackTrace() Prints out the stack trace at the point of the exception. An example of this output is shown in page 90.
1

Actuall all exceptions are subclass of Throwable.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

90

try-catch Block

8.1.3

public String getMessage() Returns the exceptions error message, if any.

8.1.3

try-catch Block

A try-catch block establishes a block of code that is to have its exception and abnormal exits handled. A catch established what exception that the block should catch and take whatever action necessary to deal with the exceptional condition. The syntax of a try-catch block is as follows: try { <exception_block> } catch (<exception_class> e) { <exception_handler> } Lets look at a try-catch block; we will rewrite the example on page 89 to handle the ArrayIndexOutOfBoundsException and recover from it.
1 2 3 4 5 6 7 8 9 10 11 12

. . . public void doit() { int i = 0; while (i < msgs.length) try { System.out.println(msgs[++i]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Oops! Busted index. Reseting"); i = 0; } } . . . The explanation for the exception code snippet is as follows: line 5 to 7, These lines dene the exception block, viz. the block of code that could generate an exception. Our block of code consist of 1 single line vis.the { } between lines 5 and 7. line 7, The catch denes what exception to catch. This is specied as a formal parameter immediately after catch. If such an exception is raised in the exception block, program control is passed to the exception handler. Notice that the catch block looks a lot like a method declaration. Also, note thee. This is the exception object. When we run the ExceptionEx again, we get the following output:

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

91

And finally...

8.1.4

chou san selamat pagi Oops! Busted index. Reseting chou san selamat pagi Oops! Busted index. Reseting chou san selamat pagi When i equals to 3, line 6 of the code snippet above generated an exception; the exception object is caught by catch and the exception handler is executed. We see the Oops! message printed out and i s reset to 0. When the exception handler completes, the program returns to while and the entire process is repeated. You can catch multiple exception within an exception block. This is done by adding multiple catch to the exception block; the previous code snippet is modied to catch 2 exceptions as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

. . . public void doit() { int i = 0; while (i < msgs.length) try { System.out.println(msgs[++i]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Oops! Busted index. Reseting"); i = 0; } catch (NullPointerException e) { System.out.println("Oops! Did not initialize array"); break; } } . . . When an exception is thrown in a multi catch blocks, it is caught by the rst catch block with the appropriate exception object type. Only one catch block will be executed. Please note that the previous code snippet has a bug in the array access. You should always correct a bug rather than using a try-catch to circumvent it.

8.1.4

And finally...

The finally keyword is generally used to clean up after a try or a catch. It denes a block of code that is always executed regardless of whether an
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

92

And finally...

8.1.4

exception is thrown. This is also true when you execute a return, continue or break. Lets take a look at an example :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

. . . public void readFile(String file) { FileInputStream fis = null; int ch; try { fis = new FileInputStream(file); while ((ch = is.read()) != -1) System.out.println(ch); } catch (IOException e) { System.out.println(e.getMessage()); } finally { is.close(); } } . . . Line 7 reads a character for a FileInputStream. The read() method may throw a IOException if it encountered an error during the read. But regardless of whether an IOExceptiion exception is thrown or not, the above code snippet will gurantee to close the le because we have the close() (line 12 ) in a finally block. You can also use a finally without a catch clause. We will cover I/O in the next chapter. There are 4 possible scenerios to a try-catch-finally block. They are Forced exit is when you forced program control to be passed out of a try block. This is done using a return, continue or break. Normal completion is when the code in a try-catch-finally block completes normally. Caught exception thrown refers to an exception thrown that is specied in a catch. Uncaught exception thrown is when an exception is thrown and this exception is not specied in a catch. This category of exception is almost always a RuntimeException. If we have the following code skeleton try { // Point 1 } catch ( . . .) { // Point 2 } finally {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

93

Generating Exceptions

8.1.6

// Point 3 } // Point 4 the execution path of the above mentioned scenerios are as follows: Forced Point 1, Point 3, Point 4 Normal Point 1, Point 3, Point 4 Caught Point 1, Point 2, Point 3, Point 4 Uncaught Point 1, Point 3, Point 4

8.1.5

Declaring Exceptions

Sometimes you may not choose to handle an exception but pass it on the calling method. In this case you must declare the out going exception as part of your method declaration. The readFile() method on page 93 is modied so that the caller instead of readFile() is responsible for handling IOException.
1 2 3 4 5 6 7 8 9 10 11 12 13 14

. . . public void readFile(String file) throws IOException { FileInputStream fis = null; try { int ch; fis = new FileInputStream(file); while ((ch = is.read()) != -1) System.out.println(ch); } finally { is.close(); } } . . . Note line 3, you use throws to declare that there is an exception to be caught. It is the responsibility of the caller to handle it within a try-catch block.

8.1.6

Generating Exceptions

You can generate your own exception with a throw (note the missing s) statement. A throw does 2 things: 1. Creates an exception object. 2. Causes normal program execution to stop. This above 2 points is best illustrated with the following example:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

94

Creating Your Own Exception

8.2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

. . . public void readFile(String file) throws IOException, FileNotFoundException { FileInputStream fis = null; File fd = new File(file); if (!fd.exists()) throw new FileNotFoundException(file); try { int ch; fis = new FileInputStream(file); while ((ch = is.read()) != -1) System.out.println(ch); } finally { is.close(); } } . . . The explanation for the above code is as follows: we check if the le exists; if it exists we proceed to open and read it. But if it does not exists, we throw a FileNotFoundException (line 7 ). Notice that you have to instantiate the exception object. After the throw statement, the program exist the readFile() method. The remainding statements in the method is not executed.

8.2

Creating Your Own Exception

Besides using the JDK supplied exception classes, you can also create your own exception objects. This requires you to subclass the Exception class. We will add exception to Account class. We will modify OverdrawnException to withdrawal() method. The OverdrawnException will capture all information pertaining to an overdrawn transaction. These information includes the following: Account name. Account number. Account balance at the time of the withdrawal. Overdrawn amount. The date of the transaction. The following code implements OverdrawnException.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

95

Creating Your Own Exception

8.2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

import java.lang.*; import java.util.*; public class OverdrawnException extends Exception { private final String name; private final int acctNo; private final float balance; private final float withdrawAmount; private final Date date; public OverdrawnException(Account acct, float amt) { super("Overdrawn"); name = acct.getName(); acctNo = acct.getAccountNumber(); balance = acct.getBalance(); withdrawnAmount = amt; date = new Date(); } public String getName() { return (name); } . . . public float getWithdrawAmount() { return (withdrawAmount); } } The explanation for the above code is as follows: line 10, The constructor takes an Account object and initializes the account name (name), account number (acctNo) and current balance (balance). The amt holds the amount the the user is attempting to withdraw. Note that these members are marked final. This means that the value of these members cannot be changed. What we are really saying here is that OverdrawnException object is immtable; viz. once create, the content of the object cannot be changed. line 11, Since the superclass has a constructor with a String parameter. You can get this message using the getMessage() method. line 12 to 16, are read only properties to name, account number, balance, amount attempted to withdraw and the transcation date. To use OverdrawnException, we modify withdrawal() of Account class as follows:

1 2

public class Account { . . .


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

96

Creating Your Own Exception

8.2

3 4 5 6 7 8 9 10 11 12

public void withdrawal(float amt) throws OverdrawnException, ErroneousAmountException { if (amt > balance) throw new OverdrawnException(this, amt); if ((amt < 0) throw new ErroneousAmountException(this, amt); balance -= amt; } . . . } line 4, Declares OverdrawnException and ErroneousAmountException will be thrown from withdrawal(). Assme ErroneousAmountException is dened. line 6, If the amount to be withdrawn is greater than what is in the account, we throw the OverdrawnException, passing to it all the necessary parameters. line 7, 8, We check if we are withdrawing negative amount. If so we throw a dierent exception. line 9, When we have passed all the necessary checks, we do the withdraw. The following code snippet show how we use the modied withdrawal().

1 2 3 4 5 6 7 8 9

. . . try { account.withdraw(amt); } catch (OverdrawnException e) { System.out.println("Overdrawn"); System.out.println("You have: " + e.getBalance()); System.out.println("You attempt to withdraw: " + e.getWithdrawAmount()); }

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

97

Chapter 9

Streams
Most programs use data in one form or another. These data can be in a local le, a socket on the network, a database, variables or from another program. The JDK provides classes for reading and writing streams of data. These classes are part of the java.io package. We will also look at the concept of stream chaining.

9.1

The File Class

One of the commonly used classes in java.io package is the File class. This class provides methods to manipulate the le system which includes deleting les, listing directories, returning le statistics renaming les and others. Recall that in chapter 8 in page 94 we used File to check for le existance; we will look at a code snippet which displays the properties of a given le if it exists. The code is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

import java.io.*; . . . public void fileInformation(String fileName) { File fn = new File(fileName); if (!fn.exists()) { System.out.println(fileName + " does not exists."); return; } System.out.println(fileName + " is a " + fn.isDirectory()? "directory." :"file."); if (!fn.canRead()) { System.out.println(fileName + " is not readable."); return; } System.out.println(fileName + " is " + fn.length() + " bytes long."); 98

The File Class

9.1

17 18 19 20

System.out.println(fileName + " is last modified on " + fn.lastModified() + "."); } . . . The explanation for the above code is as follows: line 1, Do not forget to import the java.io package. line 5, If the le does not exist we proceed no further. line 10, Prints out an appropriate message if fileName is a directory. line 11, Check if we can read the le. line 15 to 18, If we can read the le, we print out the size and the last modied date. The File class has the following methods: public String getName() Returns the le name. public String getParent() Returns the parent part of the le or null if the le name has no parent part. public void delete() Deletes a le. public boolean exists() Test if the le exists. public long length() Returns the size of the le. public long lastModified() Returns the last modied date of the le. public boolean canRead() Test if the le is readable. public boolean canWrite() Test if the le is writable. public boolean isFile() Test if the le is a normal le. public boolean isDirectory() Test if the le is a directory.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

99

InputStream

9.2.1

public String[] list() Returns a list of les in a directory, if the current File object is a directory. public void mkdir() Creates a directory.

9.2

Character and Byte Streams

Streams are data sources that we read and write to serially. Whether you are reading a le or a socket connection, the idea of a stream is the same. Prior to JDK 1.1, the java.io package only supported 8-bit byte streams. The concept of 16-bit character streams was introduced in JDK 1.1. To read byte streams we use InputStream and OutputStream classes and their subclasses; to read character streams, we use Reader and Writer classes and their subclasses. The InputStream and Reader classes are input streams. As the name implies, you read from these streams but not write to it. Similarly, OutputStream and Writer classes are output streams; you cannot read from these latter streams. Both the character and byte stream classes are quite similar in their use so we will just concentrate on the byte stream and leave character stream classes as an exercise for the reader.

9.2.1

InputStream

The InputStream class is an abstract and is a superclass of all classes representing input byte streams. Figure 9.1 shows all the subclasses of InputStream. InputStream has the following methods; this list is not exhaustive. Also all the above methods throws IOException. public int read() Reads the next byte of data from this input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown. public int read(byte[] b) Reads up to b.length bytes of data from this input stream into an array of bytes. public long skip(long n) Skips over and discards n bytes of data from this input stream.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

100

OutputStream

9.2.2

InputStream SequencedInputStream PipedInputStream FileInputStream StringBufferInputStream ByteArrayInputStream FilterInputStream DataInputStream BufferedInputStream PushbackInputStream
Figure 9.1: InputStream class hierarchy public int available() Returns the number of bytes that can be read from this input stream without blocking. public void close() Closes this input stream and releases any system resources associated with the stream. Recall that InputStream is an abstract class. We cannot use InputStream to do any reading. We must use a concrete implementation of it such as a FileInputStream. Table 9.1 summarize all input stream classes available in java.io and also java.util.zip1.

9.2.2

OutputStream

The OutputStream class is an abstract and is a superclass of all classes representing output byte streams. Figure 9.2 shows all the subclasses of OutputStream. OutputStream has the following methods; these methods throw IOException. public void write(int b) Writes a byte to the output stream.
1

Yes, ZIP, as in PKZIP

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

101

OutputStream

9.2.3

Class Name BufferedInputStream

ByteArrayInputStream CheckedInputStream

DataInputStream FileInputStream FilterInputStream GZIPInputStream InflaterInputStream InputStream ObjectInputStream PipedInputStream PushbackInputStream SequenceInputStream ZipInputStream

Description Reads a buer of bytes from an InputStream, and then returns bytes from the buer, making small reads more ecient. Reads bytes sequentially from an array. Coputes a checksum of the bytes it reads from an InputStream. This class is found in java.util.zip. Reads binary representations of Java primitive types from an InputStream. Reads bytes sequentially from a le. The superclass of byte input stream lter classes. This class uncompresses GZIP compressed bytes it reads from an InputStream. This is the superclass of GZIPInputStream and ZipInputStream. The superclass of all byte input stream. Reads binary representations of Java objects and primitive values from a byte stream. Reads bytes written to the PipedOutputStream to which it is connected. Adds a xed-size pushback buer to an input stream, so that bytes can be unread. Reads bytes sequentially from two or more input streams, as if they were a single stream. This class uncompresses entries in a ZIP le. This class is found in java.util.zip.

Table 9.1: Input stream classes public void write(byte[] b) Writes b.length bytes from the specied byte array to this output stream. public void flush() Flushes this output stream and forces any buered output bytes to be written out. public void close() Closes the output stream. It is advisable call flush() before closing. Table 9.2 summerizes all the OutputStream classes in java.io and java.util.zip.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

102

A File Copy Example

9.2.3

OutputStream PipedOutputStream FileOutputStream StringBufferOutputStream ByteArrayOutputStream FilterOutputStream DataOutputStream BufferedOutputStream PushbackOutputStream

Figure 9.2: InputStream class hierarchy

9.2.3

A File Copy Example

We will now write a copy program in Java using FileInputStream and FileOutupStream. The explanation will follow the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

import java.lang.*; import java.io.*; public class FileCopy { public static void main(String[] args) throws IOException { if (args.length != 2) { System.out.println("java FileCopy <src> <dst>"); System.exit(-1); } if (!(new File(args[0])).exists()) { System.out.println(args[0] + " does not exists."); System.exit(-1); } if ((new File(args[1])).exists()) { System.out.println(args[1] + " will be overwritten."); System.exit(-1); } int c; FileInputStream srcFile = new FileInputStream(args[0]); FileOutputStream dstFile = new FileOutputStream(args[1]);
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

103

A File Copy Example

9.2.3

Class Name BufferedOutputStream ByteArrayOutputStream CheckedOutputStream

DataOutputStream DeflaterOutputStream FileOutputStream FilterOuputStream GZIPOutputStream ObjectOutputStream OutputStream PipedOutputStream ZipOutputStream

Description Buers byte output for eciency; write to an OutputStream only when the buer lls up. Writes bytes sequentially to an array. Computes a checksup of the bytes written to an OutputStream. This class is found in java.util.zip. Writes binary representations of Java primitive types to an OutputStream. the superclass of GZIPOutputStream and ZipOutputStream. Writes bytes sequentially to a le. The superclass of all byte output stream lters. Compress and write bytes out using the GZIP algorithm. Writes binary representation of java objects and primitive values to an OutputStream. The superclass of all byte output streams. Writes bytes to the PipedInputStream to which it is connected. Compress and write bytes to a ZIP le. This class is found in java.util.zip.

Table 9.2: Output stream classes


21 22 23 24 25 26 27 28 29 30

try { while ((c = srcFile.read()) != -1) dstFile.write(c); dstFile.flush(); } finally { srcFile.close(); dstFile.close(); } } } line 2, Do not forget to import java.io package. line 5, The main() thorws IOException. line 6 to 9, Checks if we have enough parameter. We need a source le to copy from and a destination le to copy to.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

104

Stream Chaining

9.3

line 10 to 13, These 4 lines of code checks if the source le exists. line 14 to 17, These lines warns that an existing le will be overwritten. Remember we are copying to this le. line 19, Use FileInputStream to open source le for reading. line 20, Use FileOutputStream to open destination le for writing. line 22, Keep reading a byte from srcFile until we hit an end of le which is denoted by a -1. line 23, After reading the byte, we write to dstFile. line 24, When we have completed copying, force any remainding bytes in the output buer to the disk. line 25, The finally clause here ensures that all opened les will be closed. If we some how encountered an IOException in line 22 or 23, the les are rst closed before throwing an exception (line 5 ).

9.3

Stream Chaining

The java.io packages distinguishes between 2 type of streams: Node stream This type of streams read or write from a specic place such as a disk or from the network. FileInputStream is a node stream. Filter stream These are layered onto of node streams and provide additional functionalities that are not found in node streams. The idea of stacking lter stream classes on node streeam is one of the most powerful feature in the Java I/O package; this is known as stream chaining. Stream chaining is a way of connecting several stream classes to work together to provide new functionalities. Its plug and play, mix and match. Assume we have GZIPOutputStream2 and DataOutputStream class; we can chain these classes so that whatever value that we write will be compressed before saving to a le. Figure 9.3 illustrates this chain. This is how the chain works; when values are written to DataOutputStream, they are converted to a portable binary format before forwarding to GZIPOutputStream. GZIPOutputStream then compresses the data before passing it to FileOutputStream for saving. The source code for creating the chain shown in gure 9.3 is as below:
2

GZIP is GNU Zip

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

105

Stream Chaining

9.3

Data

DataOutputStream Convert to portable binary format

GZIPOutputStream Compress data

FileOutputStream

To disk

Figure 9.3: Stream chaining example


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

import java.lang.*; import java.io.*; import java.util.zip.*; public class WriteChain { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("output.gz"); GZIPOutputStream gzos = new GZIPOutputStream(fos); DataOutputStream dos = new DataOutputStream(gzos); try { dos.writeUTF(args[0]); dos.flush(); } finally { dos.close(); } } } The explanation for the above code snippet is as follows: line 7, We create a FileOutputStream which is our node stream. The le name that we will be writing our data to is output.gz. line 8, Here, we instantiate the rst of our lter stream, the GZIPOutputStream which does all the compression. Notice that this lter stream is layered on fos, which is FileOutputStream.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

106

Performing I/O with Account

9.3.2

line 9, We now add another lter stream on to the chain. Notice that when instantiating DataOutputStream, we use gzos. line 11, We now can write a String to our chain. You always write to the stream class at the top of the chain. In this case it is dos. line 12, Do not forget to force the data to the hard disk. line 14, When closing a chained stream, you only need to close the top most stream class because the dos.close() method call will automatically trigger other close() through all the chained classes.

9.3.1

Decorator Design

Stream chaining is a well known design in object oriented software. Its formal name is known as decorator or wrapper. The decorator keeps our object simple; you do not have to write a complex object with all possible features. Instead you write small, simple and focused objects that can be combined with other objects to provide new functionalities. The advantages of the decorator design is as follows: Add function to objects dynamically and transparently with out affecting other objects. Functions can also be revoked dynamically and transparently.

9.3.2

Performing I/O with Account

The best way to understand how a lter stream works is to actually write one. We will write a AccountInputStream and AccountOutputStream that writes and reads Account objects. We will rst detail the steps involved followed by an implementation of the above mentioned 2 classes. The steps for creating a lter stream is a lter streams is as follows: 1. Extend the FilterInputStream or FilterOutputStream. 2. Write a constructor that accepts an InputStream or an OutputStream respectively. 3. Override read() or write() if necessary. 4. Provide additional read and/or write methods if necessary. Lets look at AccountOutputStream rst. The lter stream has the following new methods: public void writeAccount(Account acct) Writes acct to a stream.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

107

Performing I/O with Account

9.3.2

public void writeAccount(String name, int acctNo, float balance , boolean overdraft) Writes name, acctNo, balance and overdraft as an Account object to a stream. The following code listing implements AccountOutputStream.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

import java.lang.*; import java.io.*; public class AccountOutputStream extends FilterOutputStream { private static final byte END_OF_FIELD = 0; public AccountOutputStream(OutputStream os) { super(os); } public void writeAccount(Account acct) throws IOException { writeAccount(acct.getName() , acct.getAccountNumber() , acct.getBalance() , acct.isOverdraft()); } public void writeAccount(String name, int acctNo , float balance, boolean overdraft) throws IOException { write(name.getBytes()); write(END_OF_FIELD); write(String.valueOf(acctNo).getBytes()); write(END_OF_FIELD); write(String.valueOf(balance).getBytes()); write(END_OF_FIELD); write(String.valueOf(overdraft).getBytes()); write(END_OF_FIELD); } } line 4, Extends the FilterOutputStream. line 5, The END_OF_FIELD constant is a value to separate the Account members when we write the members to a OutputStream. This is so that when we read the values back, we know where one member ends and another begins. line 6, Our constructor needs to accept a OutputStream. This is in accordance with the lter stream requirements.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

108

Performing I/O with Account

9.3.2

line 9, The rst of our writeAccount() methods. This write method takes an Account object. line 11 to 14, Calls the other writeAccount() which does the writing. line 16, The method is the workhorse of the class. Data for each member is rst converted to String using valueOf(). Once in String, the String is written as bytes. Remember a character is 16-bit in Java. line 19 to 26, After writing a member, we write the END_OF_FIELD. A dump of an AccountOutputStream is shown in gure 9.4. You can see that the account name is Fred Flintstone, the account number is 12345. There is $100 in the account with no overdraft capability. The \0 is the END_OF_FIELD. 000 020 040 F 1 e r 2 \0 e 3 d 4 5 F \0 l 1 i 0 n 0 t . s 0 t \0 o f n a e l \0 s

Figure 9.4: Contents of an AccountInputStream le The classAccountInputStream reads a le written by AccountOutputStream. readAccount() reads a record which consists of a name, an account number, a balance and an overdraft status; it then instantiate an Account object and returns it to the caller. The code that implements AccountInputStream is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

import java.lang.*; import java.io.*; public class AccountInputStream extends FilterInputStream { private static final byte END_OF_FIELD = 0; private byte[] buffer; private int dataPointer; public AccountInputStream(InputStream is) { super(is); buffer = new byte[256]; } public Account readAccount() throws IOException { String name = null; int acctNo = 0; float balance = 0F;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

109

Performing I/O with Account

9.3.2

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

boolean overdraft = false; try { name = readField(); acctNo = Integer.parseInt(readField()); balance = Float.valueOf(readField()).floatValue(); overdraft = Boolean.valueOf(readField()).booleanValue(); } catch (NumberFormatException e) { throw new IOException("Data file is corrupted."); } Account acct = new Account(name, acctNo); acct.setOverdraft(overdraft); acct.withdrawal(100F); acct.deposit(balance); return (acct); } private String readField() throws IOException { dataPointer = 0; while ((buffer[dataPointer++] = (byte)read()) != END_OF_FIELD); return (new String(buffer, 0, --dataPointer)); } } line 6, 10, buffer is an internal buer which we use to hold temporary data. The size of the buer is set to 256 bytes. line 32, The readField() method is responsible for reading a eld. Remember that a eld is denoted by a END_OF_FIELD byte. When it has a eld, readField() converts the eld to a String and returns it to the caller. line 35, We read the InputStream, one byte at a time until we hit a END_OF_FIELD byte. The read byte is stored in buffer. line 36, The eld held in buffer is converted to a String and returned to the caller. line 12, The readAccount() method returns as Account object that is constructed from the information in a InputStream. line 19 to 22, All data are stored to an OutputStream as a String. So when we read back the data with the exception of name, we need to convert all other elds back to their value. We use parseInt(), floatValue() and booleanValue() to do this for us. These method will throw a

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

110

Performing I/O with Account

9.3.2

NumberFormatException if we fail. In this case we rethrow the exception as IOException, reporting that we have encountered corrupted data. line 26, Once we have all the necessary information, we instantiate an Account object. line 28, A new Account always has $100 in it (see page 27) , so we must remove it rst before depositing the actual amount. Now that we have looked at the implementation, lets see how we write a compress Account object to a le. The code snippet is shown below:
1 2 3 4 5 6

Account acct; . . . FileOutputStream fos = new FileOutputStream("account.gz"); GZIPOutputStream gzos = new GZIPOutputStream(fos); AccountOutputStream aos = new AccountOutputStream(gzos); aos.writeAccount(acct); Reading the save Account object back is just as simple:

1 2 3 4

FileInputStream fis = new FileInputStream("account.gz"); GZIPInputStream gzis = new GZIPInputStream(fis); AccountInputStream ais = new AccountInputStream(gzis); Account acct = ais.readAccount(); Stream chaining is a very simple but powerful concept. It allows us to build new functionality into our streams modularly. The AccountInputStream and AccountOutputStream classes shows just how simple it is to build on what is provided in java.io package.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

111

Chapter 10

Object Serialization
Streams I/O has traditionally been used to save and retrieve record type data. For simple objects, stream I/O may well be adequate but for complex objects, writing a lter stream may not be as straight forward. Consider the following class and you will begin to appreciate the immensity of problem.
1 2 3 4 5 6 7 8

public class ComplexObject extends SuperComplexObject { public String name; private int index; public SomeObject someObject; // methods and constructors . . . } Consider the following points from ComplexObject class above: line 2, You have to save ComplexObjectss super class SuperComplexObject; in fact you will have to save all super classes. line 4, If there are no getter methods for index, how are you going to save this? line 5, You will also have to save someObject, including its super class if any.

10.1

Serialization

Serialization is the process of attening an object which can then be saved to some permanent storage or passed to another object via OutputStream. This is sometimes also known as persistence. When an object is serialized, only members of the object is serialized; static and final members, methods and constructors are not serialized. 112

ObjectInputStream and ObjectOutputStream

10.1.2

Besides saving the objects members, the serialization process also stores the objects graph and version. The graph is the objects ancestory viz. its superclass. Serialization of an object begins with the highest serializable class in the objects class hierarchy. The process then walks down the hierarchy to serialize data from each subclass in turn. We will look at versioning in section 10.4

10.1.1

The Serializable Interface

To allow an object to be serializable, the class must have the following: Implement the Serializable interface which is in java.io package. Provide a default constructor or a constructor with no parameters. The Serializable interface has no methods and only serve as a marker to indicate that it can be serialize. You do not have to implement Serializable on every class, just once along the class hierarchy; preferabally in the super class. All its subclasses will be serializable as well. If we have a 3 class hierarchy, with ClassA at the top, followed by ClassB and ClassC at the bottom; if ClassA implements Serializable, all of its subclass are serializable. For Account class to be serializable, we only have to implement the serializable interface as below. The Account denition is taken from page 46
1 2 3 4 5 6 7 8 9

public abstract class Account implements Serializable { private String name; private int acctNo; private float balance; private boolean overdraft; private Interest interest; public Account() { } . . . Since Account is the base class for all other account classes (eg. SavingAccount and FixedDepositAccount), these subclass will be serializable as well.

10.1.2

ObjectInputStream and ObjectOutputStream

The ObjectInputStream and ObjectOutputStream are used to serialize and deserialize objects respectively. To serialize and deserialize and write objects, use readObject() and writeObject() from ObjectInputStream and ObjectOutputStream respectively. The following code snippet creates and writes to account.ser le a SavingAccount object.
1 2

. . . SavingAccount account = new SavingAccount(


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

113

Specifying What Gets Serialized

10.2.1

3 4 5 6 7 8 9

"Fred", 12345); FileOutputStream fos = new FileOutputStream( "account.ser"); ObjectOutputStream oos = new ObjectOutputStream(fileOut); oos.writeObject(account); oos.flush(); . . . When account is serialized, name, acctNo, balance, overdraft and interest is written to account.ser. But since interest references to a class (which implements the Interest interface), we need to serialize that as well. So that class must implement Serializable as well. In other words, all non primary type member must be serializable if its containing class is to be serialized. Otherwise you will get a NotSerializableException. Notice that like lter streams, ObjectOutputStream is layered onto a node stream. To deserialize account.ser see the following code snippet:

1 2 3 4 5

. . . FileInputStream fis = new FileInputStream("account.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Account account = (Account)ois.readObject(); . . . When you deserialize an object, you need to cast the object (line 4 ) back to its original class.

10.2
10.2.1

Customizing the Serialization Process


Specifying What Gets Serialized

The default behaviour of writeObjec() is to write all the objects members to an output stream. Sometimes we may not want certain members to be serialized; members that hold sensitive information, transitory data or data that is not intended to be serialized. To denote non serializable elds, we mark these members with the transient keyword. Lets take a at Account again. One member that is not a good candidate for serialization is interest for the following reason: It is transitory in that we can change interest rate calculation. It is much better to re set interest rate calculation after we have deserialized an Account object. We cannot be sure that the class that implemented Interest interface also implemented Serialization. To mark interest non serializable, we add the transient keyword like so:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

114

readObject and writeObject Method

10.2.2

1 2 3 4 5 6 7 8 9

public abstract class Account implements Serializable { private String name; private int acctNo; private float balance; private boolean overdraft; private transient Interest interest; public Account() { } . . .

10.2.2

readObject and writeObject Method

Serialization oers you more control over the read and writing process than mere transient. A serializable class can implement the following two methods to take over the entire serialization and deserialization process. private void writeObject(ObjectOutputStream out) throws IOException private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException; The writeObject() method is responsible for writing the members of the object to the stream and readObject() is responsible for restoring it. Our Account will do the following: any attempt to deserialize an Account object that is 60 days old will result in an exception. The easiest way to write writeObject() and readObject() is to begin each method with calling defaultWriteObject() and defaultReadObject() respectively. defaultWriteObject() method writes non static and non transient members of the current object to an OutputStream which is exactly the behaviour of serialization. The idea is that you want to use the default serialization mechanism to serialize all standard members before you save any additional data. Similarly defaultReadObject() reads the non static and non transient members of the current object from an InputStream. The code to perform expiration checks on serialized Account object is as follows:
1 2 3 4 5 6 7 8

public abstract class Account implements Serializable { private String name; private int acctNo; private float balance; private boolean overdraft; private transient Interest interest; public Account() { }
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

115

Validating a Deserialized Object

10.3

9 10 11 12 13 14 15 16 17 18 19 20 21 22

. . . private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeLong(((((new Date()).getTime()/1000)/60)/60)/24); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); long current = ((((new Date()).getTime()/1000)/60)/60)/24; if ((current - in.readLong()) > 60) throw new IOException("Object expired!"); } . . . The explanation is as follows: line 10, During serialization, control is passed to writeObject(). It is up to writeObject() to determine what to persist. line 12, Use defaultWriteObject() to write the usual members into the stream. line 13, We now write the time (in days since January 1 1970) on the stream as well. This is additional information not part of Account. line 15, readObject() is called when an Account object is deserialized. line 17, defaultReadObject() populates the object with data from the InputStream. line 18, We now get the current time (again expressed in days since January 1 1970). line 19, 20, If the timestime from the stream (in.readLong()) is more than 60 days old, we throw an IOException.

10.3

Validating a Deserialized Object

In the previous section, we informally did our validation in the readObject() method. As it turns out, JDK provides a more formal method of performing validation. When a class implements the ObjectInputValidation interface, JVM will call validateObject() (from ObjectInputValidation) after the entire object graph have been installed. ObjectInputValidation has only one method with the following signature:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

116

Validating a Deserialized Object

10.3

public voiid validateObject() throws InvalidObjectException; To use ObjectInputValidation you must perform the following steps: 1. Implement ObjectInputValidation in you class; write your validation code in validateObject(). 2. Register your validation interface in readObject() method using registerValidation(). Lets rewrite the Account validation using this framework. Heres how the new code looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

public abstract class Account implements Serializable, ObjectInputValidation { private String name; . . . private boolean overdraft; private transient Interest interest; private transient long serTime; public Account() { } . . . private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeLong(((((new Date()).getTime()/1000)/60)/60)/24); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); serTime = in.readLong(); in.registerValidation(this, 0); } public void validateObject() throws InvalidObjectException { long current = ((((new Date()).getTime()/1000)/60)/60)/24; if ((current - serTime) > 60) throw new InvalidObjectException("Object expired!"); } . . . The explanation for the above code is as follows: line 2, You have to implement the ObjectInputValidation interface. line 7, serTime is a temporary variable for holding the serialized time. See line 18.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

117

Versioning

10.4

line 16, You can only validate objects as they are deserialized, so you have to register your validation routine in readObject(). line 17, As usual, read in all the standard members that we have serialized. line 18, Read in the time stamp and store it in serTime. line 19, Register our validateObject() for registration. line 21, This is our validation method. This will be called after the entire object graph has been restored. line 24, We do the check again, as in the previous incarnation of this code. line 25, If the object has been serialized for more than 60 day, we throw a InvalidObjectException.

10.4

Versioning

Versioning allows a serialized object to identify its class during the deserialization process. Consider the following scenerio: you have an object of ClassA which you have serialized. You subsequently modied ClassA to include additional members. So now when you deserialize the old ClassA object, you cannot deserialize it back to ClassA because this class is no longer the original class. In JDK 1.1, before a class is deserialized, its version number is matched against that of its class. If they match then the JVM will proceed with the serialization; otherwise you will get an InvalidClassException. As mention previously, the version is written to an OutputStream along with the objects state and the object graph during persistance. The version number for a class can be provided with one of the following 2 ways: 1. Let the JVM assign one for you randomly. 2. Dene one yourself. To provide a version number for a particular class, you add a static member call serialVersionUID in you class like so:
1 2 3 4 5 6

public abstract class Account implements Serializable { public static final long serialVersionUID = 1234567890L; private String name; private int acctNo; . . .

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

118

Versioning

10.4

If you did not provide a serialVersionUID, JDK will compute1 a version number using the signature of a stream of bytes that reect the class denition. The danger here is that the serialVersionUID will change whenever you modify the class. This will not happen if you explicitly provide a serialVersionUID. You can examine a classs serialVersionUID with serialver in the bin directory of the JDK. To view a class version number, run the following command serialver -show and type the class name in the Full Class Name: window. An example is shown in gure 10.1.

Figure 10.1: Graphical interface of serialver

The algorithm used is the NISTs Secure Hash Algorithm (SHA-1)

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

119

Chapter 11

Networking
The Java API includes a set of classes that facilitates writing TCP/IP based network programs. These classes can be found in java.net package. The network classes in the said package allows a programmer to write the following: TCP and UDP oriented programs. Multicast programs. Access URLs as stream so that you can access a web page as if it was a le local to your system. Extend URLs protocol and content. In this chapter, we will examine a client-server program that uses the TCP protocol. We will assume that the reader is familar with TCP/IP concepts. We will nonetheless provide a quick review of of IP address and port numbers.

11.1

Addresses and Ports

Every server or host that participate in a TCP/IP network requires an IP address. This is a 32 bit unique number. An IP address comprises of 2 parts: Network number The network number (netid) denes a network. All servers with the same netid belongs to the same network. Host number Servers in the same network must have unique host number (hostid). IP address are written in dotted form as a.b.c.d; each digit is an 8 bit value. An example of an address is 192.10.1.1. There are 4 classes of IP address. They are shown in Figure 11.1. 120

Client-Server Model

11.2

Class A 0 net id host id


1 - 127

Class B 1 0 net id host id


128 - 191 . 0 - 255

Class C 1 1 0 net id host id


192 - 233 . 0 - 255 . 0 - 255

Class D 1 1 1 multicast address


224 - 239 . 0 - 255 . 0 - 255 . 0 - 255

Figure 11.1: IP address format Class A networks are for networks with lots of host while class C networks cater for smaller networks. A host or server connected to a TCP/IP network can be more than one active process at any given time. Data for a particular process in the host must be able to identify the intended process uniquely. To distinguish process in a host, TCP and UDP uses 16 bit integer values known as port numbers. Port numbers are bound to one and only one process at any one time and can be reused if the process has terminated. In summary, a port number and an IP address uniquely identies a process in a host. This port number, IP address pair is know as an end point. Communication between end points can occur over either TCP or UDP.

11.2

Client-Server Model

TCP/IP applications typically consists of 2 parts: a server which provides some service like le, print or authentication and a client which use this service. To create a server side end point, the server must specify the protocol it wishes to use and a port number. This server process is know as binding and the server is said to be bound to the port number. If the bind is successful, the server will enter its second phase. This is known as listening. In this phase, the server will listen on the bound port for incoming client connection.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

121

Client-Server Model

11.2

A client opens a connection to a server by specifying the IP address of the host in which the server resides and the port number which the server is bound to. The client must also specify the same network protocol that the server uses1 . These above steps can be summarized as follows: 1. The server binds itself to a port. It then listens for client connection. 2. The client establishes a connection to the server residing on host (IP address) listening on port (port number). 3. The client and server can now communicate once the connection has established. We will now look an an overview of creating a client and server with the JDK. We use the ServerSocket class to write a server. A ServerSocket is instantiated by providing a port number in its constructor like so: ServerSocket server = new ServerSocket(12345); The ServerSocket has an accept() method which is used to listen for incoming client connection. When a client initiates a connection, accept() will return a Socket object. This Socket is the servers connection with the client. How does the client initiates a connection to a server? The client requires 2 pieces of information: 1. A host name or an IP address of the host in dotted notation. 2. The port number which the server is listening on. With this 2 pieces of information, the client can instatiate a Socket object which will connect to the given IP address or host name on the given port. The below example connects to a server listening on port 12345 on host batcave. Socket toServer = new Server("batcave", 12345); Once the connection is established, both client and server calls getInputStream() and getOutputStream. These methods returns an input and output stream. These streams on the server and client are crossed in that data written on the clients OutputStream will appear on the servers InputStream and vice versa. This entire process is illustrated in gure 11.2. We are now ready to write an account client-server program which serves Account objects to its client. The client-server program consists of the following 3 classes:
1

All this sounds rather complicated but it is very easy to do with Java.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

122

Dening the Protocol

11.2.1

Server
new ServerSocket(12345) accept() Socket() getInputStream() getOutputStream()

Client

new Socket(batcave,12345)

getOutputStream() getInputStream()

Figure 11.2: Interaction between ServerSocket and Socket AccountServer The server program that servers Account objects. AccountServerAPI This is a set of API that accesses the AccountServer. Any client that wishes to communicate with AccountServer will use this class. AccountProtocol A class that denes the protocol between the server and a client. The protocol in question here is not the network protocol but application protocol viz. the protocol used between the AccountServer and AccountClient application.

11.2.1

Dening the Protocol

The AccountProtocol is a holder class that contains information passed between server and a client. The following commands are dened for the protocol: GET Retrives an Account object from the server. PUT Request the server to save a particular Account. OKAY An acknowledgement that the last operation was successful. ERROR Denotes an error has occured. AccountProtocol contains the following method:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

123

Dening the Protocol

11.2.1

public int getCommand() Returns the command of the protocol. public int getAccountNumber() Returns the contained account number. public Account getAccount() Returns the Account object contained within the AccountProtocol. The listing for AccountProtocol is as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

public class AccountProtocol implements Serializable { public static final int ERROR = -1; public static final int GET = 0; public static final int PUT = 1; public static final int OKAY = 2; private final int cmd; private final int accountId; private final Account account; public AccountProtocol(int c, int id) { cmd = c; account = null; accountId = id; } public AccountProtocol(int c, Account acct) { cmd = c; accountId = acct.getAccountNumber(); account = acct; } public int getCommand() { return (cmd); } public int getAccountNumber() { return (accountId); } public Account getAccount() { return (account); } } Lets examine the code: line 2, The Serializable interface ensure that AccountProtocol is writable across the network. Remember, we have to pass one of these objects from the client to server and vice versa.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

124

Writing a Server

11.2.2

line 3 to 6, The 2 dened protocols. line 7 to 9, The AccountProtocol object is immutable; viz. once created, nothing in it should change. This is so that what the client knows that it has received the same object that was created at the server and vice versa. We do not want the account contained in a AccountProtocol object to change on its way from the server to the client. This design decision is reected in the class in the following manner: Getter methods only to the properties; viz. all members are readonly. Adding a final modier to cmd, account and accountId to ensure that once assigned, these variable can never be changed. We have seen this in chapter 8. line 10 to 14, The rst constructor accepts a command and an account number. This is mainly used for creating a protocol that requests an Account object. line 15 to 19, The second constructor creates a AccountProtocol using a command and an Account object. line 20 to 22, Returns the type of command represented by a protocol object. line 23 to 25, Returns the account number of the Account object contain within. line 26 to 28, Returns the Account object. Remember the validation protocol that we implemented to Account in page 116? All that continues to work here! When a AccountProtocol object gets serialized, the Account that we have set also gets serialized as well.

11.2.2

Writing a Server

The AccountServer serves Account objects to clients. Accounts objects are stored as serialized disk les. The le name is the account number with a .ser extension. When the server receives a command, it does the following: GET Deserialized an Account object using the provided account number and returns the object to the client. If the Account is not found, an ERROR is returned. If the server is able deserialize the Account, the server will create a new AccountProtocol object with OKAY command and the Account object and returns it to the client.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

125

Writing a Server

11.2.2

PUT Serialized the given Account. An ERROR is returned the AccountServer cannot serializes the object. If the server is successful in serializing the object, with will acknowledge with OKAY. The ow of the above mentioned protocol is shown in gure 11.3.

Receives a GET Gets the account

Receives a PUT Saves the account

true

Successful?

false

Returns OKAY

Returns ERROR

Figure 11.3: Flow diagram of AccountServers protocol To write a server you have to do the following steps: 1. Create an instance of ServerSocket giving the required port number in the constructor. 2. Listen on the port by calling accept(). 3. When a client connects, accept() will return a Socket instance. This instance is connected with the client. 4. To communicate with the client, the server calls getInputStream() and getOutputStream() on the Socket instance. This will return an InputStream and an OutputStream respectively. The OutputStream is attached to the clients InputStream and vice versa. 5. Terminate the connection by calling close() on the Socket. The AccountServer listing is as follows :

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

126

Writing a Server

11.2.2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

import java.lang.*; import java.io.*; import java.net.*; public class AccountServer extends ServerSocket { public static final int PORT = 12345; public AccountServer(int port) throws IOException { super(port); } public void run() { System.out.println("AccountServer started..."); while (true) { try { Socket clnt = accept(); ObjectInputStream ois = new ObjectInputStream( clnt.getInputStream()); ObjectOutputStream oos = new ObjectOutputStream( clnt.getOutputStream()); AccountProtocol acctProto = (AccountProtocol)ois.readObject(); try { switch (acctProto.getCommand()) { case AccountProtocol.GET: handleGet(oos, acctProto); break; case AccountProtocol.PUT: handlePut(oos, acctProto); break; case AccountProtocol.ERROR: default: } } catch (Exception e) { oos.writeObject(new AccountProtocol( AccountProtocol.ERROR, 0)); } finally { ois.close(); oos.close(); clnt.close(); } } catch (Exception e) { } } } private void handleGet(ObjectOutputStream netOOS
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

127

Writing a Server

11.2.2

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

, AccountProtocol proto) throws Exception { FileInputStream fis = new FileInputStream( proto.getAccountNumber() + ".ser"); ObjectInputStream ois = new ObjectInputStream(fis); AccountProtocol result = new AccountProtocol( AccountProtocol.OKAY, (Account)ois.readObject()); netOOS.writeObject(result); netOOS.flush(); ois.close(); } private void handlePut(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { FileOutputStream fos = new FileOutputStream( proto.getAccountNumber() + ".ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(proto.getAccount()); oos.flush(); oos.close(); netOOS.writeObject(new AccountProtocol(AccountProtocol.OKAY , 0)); netOOS.flush(); } public static void main(String[] args) throws Exception { AccountServer server = new AccountServer(PORT); server.run(); } } Explanation for the AccountServer code is as follows: line 3, Remember to import java.net package. line 5, The AccountServer is a subclass of ServerSocket. We do not have to create a separate ServerSocket just to accept client connection. line 9, We manually chain the super class constructor because ServerSocket does not have a construct with the default constructors signature. line 11 to 41, The run() method is our main loop. In here, we wait for client connection and process them. line 15, accept() waits for a connection; it will return with a Socket instance when a client connects.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

128

Writing the Server API

11.2.3

line 16 to 19, Creates an ObjectInputStream and an ObjectOutputStream from the Socket. line 20, 21, Once we have gotten the input and output stream, we read the AccountProtocol object send by the client. line 23 to 32, We now use a switch statment to distinguish the command that we have received from the client. line 34, 35, If any of the handleXXX() methods throw any exceptions, we will send an ERROR back to the client. line 44 to 55, handleGet() desrializes the requested account for the hard disk and returns it in a AccountProtocol to the client. line 47 to 49, Opens the serialized object le for reading. line 50, 51, Creates a AccountProtocol object that contains the requested object. line 52, Sends the AccountProtocol to the client. line 55 to 68, handlePut() gets the Account object for AccountProtocol and serializes it to the hard disk. line 59 to 60, Open a le for serializing the Account. line 62, Extract the Account and serializes it. line 65, 66, Returns an OKAY status to the client. line 71, Starts the AccountServer on port 12345.

11.2.3

Writing the Server API

A client uses the Socket class to initiate a connection to a server. To instantiate a Socket, you will need the IP address of the host and the port number. If you successfully create a Socket instance, the accept() will have returned a corresponding Socket on the server side. All you have to do now is to get the InputStream and OutputStream. Once caveat about getting the InputStream and OutputStream from the server and client side Socket is that you cannot call get the same stream from both side in the same sequence. This will cause your program to hang. Consider the following code snippet from the server side:
1 2 3

Socket clnt = accept(); InputStream is = clnt.getInputStream(); OutputStream os = clnt.getOutputStream(); 129

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Writing the Server API

11.2.3

Note the call sequence: InputStream frist. If we were to write the following on the client side, our program will hang:
1 2 3

Socket toServer = new Socket("batcave", 12345); InputStream is = toServer.getInputStream(); OutputStream os = toServer.getOutputStream(); We should reverse the getXXXStream() call sequence like below:

1 2 3

Socket toServer = new Socket("batcave", 12345); OutputStream os = toServer.getOutputStream(); InputStream is = toServer.getInputStream(); Okay, lets look at AccountServerAPI. The class has 1 constructor that takes a host name that the server is running on. The signature of the constructor is a follows: public AccountServerAPI(String server) AccountServerAPI contains the following methods: public Account getAccount(int accountNumber) Retrieves an Account with using the provided accountNumber. If the accountNumber does not exist, getAccount() will return null. public Account putAccount(Account acct) Saves acct back to the server. If the save is successful, putAccount() will return the Account object that is saved; this will be equal to acct. If the save is not successful, a null will be returned. Both getAccount() and putAccount() throws APIException2. Why do we need a AccountServerAPI? Remember abstraction and encapsulation? We want to hide the implementation details for the users, programmers who will be using our API to develop applications, and localize our protocol behaviour to 2 classes: AccountServerAPI and AccountServer. The listing for AccountServerAPI is as follows:

1 2 3 4 5 6 7 8 9

import java.lang.*; import java.io.*; import java.net.*; public class AccountServerAPI { private Socket toServer = null; private String hostname; private ObjectInputStream ois; private ObjectOutputStream oos; public AccountServerAPI(String hn) {
2

APIException listing is not shown.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

130

Writing the Server API

11.2.3

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

hostname = hn; } public Account getAccount(int acctId) throws APIException { AccountProtocol result = write( new AccountProtocol(AccountProtocol.GET, acctId)); if (result.getCommand() == AccountProtocol.OKAY) return (result.getAccount()); else return (null); } public Account putAccount(Account acct) throws APIException { AccountProtocol result = write( new AccountProtocol(AccountProtocol.PUT, acct)); if (result.getCommand() == AccountProtocol.OKAY) return (acct); else return (null); } public AccountProtocol write(AccountProtocol proto) throws APIException { AccountProtocol result = null; connect(); try { oos.writeObject(proto); oos.flush(); result = (AccountProtocol)ois.readObject(); } catch (Exception e) { close(); throw new APIException(e.getMessage()); } try { close(); } finally { return (result); } } private void connect() throws APIException { try { toServer = new Socket(hostname, AccountServer.PORT); oos = new ObjectOutputStream(toServer.getOutputStream()); ois = new ObjectInputStream(toServer.getInputStream());
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

131

Writing the Server API

11.2.3

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

} catch (UnknownHostException e) { throw new APIException(e.getMessage()); } catch (IOException e) { throw new APIException(e.getMessage()); } } private void close() throws APIException { try { oos.close(); ois.close(); toServer.close(); } catch (IOException e) { throw new APIException(e.getMessage()); } } } line 12 to 20, The getAccount() method returns the an Account from the server when given a valid account number. line 14, 15, We create a AccountProtocol, setting the command to GET and give the appropriate account number. After we have create the protocol, we use write() method (see below). The result of the GET is returned as another AccountProtocol object. line 16, We check the return result if the command is OKAY. line 17, If the command is an OKAY, we then return the Account object to the caller and exits this method. line 19, If the command is not an OKAY, very likely an ERROR, we then return a null. line 21 to 29, The putAccount() saves an Account object back to the server. line 23, 24, Again, we create a AccountProtocol object, setting the command to PUT and give an Account object that we wish to save. We then use write() to write the protocol to the server. line 25, 26, We check result again to see if the write is successful. If it is we return the saved Account object to the caller signifying that the save is successful. line 28, If the save is not successful, we return a null.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

132

Using the AccountServerAPI

11.2.4

line 30 to 47, The write() method takes a AccountProtocol object and sends it to the AccountServer. The method then reads the reply from the server and returns this to the caller. Note that write() is a public method. You can actually call write() and send you own commands to the server. line 33, Connects to the server rst. We will examine the connect() method shortly. line 35, 36, Writes the AccountProtocol to the server. The flush() forces the write on the ObjectOutputStream. oos is set by connect(). line 37, We then read the result returned by the server. ois is set by connect(). line 39, 40, If the above writeObject() or readObject() has any exceptions, we closes the stream and notify the caller by throwing an APIException. line 43, If the writeObject() and readObject() is successful, we then close the connection to the server. line 45, Returns result which we got from the server back to the caller. line 48 to 59, This method connects to an AccountServer; it also sets up the ObjectInputStream and ObjectOutputStream for reading and writing later. line 51, We create a connection to a server on host hostname which we set in the constructor listening on port AccountServer.PORT (12345). line 52, Create an ObjectOutputStream using the InputStream from the Socket. line 53, Similarly, create an ObjectInputStream. line 60 to 69, The close() method closes the opened object streams as well as the connection to the server.

11.2.4

Using the AccountServerAPI

Now that we have hide the complexities of handling the protocol in getAccount() and putAccount(), we will now see how we can use the AccountServerAPI. We need to create an instance of AccountServerAPI by providing it with the servers name as in the following code: AccountServerAPI api = new AccountServerAPI("batcave");

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

133

Using the AccountServerAPI

11.2.4

The following code snippet gets an account 12345 from the server, processes it and saves the account back.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

. . . AccountProtocol result = api.getAccount(12345); if (result.getCommand() != AccountProtocol.OKAY) { // Error ! Did not get account return; } Account acct = result.getAccount(); // process acct . . . result = api.putAccount(acct); if (result.getCommand() != AccountProtocol.OKAY) { // Error ! Save was not successful return; } . . .

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

134

Chapter 12

Threads and Synchronization


Threads, like taxes are always present in the JVM. Threading is such an integral part of the JVM that it is almost impossible to write single threaded program save for hello world; even then your single threaded program lives in the JVM threaded environment. Here are just some examples that the JVM uses threads to support your program without your knowledge: In handling event, the JVM dispatches a thread to call your event handler. When your AWT window requires repaint because of exposure, the paint() is called by a thread. Object references goes out of scope, a thread called the garbage collector is used to free the memory. Threads are very powerful concepts in computer science. They allow us to exploit the natural concurrency that occurs in our program; by natural concurrency we mean multiple independent task. By creating threads to handle these independent tasks, we make our program more ecient and responsive. Some examples tasks that will benet from being threaded are asynchronous I/O, timers and servers. Consider the code snippet below which is part of the AccountServer shown on page 126:
1 2 3 4 5 6 7 8 9

. . . public void run() { System.out.println("AccountServer started..."); while (true) { try { Socket clnt = accept(); ObjectInputStream ois = new ObjectInputStream( clnt.getInputStream()); ObjectOutputStream oos = new ObjectOutputStream( 135

What is a Thread?

12.1

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

clnt.getOutputStream()); AccountProtocol acctProto = (AccountProtocol)ois.readObject(); try { switch (acctProto.getCommand()) { // Process command } } catch (Exception e) { . . . } finally { . . . } } catch (Exception e) { } } } . . . When the server starts, the JVM creates the rst thread to execute the server. This thread is called the primordial thread. This thread will hang in accept() until it receives a connection. When a client connection comes in (line 6 ), the server returns from accept() and reads in the AccountProtocol object (line 11, 12 ) and proceeds to handle the contained command (line 14 to 16 ) by calling the appropriate handleXXX() method. The server has just received a client connection and is presently in handleGet() method. At this point, another client contacts the server. But since the server is in handleGet(), the client is forced to wait until the server returns to accept() to accept the connection. This is one instance where we can use thread to make our program more ecient and responsive by creating a thread to handle the client while the servers thread returns to wait in accept(). We will return to this example a little later and mutithread it.

12.1

What is a Thread?

So what exactly is a thread? A thread is an execution path viz. a section of code executed independently of other threads within a single program. A thread has the following characteristics: Threads start their execution at a well know point just as Java programs start their execution at a well known point. A Java program will always start executing at main(). Statemets from a threads starting point are executed according to their sequence. A thread can access all local variables and member variables.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

136

Creating a Thread

12.1.1

Threads are independent of each other; their execution order is consider to be non-deterministic viz. you cannot predict the order of execution of the threads.

12.1.1

Creating a Thread

There are 2 ways that you can create a thread with JDK. You can either 1. subclass Thread class, or 2. implements the Runnable interface. believe that the second method is more exible because you can always implement more than 1 interface but subclass only on class. So we will look at creating threads from the Runnable interface perspective. Once you have understand this approach, creating threads by subclassing Thread is very easy. Subclassing Thread is in fact a specialization of the rst method. We will leave this as an exercise for the reader1 . To create a thread, you must implement the Runnable interface. The interface contains only 1 method and is dene as below: public interface Runnable { public void run(); } You class that implements Runnable must implement run(). The run() method is very important to a thread because it is the threads starting point just as main() is a Java programs starting point. When a thread starts, it will execute the run() method. When the thread has completed executing run(), the thread is said to have completed and therefore dead. Consider the below code:
1 2 3 4 5 6 7 8 9 10 11

import java.lang.*; public class ThreadedMessage implements Runnable { private String msg; public ThreadedMessage(String m) { msg = m; } public void run() { System.out.println(msg); } }
1

Hint: think of subclassing and overriding.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

137

Creating a Thread

12.1.1

line 3, We implement the Runnable interface if we want this class to run as a thread. The ThreadedMessage class is not a thread yet. By implementing the Runnable interface, we are merely giving this class the capability to run as a thread. line 8 to 10, The run() method which we must implement. Now that we have a class which is capable of running as a thread, we will now make it run as one. The following code snippet shows how:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

. . . ThreadedMessage msg0 = new ThreadedMessage( "Hello threaded world #1"); ThreadedMessage msg1 = new ThreadedMessage( "Hello threaded world #2"); ThreadedMessage msg2 = new ThreadedMessage( "Hello threaded world #3"); Thread thr0 = new Thread(msg0); Thread thr1 = new Thread(msg1); Thread thr2 = new Thread(msg2); thr0.start(); thr1.start(); thr2.start(); . . . line 2 to 7, We create 3 instances of ThreadedMessage. These are just your regular instance. Nothing fancy. line 8 to 10, Now we create 3 instances of Thread. The Thread class takes a Runnable interface in its constructor. line 11 to 13, When we call start() on the Thread instance, the Thread instance will start running as a bona de thread. The above code deserves a little more explanation. When we pass an instance of ThreadedMessage , msg0 for example, to an instance of Thread (thr0), we are asking thr0 to execute msg0 as a thread. At this point thr0 is not a thread yet. When we call start(), the JVM will start thr0 as a thread. At this point, thr0 will start executing run(). The thread thr0 is said to complete when it has completed executing run(). Figure 12.1 shows the interaction of the threads. Therefore, creating a thread involves the following steps: 1. Implement the Runnable interface in your class. Write code in run() method; these code will be executed by the thread.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

138

Thread Class

12.1.2

Thread msg2 msg1 msg0 primordial

8`

9`

10 11 12

13

Line no.

Figure 12.1: Time/thread graph 2. Instantiate a Thread object, giving it the class that implements the Runnable interface in the Threads constructor. 3. Call start() on the Thread object.

12.1.2

Thread Class

Now that we know how to create threads, lets examine some useful methods in the Thread class. public boolean isAlive() Checks if a thread is alive. The denition of is alive is the thread has not completed executed the run() method. public void start() This method starts a thread. Note that you cannot restart a thread with this method if a thread is completed. You have to recreate the thread instance. public void setDaemon(boolean d) Sets if the thread is a daemon thread viz. a worker thread. When you create a thread, by default, these thread are known as user thread. User thread are threads that are performing your code. By contrast, daemon threads are those threads that provide certain services like clocking and watchdog. A Java program will not exit if there are user 139

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Thread Class

12.1.2

threads which are still alive. You can set a thread to be daemon by calling this method with true. This method can only be called before you call start(). public void setName(String name) Sets a user friendly name to your thread. public void setPriority(int p) Changes the priority of the thread. A thread can be in one of the following 3 priorities: Thread.MAX_PRIORITY Thread.MIN_PRIORITY Thread.NORM_PRIORITY You can set any priorities between the range of MAX_PRIORITY and MIN_PRIORITY inclusive. We will look at priorities in section 12.1.3. public void sleep(long time) Causes the thread to cease execution for a period of time (in milliseconds). When a thread is a sleep it does not loose ownership of the locks that it is holding. We will look at locks in the following section. public native static Thread currentThread(int p) Returns the thread that is executing the current object. public void yield() Causes the current thread to temporarily stop executing so that other threads can have a chance to execute. We will now rewrite AccountServer that it allocates a thread for each connection. AccountServer now behaves as follows: When the server receives a connection, accept() will return a Socket instance. This Socket will passed to a thread called ConnectionHandler which is responsible for handling clients requests. The server thread (primordial thread) will return to accept() to wait for other client connections. Lets look at the ConnectionHandler rst; the listing is as below:
1 2 3 4

import import import public

java.lang.*; java.io.*; java.net.*; class ConnectionHandler 140

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

Thread Class

12.1.2

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

implements Runnable { private ObjectInputStream ois; private ObjectOutputStream oos; private Socket clnt; public ConnectionHandler(Socket sock) throws IOException { clnt = sock; } public void run() { try { ois = new ObjectInputStream(clnt.getInputStream()); oos = new ObjectOutputStream(clnt.getOutputStream()); AccountProtocol acctProto = (AccountProtocol)ois.readObject(); try { switch (acctProto.getCommand()) { case AccountProtocol.GET: handleGet(oos, acctProto); break; case AccountProtocol.PUT: handlePut(oos, acctProto); break; case AccountProtocol.ERROR: default: } } catch (Exception e) { oos.writeObject(new AccountProtocol( AccountProtocol.ERROR, 0)); oos.flush(); } finally { ois.close(); oos.close(); clnt.close(); } } catch (Exception e) { System.out.println(e.getMessage()); } } private void handleGet(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { // as before . . . }
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

141

Thread Class

12.1.2

49 50 51 52 53 54 55

private void handlePut(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { // as before . . . } } If you examine the ConnectionHandler class you would have notice that all the the code is exactly from AccountServer. The bulk of the code which was previously in a while loop is now in a run() method (line 13 to 42 ). The following is a multi threaded version of the original AccountServer:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

import java.lang.*; import java.io.*; import java.net.*; public class AccountServer extends ServerSocket { public static final int PORT = 12345; public AccountServer(int port) throws IOException { super(port); } public void run() { System.out.println("AccountServer started..."); while (true) { try { Thread thread = new Thread( new ConnectionHandler(accept())); thread.start(); } catch (Exception e) { System.out.println(e.getMessage()); } } } public static void main(String[] args) throws Exception { AccountServer server = new AccountServer(PORT); server.run(); } } Notice how simple AccontServer has become. Most of the multi threading logic is between between lines 15 to 17. When the server receives a client connection, it creates a ConnectionHandler to handle the client.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

142

Enhancing AccountServer

12.2

The ConnectionHandler instance is started as a thread. At this point the ConnectionHandler thread will handle the clients requests (in run()) while the server thread returns to wait for other client connection (line 15 ).

12.1.3

Priorities

You can give priorities to threads. This is done with the setPriority() method. The default priority for all threads is Thread.NORM_PRIORITY. A higher priority thread will preempt a lower priority thread. The following methods suspends the execution of the calling thread and gives other threads a chance to execute: yield() When a thread calls yield(), it stops executing temporarily and give threads of higer or equal priority a chance to execute. sleep() A thread which calls sleep() will temporarily (for the duration of the sleep time) stop executing and give lower priority threads a chance to run.

12.1.4

States of a Thread

A thread can be in one of the following 4 states: Initial A thread is said to be in its initial state when it is created until start() is called. Runnable A thread is in its runnable state after its start() is called. A runnable thread does not mean that the thread is actually running. This is for the thread scheduler to decide. All this state says is that the thread is ready to run. Blocked A blocked thread is one that cannot run because it is waiting for some resource or some events. Exiting When a thread has completed its run(). Figure 12.2 show the life cycle of a thread and its transistion between the various states.

12.2

Enhancing AccountServer

Before we proceed further with threads, lets return to look at the current state of our AccountServer. Consider the following scenerio: A client connects to the server and request account number 12345. The server deserializes the account and returns it to the client. The connection between the client and the server is now severed. This behaviour is codied in AccountServer and AccountServerAPI in chapter 11. After the rst
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

143

Enhancing AccountServer

12.2

new Thread() start()

yield() or preempted

Initial

Runnable
wait()

completes run()

notify()

Exiting

Blocked

Figure 12.2: Life cycle of a thread client has request 12345, a second client connects and request the same account. We could not possibally return the account to another client because this would cause account 12345 to be inconsistent. To solve this problem, the server must know what Account objects have been requested. The server needs to keep a checkout list so that any subsequent request for account found on this list will be rejected. When the checked out account is later save, it is taken o this list. The new behaviour for AccountServer is as follows: 1. The server receives a client connection. It spawns a ConnectionHandler thread and passes it the connection. 2. If the command is a GET, the ConnectionHandler thread checks the requested account number against a requested list. If the account is not on the list, the thread will return the requested account with an OKAY status. 3. But if the account has been checked out, the thread will return a new status, INUSE to the client. 4. If the client sends a PUT command, the server must now check if the account is on the checkout list. If it is, the server must remove it from the list after serializing the account. If the account has not been checked out, we just serializes it.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

144

Enhancing AccountServer

12.2

5. We dene an additional command called RELEASE which essentially does the same thing as a PUT except that RELEASE only removes an account from the checkout list. The modied protocol is shown in gure 12.3.

Receives a GET true Checkout? false

Receives a PUT Saves the account Returns ERROR Remove from checkout

Returns INUSE

Gets the account Add to checkout

true

Successful?

false

Returns OKAY

Returns ERROR

Figure 12.3: A modifed ow diagram of AccountServers protocol To implement the checkout list, we employ Hashtable class. This class implements a hashtable. Briey, hashtable is a structure that allows you to store data by associating the data with a unique key. We will use the following 3 methods from Hashtable: public Object put(Object key, Object data) Associate the key with data. If the key exists in the hashtable, the previous will be returned and the current data data will take its place. public boolean containsKey(Object key) Test if the hashtable contains key. public Object remove(Object key) Deletes the key from the hashtable. Will return null if the key is not in the hashtable; otherwise the corresponding data is returned.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

145

Enhancing AccountServer

12.2

All 3 classes that implements the server must be modied. These classes are AccountServer, ConnectionHandler and AccountProtocol. Modication to AccountProtocol is as follows; we add the INUSE (line 7 ) and RELEASE (line 8 ) to the list of commands.
1 2 3 4 5 6 7 8 9 10 11 12 13

public class AccountProtocol implements Serializable { public static final int ERROR = -1; public static final int GET = 0; public static final int PUT = 1; public static final int OKAY = 2; public static final int INUSE = 3; public static final int RELEASE = 4; private final int cmd; private final int accountId; private final Account account; public AccountProtocol(int c, int id) { . . . The following is a listing of the modied AccountServer. import java.lang.*; import java.io.*; import java.net.*; import java.util.*; public class AccountServer extends ServerSocket { public static final int PORT = 12345; private Hashtable checkout; public AccountServer(int port) throws IOException { super(port); checkout = new Hashtable(); } public void run() { System.out.println("AccountServer started..."); while (true) { try { Thread thread = new Thread( new ConnectionHandler(accept(), checkout)); thread.start(); } catch (Exception e) { System.out.println(e.getMessage()); } . . . }
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

146

Enhancing AccountServer

12.2

line 4, Hashtable class is in java.util package. line 8, We declare checkout as a Hashtable. line 12, Instantiate checkout when we create the server. A instance of Hashtable is shared by all ConnectionHandler threads. line 19, When we create a thread, we now need to pass to the thread checkout. The thread will check if a given account is in checkout. Everything else in AccountServer remains save for those modications listed above. And nally, the listing for ConnectionHandler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

import java.lang.*; import java.io.*; import java.net.*; import java.util.*; public class ConnectionHandler implements Runnable { private ObjectInputStream ois; private ObjectOutputStream oos; private Socket clnt; private Hashtable checkout; public ConnectionHandler(Socket sock, Hashtable table) throws IOException { clnt = sock; checkout = table; } public void run() { try { ois = new ObjectInputStream(clnt.getInputStream()); oos = new ObjectOutputStream(clnt.getOutputStream()); AccountProtocol acctProto = (AccountProtocol)ois.readObject(); try { switch (acctProto.getCommand()) { . . . case AccountProtocol.RELEASE: handleRelease(oos, acctProto); break; . . . } } catch (Exception e) { . . .
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

147

Enhancing AccountServer

12.2

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

} private void handleGet(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { AccountProtocol result; Integer key = new Integer(proto.getAccountNumber()); if (checkout.containsKey(new Integer(key))) { result = new AccountProtocol(AccountProtocol.INUSE, 0); } else { FileInputStream fis = new FileInputStream(key + ".ser"); ObjectInputStream ois = new ObjectInputStream(fis); Account account = (Account)ois.readObject(); result = new AccountProtocol( AccountProtocol.OKAY, account); ois.close(); checkout.put(key, account); } netOOS.writeObject(result); netOOS.flush(); } private void handlePut(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { FileOutputStream fos = new FileOutputStream( proto.getAccountNumber() + ".ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(proto.getAccount()); oos.flush(); oos.close(); handleRelease(netOOS, proto); } private void handleRelease(ObjectOutputStream netOOS , AccountProtocol proto) { Integer key = new Integer(proto.getAccountNumber()); if (checkout.containsKey(key)) checkout.remove(key); netOOS.writeObject(new AccountProtocol( AccountProtocol.OKAY, 0)); netOOS.flush(); } public static void main(String[] args) { . . . The explanation for the modied ConnectionHandler is as follows:
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

148

Enhancing AccountServer

12.2

line 14, Hold a reference to the Hashtable object that is passed. line 25, 26, In addition to GET and PUT (not shown), we also check if the command is a RELEASE. If it is we call the handleRelease() method. line 33 to 51, The handleGet() method is now modied so that it will check checkout rst before returning the requested account. If the requested account is in checkout, a INUSE is returned. line 37, Create a key using the account number. The key has to be an object because the put() method in Hashtable only accepts Objects (see page 145). line 38, We now check if the account has been requested using the key created from the previous line. line 39, If the account has been checked out, we return a INUSE command back to the client. line 41 to 45, Otherwise we retrieve the account as before. line 46, After we have deserialized the account we now need to put this account in checkout. line 52 to 62, handlePut() method serializes an account as well as removing the account from checkout. line 61, We use handleRelease() method to remove an account from checkout. line 63 to 71, The handleRelease() removes an account from the checkout list. line 65, Again we create a key from the account number. line 66, We check if the account is on the checkout list. line 67 to 69, If it is we remove it from checkout and returns an OKAY to the client. To accomodate the new INUSE command, we add a new method to AccountServerAPI called isInUse() which indicates if the last operation on an account is has been checkout by another client. The following list this new method and modications to the AccountServerAPI class.
1 2 3 4

public class AccountServerAPI { . . . private boolean inuse; public AccountServerAPI(String hn) {


Copyright c July 1999 Lee Chuk Munn. All rights reserved.

149

Synchrnonization

12.3

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

hostname = hn; inuse = false; } . . . public AccountProtocol write(AccountProtocol proto) throws APIException { AccountProtocol result = null; connect(); try { oos.writeObject(proto); oos.flush(); result = (AccountProtocol)ois.readObject(); inuse = AccountProtocol.INUSE == result.getCommand(); } catch (Exception e) { close(); throw new APIException(e.getMessage()); } try { close(); } finally { return (result); } } public boolean isInUse() { return (inuse); } . . . } line 3, Add a new member inuse which keeps track of the INUSE command. line 17, After we have read the return result from the server, we set inuse. line 28 to 30, Checks if the last command involved a checkout account.

12.3

Synchrnonization

In the previous section, we modied our AccountServer and ConnectionHandler class such that a checkout account will be entered into a checkout list. This method of updating is acceptable if our server is single threaded. But it is not. Consider the following: a client request for account 12345. A thread is created (thr1) thread which then checks if 12345 is in the checkout list. 12345 is not on the list. thr1 proceeds to access the account from hard disk but gets preempted. At this point in time, a second thread (thr2) is
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

150

synchronized Keyword

12.3.1

created for another client which also happens to reqests 12345. It checks the checkout list. 12345 is not on the list. This is because thr1 has not yet update the checkout list. thr2 then proceeds to deserialize 12345, updates the checkout list and returns the Account object back to the client. The thread scheduler now continues thr1s execution. thr1 completes its request: deserializes 12345, updates the checkout list and returns the account back to the client. This interaction is shown in gure 12.4. What we need is a way of making the deserialize and updating the checkout list atomic. An atomic action is an action that cannot be further broken into smaller actions; in other words the deserialization and updating the checkout list actions cannot be interrupted. So how are we going to create an atomic action out of the deserialization and updating actions. As it turns out, every Java object has a lock and since our program is abound with objects, we could theoretically use any object to act as a lock. In practise, we should choose the object that best ts the purpose. In our case, the best candidate would be checkout in AccountServer. The reason for choosing checkout is because this object is shared by all ConnectionHandler threads. AccountServer passes the same checkout object to all threads that it creates. To implement atomicity, we perform the following steps: A ConnectionHandler thread will try to lock checkout object. Upon acquiring the lock, the thread will deserialize the requested account and updates checkout. After performing the two actions, the thread releases the lock. When a ConnectionHandler thread tries to lock checkout and nds that another thread has acquired the lock, the thread will not proceed until the lock has been released and it has acquired it. This is performed by the JVM. When 2 threads tires to simultaneously acquire a lock, the JVM will arbitrarily assign the lock to a thread.

12.3.1

synchronized Keyword

We will now introduce the synchronized keyword which is used to acquire a lock. The syntax is as follows: synchronized(<object>){ <body> } The <object> is the object whose lock we are trying to acquire. If we are successful, we will proceed into the <body>. If we are not succesful, we
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

151

synchronized Keyword

12.3.1

thr1 Thread is created to GET 12345. Checks if 12345 is on the checkout list. It is not. Deserializing 12345 but gets preempted.

thr2

Thread is created to GET 12345. Checks if 12345 is no the checkout list. It is not. Deserializes 12345. Returns 12345 to client. Updates checkout list. Terminates.

Continues running. Completes deserialization. Returns 12345 to client. Updates checkout list. Terminates. Figure 12.4: A race condition will wait at synchronized until we acquire the lock. When we exit the <body>, we releases the lock. Therefore, the curly brackets ({ }) denes the scope of the lock. The <body> is also called a critical region because only 1 thread is allowed in at any one time. If a thread which is holding a lock gets preempted, it still holds the lock. So atomicity is guranteed. The modications to ConnectionHandler is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

public class ConnectionHandler implements Runnable { . . . private Hashtable checkout; . . . private void handleGet(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { AccountProtocol result; Integer key = new Integer(proto.getAccountNumber()); synchronized(checkout) { if (checkout.containsKey(key)) { result = new AccountProtocol(AccountProtocol.INUSE , 0); } else {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

152

synchronized Keyword

12.3.1

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

FileInputStream fis = new FileInputStream(key + ".ser"); ObjectInputStream ois = new ObjectInputStream(fis); Account account = (Account)ois.readObject(); result = new AccountProtocol(AccountProtocol.OKAY , account); ois.close(); checkout.put(key, account); } } netOOS.writeObject(result); netOOS.flush(); } private void handlePut(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { synchronized(checkout) { FileOutputStream fos = new FileOutputStream( proto.getAccountNumber() + ".ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(proto.getAccount()); oos.flush(); oos.close(); handleRelease(netOOS, proto); } } private void handleRelease(ObjectOutputStream netOOS , AccountProtocol proto) throws Exception { Integer key = new Integer(proto.getAccountNumber()); synchronized(checkout) { if (checkout.containsKey(key)) checkout.remove(key); } netOOS.writeObject(new AccountProtocol(AccountProtocol.OKAY , 0)); netOOS.flush(); } } line 11 to 24, This synchronized block (critical region) ensures that deserialization and updating of checkout is atomic. Note that we have changed nothing in the original code save for enclosing it with a synchronized.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

153

Thread Safe Objects

12.3.2

line 31 to 39, Just as a GET is atomic, so a PUT command must be atomic as well since both handleGet() and handlePut() manipulates the same checkout list. If handlePut() is not synchronized, then this method will update checkout without regard to what handleGet() is doing. This can lead to inconsistencies in the checkout list. line 45 to 48, Similarly, when we handle a RELEASE command, we also need to acquire the lock to checkout before modifying checkout. A few pointers to remember when you are using synchronized: Create the smallest possible block; otherwise you are delaying the lock. This could lead to performance problem if you have lots of threads waiting to enter the critical region. You should always try to release a lock as quick as possible. Again, the reason should be obvious. Never write nested synchronized blocks as below: synchronized(anObject) { . . . synchronized(anotherObject) { . . . } } This can lead to dead locks.

12.3.2

Thread Safe Objects

We have seen that by using synchronized, we create a critical region which maintains atomicity on all the statements contain therein. But we must remember to explicitly lock the object (checkout in the example above). What if, in our haste, we forget to lock checkout before we use, or that the person who is writing threaded programs is a novice and have no notion of thread safeness. So, if you know that your program is going to be used in a multi threaded, make your class thread safe. Instead of requiring others to synchronized() the object before using, we can declare a method to be synchronized as in the example below: public synchronized void someMethod() { . . . }

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

154

Thread Safe Objects

12.3.2

When we invoke someMethod(), we must acquire the lock to the object viz. the object which someMethod() is part of. When we leave the method, we release. As before, the method body of someMethod() is now considered the critical region. This is just a variation of the follow: public void someMethod() { synchronized(this) { . . . } } With this new found knowledge, lets modify AccountServer such that we can control the maximum number of ConnetionHandler thread the server can create at any one time. We will call this class Counter. The Counter class has 1 constructor which takes an int; the int species the number of thread that can be active at any given time. Counter class has the following methods: public synchronized boolean add() Increments the count. add() will return true then we have not exceeded the limit; false otherwise. public synchronized void release() Decrement the count. public void getCurrentCount() Returns the number of currently active thread. Notice that this method does not have the synchronized keyword in its method declaration. We will explain this shortly. public void getCount() Returns the maximum count. This method is not synchronized as well. The listing for Counter is as follows:
1 2 3 4 5 6 7 8 9 10 11

public class Counter { private final int maximum; private int current; public Counter(int max) { maximum = max; current = 0; } public synchronized boolean add() { if (current == maximum) return (false); current++;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

155

Thread Safe Objects

12.3.2

12 13 14 15 16 17 18 19 20 21 22 23

return (true); } public synchronized void release() { current = (--current < 0)? 0: current; } public int getCurrentCount() { return (current); } public int getCount() { return (maximum); } } line 5, We set the variable maximun which controls the number of thread that can be active at any given time. line 6, current holds the number of thread that is active at any given time line 8 to 13, This is the rst of the thread safe method. We must acquire the lock to the object (this) before we can invoke this method. line 9, 10, We check if we have reached the limit, if we have then we return false. line 11, 12, Otherwise we increment current and return true. line 14 to 16, The release() method decrements the current variable. line 17 to 19, Returns the number of active thread; viz. the value of current. line 20 to 22, This method returns the maximum count; viz. the value of maximum. As we have pointed out earlier, we do not need to lock the object before invoking getCount(). Why? The observant reader will have notice that both add() and release() modies the current variable. We cannot have 2 thread calling add() and relase() simultaneous. The behaviour is nondeterministic; to enforce orderly update of current, we require the invoker of the 2 methods to acquire the object lock rst before proceeding. But since getCount() and getCurrentCount() does not update count we do not need to lock the object. This prevents unnecessary delay. Modications to AccountServer and ConnectionThread is only minimal. The changes to these classes are as follows:

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

156

Thread Safe Objects

12.3.2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

public class AccountServer extends ServerSocket { . . . private Counter count; public AccountServer(int port) throws IOException { super(port); checkout = new Hashtable(); count = new Counter(5); } public void run() { System.out.println("AccountServer started..."); while (true) { try { Thread thr = new Thread(new ConnectionHandler(accept() , checkout, count)); thr.start(); } catch (Exception e) { System.out.println(e.getMessage()); } } } . . . } The following lines are noteworthy: line 9, Create an instance of Counter. We have hard coded the Counter so that it only allows a maximum 5 threads to be active. line 15. 16, Now whenever we create a thread, will pass to it the count object. Lets turn our attention to ConnectionThread and see how it uses Counter class.

1 2 3 4 5 6 7 8 9 10

public class ConnectionHandler implements Runnable { . . . private Hashtable checkout; private Counter count; public ConnectionHandler(Socket sock, Hashtable table , Counter cnt) throws IOException { clnt = sock; checkout = table;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

157

Condition Variables

12.4

11 12 13 14 15 16 17 18 19 20 21 22 23 24

count = cnt; } public void run() { if (!count.add()) return; try { . . . } catch (Exception e) { System.out.println(e.getMessage()); } count.release(); } . . . } line 14, We try to increnemnt count. If we are successful, we continue executing the thread. line 15, If we cannot increment count, we then exit from the thread. line 21, When we are done with our processing, we must call release() to decrement the count before we exit.

12.4

Condition Variables

If synchronized allows threads to enter and exit critical region in an orderly manner then condition variables allows one thread to notify another when certain condition exists. Consider the following: presently Counter will will terminate a thread2 if there are already a maximum number of thread active. A better way to handle this situation might be as follows: When we have reached maximum number of active threads, then put the next thread in a waiting area. When one of the currently running thread terminates, it noties the waiting thread that there is now a slot available for it to run. The waiting thread then resumes execution. The condition here is that if current have reached maximum we wait for it to drop below maximum. To implement condition variables, we use wait() and notify(). These two methods are found in Object class. Since every object in the JVM inherits directly or indirectly from Object, we can use condition variables on all objects. The following explains these 2 methods: public void wait() Waits for a condition to occur. The wait() method causes a thread
2

It will return false and ConnectionHandler will terminate the thread.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

158

Condition Variables

12.4

to wait until it is notied by another thread to stop waiting. When wait() is called, the thread releases its lock on an object and waits until another thread noties it to wake up through notify(). After a thread is awakened, it must reacquire the lock before it can proceed. The caller of this method must rst acquire the lock; viz. calling from within a synchronized block. public void notify() This method wakes up a thread that has called wait(). notify() arbitrarily awakens just 1 thread. This method can only be called by the current owner of the lock. Both the above method will throw IllegalMonitorStateException if the caller is not the current owner of the lock. Below is a reimplementation of Counter that uses condition variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

public class Counter extends Object { private final int maximum; private int current; public Counter(int max) { maximum = max; current = 0; } public synchronized void add() { if (current == maximum) while (true) try { wait(); break; } catch (InterruptedException e) { } current++; } public synchronized void release() { current = (--current < 0)? 0: current; notify(); } public int getCount() { return (maximum); } } line 13, If we have already reached maximum, then we wait until we are notied.
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

159

Implication of static on Threads

12.5

line 14, When we awakened from wait(), we exit the while loop. line 15, wait() throws InterruptedException. In this case we ignore this exception. When we return from the empty exception body we return to the wait() method by virtue of the innate loop. line 20, When we decrement current, we call notify() to wakeup any waiting threads. Notice that both wait() and notify() are called from within a synchronized block. This is what we mean by saying you have to acquire the objects lock before invoking either of those methods. We now need to modify only ConnectionHandler to change the way we invoke add() as below:
1 2 3 4 5 6 7 8 9 10 11

. . . public void run() { count.add(); try { . . . } catch (Exception e) { System.out.println(e.getMessage()); } count.release(); } . . . So how does this new scheme works? We have 2 ConnectionHandler threads; lets call them thr1 and thr2. thr1 is running and has just called add() so that current is now equals to maximum. Now when thr2 invoke add() by rst acquiring the Counters lock. But add() will not let thr2 proceed because the maximum has been reached. So Counter puts thr2 in a waiting area by invoking wait(). thr2 also gives up the lock that it is holding. When thr1 completes, it calls release() before it exits. This trigger a call to notify(). thr2 awakens but it could not continue yet because it has to reacquire the objects lock. When thr1 exits release(), it releases the lock. thr2 then acquires the lock and continues with its execution. This interaction is shown in gure 12.5.

12.5

Implication of static on Threads

During the course of this book, you have notice that we have at numerous times preceed member or method declarations with static. What exactly is static? We know that when we instantiate 2 instance of FixedDepositAccount, the members of both instances are separate in the sense that if we were to
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

160

Implication of static on Threads

12.5

thr1 Acquire Counters lock. Calls add(). Release lock on exit. Continues running.

thr2

Acquire Counters lock. Calls release(). release() calls notify(). Exits release(). Releases Counters lock.

Acquire Counters lock. Calls add(). Cannot proceed. Calls wait(). Release lock and go to sleep. Wakes up. Tries to get Counters lock.

Got Counters lock. Continues running.

Figure 12.5: wait() and notify() interaction change the name member of one instance via setName(), it will not aect the name of another instance. Sometimes, we might want all instances to share a single member viz. changing a member variable cause all other instances to see this change. To achieve this behaviour, we preceed a members declaration with static. Consider the following simple declaration:
1 2 3 4

public class Fred { public String name; public static int age; } Now if we were to create 2 instances of Fred and perform the following operations on these instances:

1 2 3 4 5 6

Fred flintstone = new Fred(); flintstone.name = "Flintstone"; flintstone.age = 40; Fred mercury = new Fred(); mercury.name = "Mercury"; mercury.age = 45; The result of listing below: flintstone mercury name Flintstone Mercury age 45 45

Notice that both age member are 45 but name remains their respective values. This is because of the static declaration on age. By making age
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

161

Implication of static on Threads

static, we are declaring that there is only 1 copy of age no matter how many instance of Fred we have instantiated. When we change a static member, all other instance sees it. Members declared with static are called class members since there is only 1 copy of that member for the entire class; non static members are called instance member since they are local to the instance only. We can access class members without actually instantiating it. To access a class member, we specify the class name followed by the class member. The syntax is as follows: <class_name>.<class_member> Therefore to access age, we do the following: Fred.age = 45; Methods declared with static are called class methods. Class methods dier from instance methods in one important way: you cannot use this in a class methods body. This is because a class method is not associated withy any particular instance. What impact does class methods or members have on thread programming? When a member is declared static, there is only one copy. So if you lock a static member, you are eectively locking out all access to that member.

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

162

Appendix A

Listing
The following is a complete list of all classes pertaining to the Account class that we have used as an example throughout this book. The listing is base on ideas that we have developed until chapter 12.

A.1

Core Classes

The following listing pertains to the core Account class.

A.1.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

Account.java

import java.lang.*; import java.io.*; import java.util.*; public abstract class Account implements Serializable { public static final long serialVersionUID = 1234567890L; private String name; private int acctNo; private float balance; private boolean overdraft; private Interest interest; private Vector listeners; public class DefaultRate implements Interest { public float calculate(float amt) { return (balance * 0.1F); } } public Account() { } public Account(String n, int no) { name = n; acctNo = no; balance = 100F; overdraft = false; listeners = new Vector();

163

Account.java

A.1.1

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

interest = new Interest() { private float monthlyRate[] = {0.1F, 0.07F, 0.1F, 0.1F , 0.1F, 0.12F, 0.1F, 0.1F, 0.08F, 0.1F, 0.1F, 0.0F}; private float rate; { rate = monthlyRate[ (Calendar.getInstance()).get(Calendar.MONTH)];} public float calculate(float amt) { return (balance * rate); } }; } public void setName(String n) { name = n; } public String getName() { return (name); } public int getAccountNumber() { return (acctNo); } public float getBalance() { return (balance); } public void setOverdraft(boolean b) { overdraft = b; } public boolean isOverdraft() { return (overdraft); } public abstract void printAccount(); public void setInterestCalculation(Interest i) { interest = i; } public float calculateInterest() { if (interest != null) return (interest.calculate(getBalance())); else return (0F); } public void deposit(float amt) throws ErroneousAmountException { if (amt < 0) throw new ErroneousAmountException("deposit", this, amt); if (amt >= 0) balance += amt; fireMoneyDeposited(new AccountEvent(this, amt)); } public void withdrawal(float amt) throws OverdrawnException, ErroneousAmountException { if (amt > balance) throw new OverdrawnException("withdraw", this, amt); if (amt < 0) throw new ErroneousAmountException("withdraw", this, amt); balance -= amt;

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

164

Account.java

A.1.1

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

fireMoneyWithdrawn(new AccountEvent(this, amt)); } public void transfer(Account from, float amt) throws OverdrawnException, ErroneousAmountException { if (amt < 0) return; from.withdrawal(amt); deposit(amt); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeLong(((((new Date()).getTime()/1000)/60)/60)/24); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); long current = ((((new Date()).getTime()/1000)/60)/60)/24; if ((current - in.readLong()) > 60) throw new IOException("Object expired!"); } public void addAccountListener(AccountListener l) { listeners.addElement(l); } public void removeAccountListner(AccountListener l) { listeners.removeElement(l); } private void fireMoneyDeposited(AccountEvent aEvt) { Vector tmpV; synchronized(listeners) { tmpV = (Vector)listeners.clone(); } for (int i = 0; i < tmpV.size(); i++) { AccountListener l = (AccountListener)tmpV.elementAt(i); l.moneyDeposited(aEvt); } } private void fireMoneyWithdrawn(AccountEvent aEvt) { Vector tmpV; synchronized(listeners) { tmpV = (Vector)listeners.clone(); } for (int i = 0; i < tmpV.size(); i++) { AccountListener l = (AccountListener)tmpV.elementAt(i); l.moneyWithdrawn(aEvt); } } public String toString() { return ("name = " + name + "\n" + "acctNo = " + acctNo + "\n" + "balance = " + balance + "\n" + "overdraft = " + overdraft + "\n"); } }

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

165

AccountException.java

A.1.4

A.1.2
1 2 3 4 5 6 7

AccountListener.java

import java.lang.*; import java.util.*; public interface AccountListener extends EventListener { public void moneyDeposited(AccountEvent aEvt); public void moneyWithdrawn(AccountEvent aEvt); }

A.1.3
1 2 3 4 5 6 7 8 9 10 11 12 13

AccountEvent.java

import java.lang.*; import java.util.*; public class AccountEvent extends EventObject { private float amount; public AccountEvent(Object src, float amt) { super(src); amount = amt; } public float getAmount() { return (amount); } }

A.1.4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

AccountException.java

import java.lang.*; import java.util.*; public class AccountException extends Exception { private final String name; private final int acctNo; private final float balance; private final float withdrawAmount; private final Date date; public AccountException(String exName, Account acct, float amt) { super(exName); name = acct.getName(); acctNo = acct.getAccountNumber(); balance = acct.getBalance(); withdrawAmount = amt; date = new Date(); } public String getName() { return (name); } public int getAccountNumber() { return (acctNo); } public float getBalance() {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

166

FixedDepositAccount.java

A.2.1

25 26 27 28 29 30 31 32 33

return (balance); } public float getWithdrawAmount() { return (withdrawAmount); } public Date getExceptionDate() { return (date); } }

A.1.5
1 2 3 4

Interest.java

import java.lang.*; public interface Interest { public float calculate(float amt); }

A.2

Subclass and Implementations

The following are classes that either subclass or implemnts one of the previous class or interface.

A.2.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

FixedDepositAccount.java

import java.lang.*; import java.util.*; public class FixedDepositAccount extends Account { private Date term; private float interest; public FixedDepositAccount(String n, int acct) { super(n, acct); term = null; interest = -1F; } public FixedDepositAccount(String n) { this(n, Math.abs((new Random()).nextInt())); } public void setTerm(Date t) { if (term == null) term = t; } public Date getTerm() { return (term); } public void setInterest(float i) { if (interest == -1F) interest = i; } public float getInterest() {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

167

TightRate.java

A.2.4

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

return(interest); } public void withdrawal(float amt) throws OverdrawnException, ErroneousAmountException { Date today = new Date(); if (today.after(term)) super.withdrawal(amt); } public void withdrawal() throws OverdrawnException, ErroneousAmountException { withdrawal(getBalance()); } public void printAccount() { System.out.println("Account name = " + getName()); System.out.println("Amount deposited = $" + getBalance()); System.out.println("Mature on = " + getTerm()); System.out.println("Interest rate = " + getInterest() + "%"); } }

A.2.2
1 2 3 4 5 6 7 8 9 10 11

SavingAccount.java

import java.lang.*; public class SavingAccount extends Account { public SavingAccount(String n, int acct) { super(n, acct); } public void printAccount() { System.out.println("Account name = " + getName()); System.out.println("Current balance = $" + getBalance()); } }

A.2.3
1 2 3 4 5 6 7

GenerousRate.java

import java.lang.*; public class GenerousRate implements Interest { public float calculate(float amt) { return (amt * 0.5F); } }

A.2.4
1 2 3 4

TightRate.java

import java.lang.*; public class TightRate implements Interest { public float calculate(float amt) {
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

168

AccountServer.java

A.3.1

5 6 7

return (amt * 0.01F); } }

A.2.5
1 2 3 4 5 6 7 8

ErroneousAmountException.java

import java.lang.*; public class ErroneousAmountException extends AccountException { public ErroneousAmountException(String exName, Account acct , float amt) { super(exName, acct, amt); } }

A.2.6
1 2 3 4 5 6 7

OverDrawnException.java

import java.lang.*; public class OverdrawnException extends AccountException { public OverdrawnException(String exName, Account acct, float amt) { super(exName, acct, amt); } }

A.3

Account Server

The following list the account server classes.

A.3.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

AccountServer.java

import java.lang.*; import java.io.*; import java.net.*; import java.util.*; public class AccountServer extends ServerSocket { public static final int PORT = 12345; private Hashtable checkout; private Counter count; public AccountServer(int port) throws IOException { super(port); checkout = new Hashtable(); count = new Counter(5); } public void run() { System.out.println("AccountServer started...");

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

169

ConnectionHandler.java

A.3.2

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

while (true) { try { Thread thr = new Thread(new ConnectionHandler(accept() , checkout, count)); thr.start(); } catch (Exception e) { System.out.println(e.getMessage()); } } } public static void main(String[] args) throws Exception { AccountServer server = new AccountServer(PORT); server.run(); } }

A.3.2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

ConnectionHandler.java

import java.lang.*; import java.io.*; import java.net.*; import java.util.*; public class ConnectionHandler implements Runnable { private ObjectInputStream ois; private ObjectOutputStream oos; private Socket clnt; private Hashtable checkout; private Counter count; public ConnectionHandler(Socket sock, Hashtable table, Counter cnt) throws IOException { clnt = sock; checkout = table; count = cnt; } public void run() { count.add(); try { ois = new ObjectInputStream(clnt.getInputStream()); oos = new ObjectOutputStream(clnt.getOutputStream()); AccountProtocol acctProto = (AccountProtocol)ois.readObject(); try { switch (acctProto.getCommand()) { case AccountProtocol.GET: handleGet(oos, acctProto); break; case AccountProtocol.PUT: handlePut(oos, acctProto); break; case AccountProtocol.RELEASE: handleRelease(oos, acctProto); break;
Copyright c July 1999 Lee Chuk Munn. All rights reserved.

170

ConnectionHandler.java

A.3.2

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

case AccountProtocol.ERROR: default: } } catch (Exception e) { oos.writeObject(new AccountProtocol(AccountProtocol.ERROR, 0)); oos.flush(); } finally { ois.close(); oos.close(); clnt.close(); } } catch (Exception e) { System.out.println(e.getMessage()); } count.release(); } private void handleGet(ObjectOutputStream netOOS, AccountProtocol proto) throws Exception { AccountProtocol result; Integer key = new Integer(proto.getAccountNumber()); synchronized(checkout) { if (checkout.containsKey(key)) { result = new AccountProtocol(AccountProtocol.INUSE, 0); } else { FileInputStream fis = new FileInputStream(key + ".ser"); ObjectInputStream ois = new ObjectInputStream(fis); Account account = (Account)ois.readObject(); result = new AccountProtocol(AccountProtocol.OKAY, account); ois.close(); checkout.put(key, account); } } netOOS.writeObject(result); netOOS.flush(); } private void handlePut(ObjectOutputStream netOOS, AccountProtocol proto) throws Exception { synchronized(checkout) { FileOutputStream fos = new FileOutputStream(proto.getAccountNumber() + ".ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(proto.getAccount()); oos.flush(); oos.close(); handleRelease(netOOS, proto); } } private void handleRelease(ObjectOutputStream netOOS, AccountProtocol proto) throws Exception { Integer key = new Integer(proto.getAccountNumber()); synchronized(checkout) { if (checkout.containsKey(key)) checkout.remove(key);

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

171

Counter.java

A.3.4

89 90 91 92 93

} netOOS.writeObject(new AccountProtocol(AccountProtocol.OKAY, 0)); netOOS.flush(); } }

A.3.3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

AccountProtocol.java

import java.lang.*; import java.io.*; public class AccountProtocol implements Serializable { public static final int ERROR = -1; public static final int GET = 0; public static final int PUT = 1; public static final int OKAY = 2; public static final int INUSE = 3; public static final int RELEASE = 4; private final int cmd; private final int accountId; private final Account account; public AccountProtocol(int c, int a) { cmd = c; accountId = a; account = null; } public AccountProtocol(int c, Account a) { cmd = c; accountId = a.getAccountNumber(); account = a; } public int getCommand() { return (cmd); } public int getAccountNumber() { return (accountId); } public Account getAccount() { return (account); } }

A.3.4
1 2 3 4 5 6 7

Counter.java

import java.lang.*; public class Counter extends Object { private final int maximum; private int current; public Counter(int max) { maximum = max;

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

172

AccountServerAPI.java

A.3.6

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

current = 0; } public synchronized void add() { if (current == maximum) while (true) try { wait(); break; } catch (InterruptedException e) { } current++; } public synchronized void release() { current = (--current < 0)? 0: current; notify(); } public int getCount() { return (maximum); } }

A.3.5
1 2 3 4 5 6 7 8 9 10

Counter.java

import java.lang.*; public class APIException extends Exception { public APIException() { super(); } public APIException(String msg) { super(msg); } }

A.3.6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

AccountServerAPI.java

import java.lang.*; import java.io.*; import java.net.*; public class AccountServerAPI { private Socket toServer = null; private String hostname; private ObjectInputStream ois; private ObjectOutputStream oos; private boolean inuse; public AccountServerAPI(String hn) { hostname = hn; inuse = false; } public Account getAccount(int acctId) throws APIException { AccountProtocol result = write(

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

173

AccountServerAPI.java

A.3.6

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

new AccountProtocol(AccountProtocol.GET, acctId)); if (result.getCommand() == AccountProtocol.OKAY) return (result.getAccount()); else return (null); } public Account putAccount(Account acct) throws APIException { AccountProtocol result = write( new AccountProtocol(AccountProtocol.PUT, acct)); if (result.getCommand() == AccountProtocol.OKAY) return (acct); else return (null); } public void releaseAccount(int acctId) throws APIException { write(new AccountProtocol(AccountProtocol.RELEASE, acctId)); } public AccountProtocol write(AccountProtocol proto) throws APIException { AccountProtocol result = null; connect(); try { oos.writeObject(proto); oos.flush(); result = (AccountProtocol)ois.readObject(); inuse = AccountProtocol.INUSE == result.getCommand(); } catch (Exception e) { close(); throw new APIException(e.getMessage()); } try { close(); } finally { return (result); } } public boolean isInUse() { return (inuse); } private void connect() throws APIException { try { toServer = new Socket(hostname, AccountServer.PORT); oos = new ObjectOutputStream(toServer.getOutputStream()); ois = new ObjectInputStream(toServer.getInputStream()); } catch (UnknownHostException e) { throw new APIException(e.getMessage()); } catch (IOException e) { throw new APIException(e.getMessage()); } } private void close()

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

174

AccountServerAPI.java

A.3.6

71 72 73 74 75 76 77 78 79 80

throws APIException { try { oos.close(); ois.close(); toServer.close(); } catch (IOException e) { throw new APIException(e.getMessage()); } } }

Copyright c July 1999 Lee Chuk Munn. All rights reserved.

175

Vous aimerez peut-être aussi