100% found this document useful (1 vote)
332 views664 pages

Jia Object Oriented Software Development Using Java Principles Patterns and Frameworks 2nbsped 0201737337

Uploaded by

Yunus Güneş
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
100% found this document useful (1 vote)
332 views664 pages

Jia Object Oriented Software Development Using Java Principles Patterns and Frameworks 2nbsped 0201737337

Uploaded by

Yunus Güneş
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 664

Obje_ct-O_rie_11te____ __.

_eRI_NcLeLEs__ _ _

Software Development PATTERNS, AND


U_sjng_Jaya:_ ME_W_QRJ{s_ _

Xiaoping Jia
DePaul University

~
.
Addison
WPsl<•y

Uo1Jto11 San Francisco New York


London Toronto Sydney Tokyo Singapore Madrid
MexJco City Munich Purlll Cnpc Town Hong Kong Montreal
Senior Acquisitions Editor Maite Suarez-Rivas
Project Editor Katherine Harutunian
Senior Production Supervisor Juliet Silveri
Production Services P. M. Gordon Associates, Inc.
Composition and Text Illustration Windfall Software, using ZzTe)(
Cover Design Supervisor Gina Hagen Kolenda
Cover Designer Jean Wilcox
Executive Marketing Manager Michael Hirsch
Print Buyer Caroline Fell

Cover image © 2002 by PhotoDisc.

Acces the latest information about Addison-Wesley titles from our World Wide Web site:
www.aw.com/cs

Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in this book, and Addison-Wesley
was aware of a trademark claim, the designations have been printed in initial caps or all caps.

Toe programs and applications presented in this book have been included for their instructional
value. They have been tested with care, but are not guaranteed for any particular purpose. The
publisher does not offer any warranties or representations, nor does it accept any liabilities
with respect to the programs or applications.

Credits: Figure 1.2: Kruchten, Rational Unified Process 2nd ed., Fig. 2.2 (p. 23), © 2000
Addi.son Wesley Longman Inc. Reprinted by permission of Pearson Education, Inc. Figure
3.1: Riggs et al, Programming Wireless Devices w/Java 2 Platform, Micro Edition, Fig. 2.1
(p. 8), © 2001 Sun Microsystems Inc. Reprinted by permission of Pearson Education, Inc.

Library of Congress Cataloging-in-Publication Data


Jia, Xiaoping.
Object-oriented software development using Java : principles, patterns, and frameworks
/ Xiaoping Jia-2nd ed.
p. cm.
lnclude-s bibliographical references and index.
lSB 0-201-73733-7
1. Object-oriented programming (Computer science) 2. Computer
softwar~Development. 3 . Java (Computer program language) I. Title.
QA76.64 J53 2003
005.13'3--0c21
2002032639

For information on obtaining permission for the use of material from this work, please submit
a wrinen request LO Pearson Education, Inc., Rights and Contracts Department, 75 Arlington
SL, Suite 300, Boston, MA 02116 or fax. your request to 617-848-7047.

Copyright © 2003 by Pearson Education, Inc.

All rights reserved. No part of this publication may be reproduced, stored in a retrieval system,
or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording,
or otherwise, without the prior written permission of the publisher. Printed in the United States
of America.
2 3 4 5 6 7 8 9 10- HT-05 04 03
To Ai-Ling and Robin
B•,ZiiiiiH

Preface xv

CHAPTER 1 Object-Oriented Software Development 1


1.1 The Challenges of Software Development 2
1.2 An Engineering Perspective 4
1.2.1 Software Development Activities 4
1.2.2 Software Development Processes 5
1.2.3 Desirable Qualities of Software Systems 6
1.2.4 Is Software Development an Engineering Process? 8
1.3 Object Orientation 9
1.3.1 Modeling the Real World 9
1.3.2 Evolution of Programming Models 10
1.3.3 A Brief History 10
1.4 Iterative Development Processes 11
1.4.1 Object-Oriented Development Activities 12
1.4.2 Rational Unified Process 13
1.4.3 Extreme Programming 15
Chapter Summary 16
Further Readings 17
Exercises 18

CHAPTER 2 Object-Oriented Modeling Using UML


2.1 Principles and Concept 19
2.1.1 Object and Cla e 20
2.1 .2 Principle 26
2.2 Modeling Relation hip and Structure 29
2.2. l Inheritance 29
2.2.2 A ociation 32
2.2.3 Aggregation and Compo ition 34
2.2.4 Dependency 35
2.3 Modeling Dynanuc Behavior 36
viii • Contents

2.3.1 Sequence Diagram 36


2.3.2 State Diagram 37
2.4 Modeling Requirements with Use Cases 41
2.4.1 Tem1s and Concepts 41
2.4.2 Use Case Diagrams 42
2.5 Case Study: An E-Bookstore 44
2.5.1 Conceptualization 44
2.5.2 Use Cases 44
2.5.3 Object Models 46
Chapter Summary 51
Further Readings 52
Exercises 52

CHAPTER 3 Introduction to Java 55


3.1 An Overview of the Java 2 Platform 56
3.2 The Java Run-Time Architecture 59
3.2.1 Program Execution Models 60
3.2.2 Java Vrrtual Machine 61
3.3 Getting Started with Java 65
3.3. l A Simple Java Application 65
3.3.2 A Java Applet 67
Common Problems and Solutions 72
Chapter Summary 73
Further Readings 73
Exercises 74

CHAPTER 4 Elements of Java 75


4.1 Lexical Elements 76
4.1.1 Character Set 76
4.1.2 Identifiers 77
4.1.3 Primitive Types and Literals 77
4.1.4 Operators and Expressions 80
4.2 Variables and Types 87
4.2.J Variable Declarations 88
4.2.2 Type Compatibility and Conversion 88
4.2.3 Reference Types 89
4.2.4 Arrays 91
4.3 Statements 93
4.3.) Expres ion Statements 94
4.3.2 Statement Blocks 94
4.3.3 Local Variable Declarations 95
4.3.4 The return Statement 95
4.3.5 Selection Statements 96
Contents ■ ix

4.3.6 Loop Statements 96


4.3.7 The break and continue Statements 99
4.4 Class Declarations JO 1
4.4. l Syntax of Class Declarations 101
4.4.2 Creating and Initializing Objects 104
4.4.3 Accessing Fields and Methods I 06
4.4.4 Method Invocation and Parameter Passing 107
4.4.5 Class (Static) Fields and Methods 110
4.4.6 Object Reference this 114
4.4.7 Interfaces and Abstract Classes 117
4.4.8 Strings 118
4.4.9 Wrapper Classes 128
4.5 Packages 134
4.5.l Using Packages 135
4.5.2 Partitioning the Name Space 136
4.5.3 Packages and the Directory Structure 136
4.5.4 Organization of the Java Class Library 138
4.6 Exceptions 139
4.6.1 Sources of Exceptions 140
4.6.2 Hierarchy of Exceptions 140
4.6.3 Throwing Exceptions 143
4.6.4 Catching and Handling Exceptions 144
4.7 A Simple Animation Applet 148
Chapter Summary 155
Exercise 156
Project 157

CHAPTER 5 Classes and Inheritance 159


5.1 Overloading Methods and Corrtructor 159
5.2 Extending Cla e 163
5.2.1 Constructor· of Extended C la e 164
5.2.2 Subtype and Polymorphi m 165
5.2.3 Overriding Method 171
5.3 Extending and Implementing Interface 176
5.3. l Subtype RevLited 177
5.3.2 Single Ver u Multiple Inheritance 179
5.3.3 Name Colli ' ion among Interface 183
5.3.4 Marker Interfaces 184
5.4 Hidino0 Field · and Class Mel.hod · 14
5.5 Application - Animation Applets l 6
5.5. l Parameters of ppkts l 6
5.5.- An Idiom for Animation Applets 188
5.5.3 Double-Buffered Animation 193
5.5.4 Reading File · in Applet · 200
X ■ Contents

Common Problems and Solutions 202


Chapter Summary 202
Exercises 204
Projects 204

CHAPTER 6 From Building Blocks to Projects 207


6.1 Design and Implementation of Classes 207
6.1 .1 Public and Helper Classes 207
6.1.2 Class Members 209
6.1.3 Design Guidelines 209
6.1.4 Documenting the Source Code 214
6.2 Contracts and Invariants 216
6.2. l Contracts of Methods 216
6.2.2 Invaraints of Classes 222
6.2.3 Assertions 224
6.2.4 Design by Contract 226
6.3 The Canonical Form of Classes 227
6.3.1 No-Argument Constructor 228
6.3.2 Object Equality 228
6.3.3 Hash Code of Objects 230
6.3.4 Cloning Objects 231
63.5 String Representation of Objects 234
6.3.6 Serialization 234
6.4 Unit Testing 235
6.4.1 Simple Unit Testing 235
6.4.2 JUnit-A Unit-Testing Tool 239
6.4.3 Testing Coverage Criteria 241
6.5 Project Build 243
6.5.1 Ant-A Build Tool 243
Chapter Summary 246
Further Readings 247
Exercises 247

CHAPTER 7 Design by Abstraction 249


7.1 Design Patterns 249
7. l . l Design Pattern: Singleton 251
7.2 De igning Generic Components 252
7 .2.1 Refactoring 252
7 .2.2 Design Pattern: Template Method 266
7 .2.3 Generalizing 271
7.2.4 Design Pattern: Strategy 275
7.3 Abstract Coupling 276
7.3.1 Enumerating Elements 278
Contents • Xi

7.3.2 Design Pattern: Iterator 283


7.4 Design Case Study-Animation of Sorting Algorithms 284
7.4.l The Initial Implementation 285
7.4.2 Separating Algorithms 290
7.4.3 Design Pattern: Factory 296
7.4.4 Separating Display Strategies 296
Chapter Summary 302
Further Readings 304
Exercises 304
Project 304

CHAPTER 8 Object-Oriented Application Frameworks 305


8.1 Application Frameworks 305
8.1.1 Characteristics 306
8.1.2 Design Requirements 307
8.1.3 Specific Frameworks Considered 308
8.2 The Collections Framework 308
8.2.1 Abstract Collections 309
8.2.2 Interfaces of Collections 310
8.2.3 Implementations of Collections 315
8.2.4 Iterators of Collections 319
8.2.5 Ordering and Sorting 324
8.3 The Graphical User Interface Framework-AWT and Swing 333
8.3.1 The GUI Components 333
8.3.2 Design Pattern: Composite 336
8.3.3 Layout Managers 338
8.3.4 Handling Event 348
8.3.5 Frame and Dialogs 359
8.4 The Input/Output Framework 366
8.4.1 Byte Stream 367
8.4.2 Design Pattern: Decorator 380
8.4.3 Character Streams 382
8.4.4 Random Acces File 389
Chapter Summary 392
Further Reading 394
Exerci e 394
Projects 395

CHAPTER 9 Design Case Study: A Drawing Pad 397


9.1 Planning 97
9.2 Iteration l : A Simple Scribble Pad 398
9.2. l Th Scribbling Canvas and It Li tener 399
9.2.2 The Application 40-
Xii • Contents

9.3 Iteration 2: Menus, Options, and Files 403


9.3.l Strokes 403
9.3.2 The Scribble Canvas 405
9.3.3 The Canvas Listener 408
9.3.4 The Application 409
9.3.5 Choosing Colors 416
9.4 Iteration 3: Refactoring 421
9.4.l The Shapes 421
9.4.2 The Tools 424
9.4.3 Extending Components 428
9.5 Iteration 4: Adding Shapes and Tools 432
9.5.1 The Shapes 433
9.5.2 The Toolkit 436
9.5.3 Design Pattern: State 438
9.5.4 A Concrete Tool-TwoEndsTool 439
9.5.5 Extending Components 442
9.5.6 Design Pattern: Factory Method 447
9.6 Iteration 5: More Drawing Tools 448
9 .6.1 Filled Shapes 448
9.6.2 Drawing Filled Shapes 449
9.6.3 The Application 452
9.7 Iteration 6: The Text Tool 453
9.7.1 The Text Shape 454
9 .7 .2 The Keyboard Input Tool 455
9.7.3 The Font Option Menu 459
Chapter Summary 462
Further Readings 462
Project 463

CHAPTER 1O More Design Patterns 465


IO.I Type-Safe Enumeration Types 465
10.1.1 A Simple Maze Game 465
10.1 .2 Enumeration Types 466
I0.1.3 Unordered Ty~Safe Enumeration Idiom 468
10.1.4 Ordered Type-Safe Enumeration Idiom 469
10.2 Creational Design Patterns 470
J0.2.1 A Simple Design of the Maze Game 470
J0.2.2 Design Paltem.: Abstract Factory 484
10.2.3 Design Pattern: Factory Method 491
I0.2.4 Design Pattern; Prototype 495
10.2.5 Design Paltem: Builder 502
10.3 Behavioral Patterns 507
10.3.1 Design Pallern: Command 507
Contents ■ xiii

10.3.2 Supporting Undo 509


10.4 Structural Patterns 513
I 0.4.1 Design Pattern: Adapter 513
I0.4.2 Design Pattern: Composite 531
Chapter Summary 544
Further Readings 545

CHAPTER 11 Concurrent Programming


547
11.1 Threads 547
11.1.1 Creation of Threads 548
11.1.2 Controlling Threads 553
11.2 Thread Safety and Liveness 556
11.2.1 Synchronization 557
11.2.2 Cooperation Among Threads 564
11 .2.3 Liveness Failures 569
11.3 Design Case Study-Tic-Tac-Toe Game 571
11.3.1 The Game Board 572
11 .3.2 The Game 577
11.3.3 The Players 579
11.3.4 Idiom: Talcing Turns 582
Chapter Summary 583
Further Reading 584
Exercises 584
Projects 585

CHAPTER 12 Distributed Computing 587


12.1 Socket-Based Communication 588
12.1 .1 Server and Client Sockets 5 8
12.1 .2 Server and Client U ing Socket 590
12.1.3 De ign Ca e Study-Stock Quote I 600
12.2 Remote Method Invocation 61-+
12.2.1 The Architecture 614
12.2.2 U ing RMI 616
12.2.3 De ign Ca e Stud -Stock Quote lI 620
12.3 Java Databa e Connecti ity 628
12.4 Common Object Reque, t Broker Archite ture 640
Chapter Summary 641
Further Reading, 642
Exercise, 642
Projects 643
XiV • Contents

APPENDIX A Summary of the APPLET Tag 645

APPENDIX B Summary of Documentation Tags 647

APPENDIX C Summary of Java Naming Conventions 649

Glossary 653

References 663

Index 667
Object-Oriented Software
Development

CHAPTER OVERVIEW
In this chapter, we provide an overview of object-oriented software development. We
start w ith a general d iscussion of software development processes and the desi@ble
qualities of software products. Next, we discuss what makes software development
difficult and the difference between software engineering and other more established
engineering practices. Then we take a close look at ite@tive software development
processes, including the Rational Unified Process (RUP) and Extreme Programming (XP).

Whether you are a novice or experienced computer u er or programmer, you would


agree that we are living in an ex iting period of time with a con tant flow of inno-
vation in both computer hard, are and oftware. The oftware indu try wa one of
the mo t ucce ful indu ·trie' during the p:l'l t, o decade . ot only wa it growth
in market value exceptional. but it was al o able to deliver technologically advanced
and innovative products at an unrelenting pa e. Today, computer software ha become
prevalent in every a pect of life. ocietie are becoming more and more dependent on
oftwa.re y tern • from autopilot sy tern , of jetliners to computerized trading system
of tock market to per-anal organizer' on palm-top computer . However, oftware i

1
2 ■ Object-Oriented Software Development

expensive. The cost of purchasing, developing, maintaining, and �pgrading so�tware


systems has become the largest single expe� diture f or many bu messes, and it con­
tinues to increase. This continuing increase m software costs contrasts sharply to the
dramatic decrease in hardware costs and the equally dramatic increase in hardware
perfonnance and capabilities. The object-oriented software develop� ent methodol­
ogy aims to significantly improve current software �evelopm_ent practice. It ha _ been
well received and widely adopted by the software mdustry m recent years. It 1s the
methodology of choice in today's software development practices.

1.1 THE CHALLENGES OF SOFTWARE DEVELOPMENT


During the past two decades, the software industry has produced many technolog­
ically advanced, innovative, and commercially successful products. However, the
process of creating these successful products (i.e., software development) i a dif­
ficult, time-consuming, and costly endeavor. For example, the initial version of the
Microsoft Windows NT operating system consisted of 6 million line of code, co t
$150 million to develop, and took 200 developers, testers, and technical writers 5 years
to complete. The struggle to create Windows NT is vividly presented in Show-Stopper
[Zachary, 1994). Furthermore, software systems tend to be "buggy"; that i , they con­
tain glitches that hamper or even disrupt their normal function or performance. Minor
glitches can be merely annoying, but serious ones can be disastrous.

• On January 15, 1990, the AT&T long-distance telephone network broke down,
interrupting nationwide long-distance telephone services in the United State f or
more than 8 hours. An ill-placed break statement in the switching software,
written in the C language, was to blame for the breakdown.
• On June 4, 1996, the maiden flight of the new and improved Ariane 5 commu­
nication satellite launcher developed by the European Space Agency exploded
37 seconds after liftoff. An incorrectly handled software exception resulting from
converting a 64-bit floating point to a 16-bit signed integer caused the disaster.
• On June 8, 200 I, a software problem in the new trading software installed
overnight for the New York Stock Exchange caused failures in trading on half of
the floor of the exchange and forced the NYSE to shut down the entire trading
floor for more than an hour.

Although uch cata trophic failures are rare, minor glitches are common in almost all
oftware. In other words, buggy software is the nom1.
However, the state of oftware development practice i far from the "software
crisis" many have proclaimed in the past. Advances in many aspects of software de­
velopment methodologies and software engineering proce ses have made it po ible
I? develop many large-scale software systems that perform as expected most of the
time. We are not capable of delivering nor required to deliver 100% reliable software
The question i , How good i good enough?
1.1 The Challenges of Software Development ■ 3

Software development is labor intensive. A majority of software development


projects are over budget and behind schedule. The reality of software development
remains that software is very expensive and often unreliable. Despite the phenomenal
success of the software industry in technological advance and innovation, it still face
challenges in delivering high-quality software on time and under budget. The object-
oriented software development methodology is one of the solutions the software
industry is embracing now, hoping to improve the reliability of software system and
the cost-effectiveness of software development.
In order to improve software development practice, let u first examine some
of the underlying causes of the difficulties of software development: complexity,
longevity and evolution, and high user expectations.

Complexity The software ystem being developed today are often very large and
complex. Complexity is dictated by the problems the sy tem are intended to solve
and the services they are intended to provide. From the engineering perspective, both
requirements are often beyond the control of software developers. The complexity
involved in a large-scale software system is so great that no individual can comprehend
every detail of the system. To build such a complex sy tem, it mu t be broken down
into manageable parts and requires the cooperative effo11s of a team of developer
rather than the efforts of an individual. Methodologie , techniques, and tool that
work well for small systems developed by individuals usually are not effective for
large systems developed by tean1s.

Longevity and Evolution Because of economic, political, and other constraint ,


software systems are often in service for very long periods of time. Today, some
legacy systems have been operating for more than 20 year . During their lifetimes,
software systems must constantly evolve to accommodate changes in u er ' need
and environments. However, making changes to software system (i.e., maintenance)
is a difficult task. Furthermore, maintenance not only is co tly and Lime-consuming,
but also usually degrades the quality of the ystem being maintained. On average,
the maintenance cost of a software system over it li fetime i far greater than its initial
development cost.

High User Expectations In the past, whe n computer were mainly u ed in univer-
sitie , research institutions, and large corporation , the majority of software ystem
users were scientists and engineer who had the technical kill 10 handle glitche
they might encounter while using the sy tern . Today, computer are used in home ,
school , and businesses of all size , and are used for plea ure a well a for work.
The majority of today's software u er are nontechnical, ordinary people. Computer
oftware products are con idered more and more like con umer producl and are ex-
pected to perfom1 with the a me depe ndability as hou ehold appliances. Occa ional
glitche that once were con idered acceptable are now intolerable. Software Y tern
are expected to be " bug-free ," but uch perfection i next to impossible.

The challenge faced by oftware development are to find effective olution to


control the complexicie of oft ware y tern , co manage the longevity and e olution of
software y tern , and to deliver oftware y rems with higher reliability and u ability.
4 ■ Object-Oriented Software Development

I>

AN ENGINEERING PERSPECTIVE
..i
The term software engineering was coined al a NATO workshop in 1968. It repre­
ented an aspiration to m ch e practice of software development on a solid scientific
foundation and to attain the level of reliability and productivity associated with well­
e tabli hed engineering di cipline , uch as civil and mechanical engineering.
Software engineering is �jneering discipline concerned with al�
developing and delivering high-quality and use�i:�ecti-ve-manner.
Software engineering defines the various activities in the software development and
the products, or deliverables, a sociated with these activities. Software engineering
also define the sofnvare development processes, which define the order for carrying
out the development activities and the criteria for the deliverables of the activities.

1.2.1 Software Development Activities


The most important product of software development is obviously the software. Most
people equate software to computer programs, or the source code. However, the source
code is onJy a part of the products produced in software development. In software
engineering, the term software is defined in a broader sense. It encompasses the source
code as well as all the associated documentation produced during the various activities
in the sofu are development process. The documentation of software may include
requirements specifications, architecture and design documents, configuration data,
installation and user manuals, and so on. Software development usually involves the
following activities:

Requirements Analysis The goal of requirements analysis is to establish the func­


tions, services, and constraints of the software to be developed. For custom software,
that is, oftware developed for one specific customer, this is usually accomplished by
consultation with system users. For commercial ("shrink-wrapped") software, that
is, software intended to be marketed and sold to any customers who are willing to
buy it, this goal is usually accomplished by market analysis of perceived needs of
potential customers and/or feedback from existing customers. There are two cate­
gories of requirements:fimctiona/ requirements, which are concerned with functions
and ervices to be performed by the software, and nonfunctional requirements, which
are concerned with the constraints under which the software must operate, such as
response time, memory consumption, and user friendliness. The main concern of
requirements analysi is lo define the problem to be solved. The requirements are
documented in requirements specifications or system specifications.

Design The goal of design is to construct a solution to the problem by establishing


an overall architecture of the software, by partitioning the software into components
or sub ystem , and by identifying the relationships and dependencies among them.
These de ign activities can often be further divided into systern design, which is
primarily concerned with rhe decomposition of complex problems into manageable
1.2 An Engineering Perspective ■ 5

components, and detail design, which is primarily concerned with the solutions to
each component. Software designs are often documented using various diagrams.

Implementation and Unit Testing Implementation is the realization of the soft-


ware design in programs, that is, source code. Each component is implemented
separately. Unit testing is carried out to test each individual component, or unit, inde-
pendently. The goal of unit testing is to ensure that each unit functions properly with
respect to its specification before the units are integrated.

Integration and System Testing The individual components or units are integrated
and tested as a whole to ensure that the entire software system functions properly with
respect to its specification.

Maintenance Maintenance involves a variety of activities after the delivery of


software systems. These activities include correcting bugs, improving performance,
enhancing functions or services, and adapting to new environments. Software main-
tenance continues as long as the software is in service. It is usually the longest and
most costly activity in the software life cycle.

1.2.2 Software Development Processes

The most well-known software development process is the ·<waterfall" model illus-
trated in Figure 1.1, which has been the de facto standard of the software development
process. In the waterfall model, the development activities are carried out in succes-
sive phases linearly: requirements analysis, design, implementation and unit te ting,
integration and system testing, and maintenance. A phase i the pan of time between
two major milestones of the process in which a well-defined et of objective are met,
artifacts are completed, and decisions are made whether to move into the next phase.
In principle, the deliverable of each phase must be approved (" igned off') before the

Figure 1.1
Requirements

l
analysis
The waterfall model
of software devel- +
I
I
opment. '---- - Design

+
,' _____
I
i
Implementation
and unit testing

+
I
I
i
Integration and
·----- system testing

+
I
,_____
I
Maintenance
as

6 • Object-Oriented Software Development

next phase can begin. The rationale is that changes to the requirements specification
cost much less to implement in the requirements analysis than in the later phases. The
later the phase in which a change to the requirements is introduced, the more it costs.
So the goal is to minimize changes after the documents are delivered. This requires
that the tasks of each phase be completed thoroughly, and that the deliverables of each
phase be frozen once they are delivered and approved.
However, the waterfall model is not realistic. It is very common that changes
occur during every phase of the development process. The changes may come from a
number of sources: errors or faults of the specification and design may be discovered
during implementation; assumptions made in design may be proven false during
y tern testing; some features requested by the customers may be proven to be too
low, excessive in resource consumption, or infeasible during system testing; and
user needs and requirements may have changed after the requirements analysis is
completed. Therefore, in practice, it is often necessary to have several iterations of
the phase in the waterfall model. However, one of the major shortcomings of the
waterfall model is that it does not facilitate such iterations.
There are several alternative software development processes that are designed
to carry out the software development activities in an iterative fashion. The itera-
tive software development processes are becoming popular and gaining acceptance
in practice, partly because of the wide acceptance of object-oriented development
methodologies, which are especially suited to iterative development. We will discuss
two of the common iterative development processes in Section 1.4.

1.2.3 Desirable Qualities of Software Systems

Let's now tum our attention to the products of software development-software


ystems. The following are the most desirable qualities of software systems:

Usefulness: Software systems should adequately address the needs of their


intended users in solving problems and providing services.
Trmeliness: Software systems should be completed and shipped in a timely
manner. Otherwise, they may be less useful or even useless owing to changes
in users' needs and operating environments. This factor is also important in
the software vendor's ability to remain competitive.
Reliability: Software system should perform as expected by users in terms of
the correctness of the functions being performed, the availability of service ,
and an acceptable level of failures.
Maintainability: Software ystems should be easily maintainable; that is, it
should be possible to make corrections, adaptations, and extensions without
undue costs.
Reusability: Components of software systems should not be designed as ad hoc
olutions to specific problems in specific contexts; rather they should be
designed as general solutions to a class of problems in different contex t
Such general components can be adapted and reused many times.
1.2 An Engineering Perspective
• 7

User f .riendliness: Software systems should provide user-fn·endl Y mte · rfaces


tailored to the capabilities and the background of the intended
, ·1· users to
,aci Itate easy use and access to the full extent of the systems' capabilities.
Efficiency: Software systems should not make wasteful use of system
. . . . resources,
mcludmg processing time, memory, and disk space.

N?t all of these desir~ble qualities are attainable at the same time, nor are they of
equal importance. A crucial part of software development is dealing with the trade.-
o~s amo?g these different qualities to achieve a reasonable balance. Obviously, the
obJect-on:nted developm~ t approach cannot directly improve aJI these qualities. It
~cuse~ primarily O!!_improving_the_!!laintainability and reusabj lity of software sys- ,
terns.Maintainability should be the focuo fife development process for three main
reasons. First, for software systems with long lifetimes, maintenance co ts will far ex-
ceed initial development costs. It is imprudent to compromise maintainability because
any savings that may result ini tial ly will undoubtedly be dwarfed by maintenance cost
penalties over the long run. Second, current development technology does not yield
high reliability in the initial release of software sy tern . Reliability i u ually attained
through repeated corrections during the development pha e and throughout the life-
times of software system . Software system reliability can be everely hampered by
poor maintainabi lity. Third, high maintainability require flexibility in the de ign and
implementation of software systems. Such flexibility facilitate the kind of incremen-
tal development that enhances reliability, u efulne s, and u er friendline . a well as
the ability to contain costs.
Several factors contribute to the maintainability of oftware y tern :

Flexibility: Flexibility mean that variou aspect of ofrware y tern. hould


be easily changeable. The impact of the change can be confi ned to mall
region , and the correctnes of the change can be verified locally. that L, by
only examining the small affected region rather than examining the entire
software.
Simplicity: Human beings are fal lible. It is impo ible for people to avoid making
mi take . However, when thing are imple. people are much le s error-
prone, and making ure that thing are working properly i' much ea ier. If
there are error , they become more obviou . and correcting them i ea. ier.
Complex software y terns can be implified by the effective u e of the
divide-and-conquer technique.
Readability: A prerequi ite fo r maintainability i. readability, or under tand-
ability, because ·oftware ·y tems mu t be under tood before they can be
modified. Readability depends on the clarity and the impli ity of the de ign
and the program code, the clarity :rnd completene of the accompanying
documentation, and a ' imp le and consi tent style of de, ign, implementation,
and documentation.

These fac tors me the fo us of our discu. sion of methods and techniques in later
chapters.
8 ■ Object-Oriented Software Development

1 .2.4 Is Software Development an Engineering Process?


After more than 30 year since its inception, there is still little consensus on the pr~cise
definition of software engineering, and even the legitimacy of using software_ engmeer
as a professional title is till being debated. As Shaw and Garlan [ 1996] pomted out,
software engineering is a label that
refers to a collection of management processes, software tooling, and design activities
for oftware de,·elopment. The resulting practice, however, differs significantly from
the practice of older form of engineering.

A clo e examination of traditional, well-established engineering disciplines reveals


that e eral e ential characteristics of those practices are absent in today's software
development practice.

Analysis of Designs
Over the centuries, craftsmanship clearly has proved capable of building magnificent
tructures. such as the Egyptian pyramids, Roman aqueducts, and Notre Dame Cathe-
dral. However, modem engineering offers assurance, predictability, and efficiency
that craftsmanship cannot match. One of the key differences between engineering
and craftsmanship is that the success of engineering projects can be assured before-
hand through scientific analysis of their designs, whereas the success of craftsmanship
projects is attained through trial and error during current and prior construction.
Civil engineers depend on mechanics to help them predict with confidence before
construction begins that a newly designed bridge or building will stand and function
as it is supposed to. Aerospace engineers depend on aerodynamics and simulation to
help them predict with confidence before it is built that a newly designed airplane
will fly.
In contrast, software developers largely depend on testing and debugging (i.e.,
trial and error) to establish confidence in their products. Software development is
like building modern skyscrapers with craftsmanship, with the success of software
development projects rarely assured beforehand.

Nonrecurrence of Failures
Failures, ometimes catastrophic, also occur in well-established engineering fields.
Perhaps one of the most spectacular failures in the history of engineering was the
collapse of the Tacoma Narrows Bridge in 1940. Its design was unconventional and
innovative, and the bridge was dramatic and elegant in appearance. Careful analysis
w~s per~o~ed to en ure that the bridge would behave well under its own weight
with ant1c1pated traffic load and winds as high as 45 miles per hour. However the
~esigner did nor fore~ee that the slender ~ridge deck would act like an airplane ~ing
ma moderate crosswind of less than 40 miles per hour, which twisted the bridge apart.
A oo~ a th~ cause of the collapse _was known, 1~easures were developed to prevent
such failures in the fu1ure. Hence, rn well-established engineering fields, the same
type of failure is rarely repealed.
1.3 Object Orientation ■ 9

In software development, the same types of failures recur all the time. Few
practical measures can be taken to ensure the absence of certain types of faults in
software systems. The sad truth about software development is that no one can ensure
that the type of failure that occurred in Ariane 5 will never occur again.

Codification of Knowledge
The success of well-established engineering fields is due largely to the accumulation
and codification of knowledge and the reuse of prior solutions. Design knowledge
and solutions often are organized and presented in manuals and handbooks to make
common and routine design not only easier and faster, but also more reliable, depend-
able, and manageable. Designers often find solutions inhandbooks and then adapt and
assemble the solutions to their specific design problems. Only rarely are original and
innovative solutions needed. Usually, the codified knowledge includes what to avoid
as well as what to do.
In software development, although a lot of design knowledge and experience has
been accumulated, very little has been systematically codified. Without the benefit of
prior design solutions, each design of a software system i treated as an original.
Therefore, it is no surprise that software design is difficult, time-consuming, and
unreliable.
Thus, software development is quite different from the traditional engineering
disciplines. At best, it is an immature engineering discipline. For software develop-
ment to become a true engineering discipline, software developers must have mech-
anisms to carry out the analysis of designs, ensure nonrecurrence of knm,vn failures,
and codify design knowledge.

1.3 OBJECT ORIENTATION

1 .3.1 Modeling the Real World

The main goal of software development i to build soft\ are y tern that provide
services to people and enhance their abilitie to . olve problem in the real world. A
software system usually con i ts of two e ential component : a model, which is a
representation of a pertinent part of the real world, and an algorithm, which captures
the computation involved in manipulating or proce ing the model.

Software system
Abstraction
Model Algorithm
Interpretation
10 ■ Object-Oriented Software Development

The real world is enonnous and complex. Many of its aspects are fuzzy, unknown,
or intangible. 1n contrast, the programming models used in software systems must be
precise ~d relatively small. A model is necessarily a~ ~bstractio11 of the real world.
It capture only the essential and relevant charactenst1cs of the real wo_rld from a
particular perspective and ignores others. Models are intended to be manipulated or
proce ed. and their behaviors should mimic those of the rea~ worl_d to reflect the
cho en perspectives reasonably accurately. The results of mampulations can be fed
back to the real world through i11terpretatio11 (i.e., the assignment of meanings to the
entitie in the models) and often represent solutions to real-world problems.

1.3.2 Evolution of Programming Models

Programming languages are the main tools used by software developers to de-
scribe computer models. The evolution of progranuning languages and progranuning
methodologies is driven by the need to build more and more sophisticated and ef-
fective models. That need in turn is driven by the ever-increasing power of modern
computers and the desire to utilize this power.
One of the fundamental problems in software development is, How does someone
model the real world? The answer largely depends on the problems to be solved. One
way to look at the evolution of software development methodologies is through the
changing views of programming models.
In the 1950s and 1960s, the focus of software developers was on the algorithm. As
a result, the main concerns at that time were solving computation problems, designing
efficient algorithms, and controlling the complexity of computation. The models used
were computation-oriented models, and the decomposition of complex systems was
primarily based on control flo w.
In the 1970s and 1980s, different types of models emerged to address the com-
plexity of the data being processed. These systems were centered on data entities
and data fl ows, with computation becoming a secondary concern. The models used
were data-oriented models, and the decomposition of complex systems was primarily
based on data flow.
Object-oriented models represent a balanced view of the data and computation
aspects of software systems. Object-oriented models are composed of objects, which
contain data and the associated computations. The decomposition of complex systems
is based on the structure of objects, classes, and the relationships among them.

1.3.3 A Brief History

The origin of object-oriented software development dates back to the late 1960s. A
computer simulation language called Simula wa,; the first programming language that
included ome important features of object-oriented programming, such as class. The
fir t full -blown and perhaps the best known object-oriented programming language
was Smalltalk, developed by Xerox PARC in the I 970s. Object-oriented technology
grew tremendously during the 1980s, with the emergence of several more sophi _
1.4 Iterative Development Processes ■ 11

ti�ated object- oriented programming languages, including C++, Objective-C


, and
Eiffel. It also evolved from a programming methodolo gy to a software devel
opment
metho do lo gy that addresses the analysis, design, testing, and evolution o
f software
systems in addition to the implementation of software systems. Despite its long
history
of devel opment, o nly recently has the object- oriented devel
opment appro ach matured
and become widely accepted by the mainstream software industry. This acceptance is
largely due to significant advances in several aspects of object-oriented development
method olo gy in recent years:
■ A number of object-oriented programming languages have become mature, prac­
tical, and widely accepted by the oftware industry, including C++ and later Java.
■ A number of object-oriented analysis and modeling techniques and notations
have been developed and eventually unified in the fonn of Unified Modeling
Language. UML has been widely adopted in the industry.
■ In 1995, the Design Patterns book was published. It repre ented the first system­
atic attempt at codifying object-o riented de ign knowledge.
■ Iterative development processes have been gradually accepted in practice.
The o bject-o riented so ftware development approach repre ent a dramatic depar­
ture from co nventional s oftware development approaches. It looks at the world from
a rather different perspective.

1.4 ITERATIVE DEVELOPMENT PROCESSES

In contrast to the traditional waterfall oftware development model, Boehm [ 1988]


prop o sed the first iterative software development proce . knm\' n as the spiml model.
Booch [ 1994] proposed an iterative oft\ are development proce for object-oriented
oftware development. Booch' iterative o bject-oriented de\ ' elopment proce con­
si ts of a number of succes ive iteration . Each iteration contain the follo\ ing tep :
identifying the classes, identifying the emantic· (i.e., attribute and behaviors) o f
the c lasses, identifying the relation hip among the clas es, defining the cla inter­
face, and then implementing the clas e . Each iteration deal· with a relatively mall
increment of the y tern being de �loped. Thu the y tem i de\'. eloped �ncremen­
tally, not as a mo nolithic piece. The iterative pro e continue unul the enure Y tern
i complete. Thi proc e i , hat B h ailed the micro proce • Bo�ch al O pro ­
po ed a macro proce to er e a the conlrolling frarne·work of the mi� ro proc� ·
The macro proce co nsi t of the folio,\' ing pha e : analy. i and modeling, de ign,
implementation, and maintenance.
Boo c h' iterative development pro e • became the foundation of lhe more com-
plete and nov widely adopted obje l-orienled de elopmenl p roce_ , �own as_ the
Ratio11a/ U11 ified Pro •ess ( RUP). lt al O in pired a number of hghtwe1ghl obJeCl­
orient d iterative de elopm nt proces�e . The mo. t , ell-known o f t�e e proc�sse
L th s o- alled Exmm,e P,vgrcimmi11i: ( P). ln thi ection, we begm by taking a
12 ■ Object-Oriented Software Development

look at the activities of object-oriented development. We then discuss so~ne of t~e


common characteri tics of iterative development processes. Next, we provide a bnef
overview of both RUP and XP. We will revisit and elaborate on many of the underly-
ing principles and technique of both RUP and XP throughout the remainder of the
book.

1.4.1 Object-Oriented Development Activities

Object-oriented development processes consist of activities that are similar to those in


the waterfall software development model discussed in Section 1.2.2. Compared to the
activitie in the waterfall software development model, the activities in object-oriented
development proce es have a rather different focus and adopt a rather different set
of techniques, notations, tools, and criteria. Here are the common activities in object-
oriented development processes:

Conceptualization_· The goal of conceptualization is to establish the vision and


the core requirements of the software system to be developed. Unlike the
requirements analysis phase in the waterfall process model, the goal here
does involve establishing the complete requirements of the system.
Object-oriemed analysis and modeling: The goal of object-oriented analysis and
modeling is to build models of the system's desired behavior, using notations
such as the Unified Modeling Language (UML). A model intends to capture
the essential relevant aspects of the real world and to define the services to
be provided and/or the problems to be solved. A model is a simplification of
reality, created to better understand the system to be developed. In contrast
to the informal requirements analysis in the waterfall process, the emphasis
here is to use notations such as UML, which includes use cases and class
diagrams, and so on, to describe the models in a way that is semantically
richer and more precise compared to informal paper documents.
Object-oriented design: The goal of object-oriented design is to create an
architecture for implementation. Designs are represented in terms of objects
and classes and the relationships among them. Key concerns of an object-
oriented design include the following: (1) Does the design satisfy all the
stated requirements and constraints and provide all the desired services
adequately? (2) Is the design flexible enough to accommodate future changes
and enhancements? (3) Is the design feasible for implementation, and, if so,
can it be implemented efficiently?
lmplemenrarion: The goal of implementation is to implement the design by using
an object-oriented programming language, such as Java. Implementation in-
volves coding, unit testing, and debugging. Key concerns of implementation
include I.he e: (1 ) Is the implementation correct? (2) I the implementation
effi cient and maintainable? (3) I the implementation robust, that is, capable
of tolerating faults and recovering from failures?
1.4 Iterative Development Processes ■ 13

Maintenance: The goal of maintenance is to manage postdelivery evolution. The


primary maintenance tasks include removing bugs, enhancing functionali-
ties, and adapting the system to evolving needs and environments.
A key assumption in the iterative software development process is that changes
occur throughout the life cycle of software development. Instead of trying to minimize
or prevent changes, iterative software development processes try to facilitate and
manage changes. In iterative development processes, software systems are developed
in successive iterations. Each iteration represents a complete development cycle
for a small piece, or increment, of the entire system, from analysis and design,
to implementation and testing. The key characteristics of an iteration include the
following:

■ Each iteration is relatively small and can be completed in a relatively short period
of time.
■ Each iteration results in a release of an executable product or component, which
is a part of the final product.
The final product is developed incrementally from iteration to iteration.

1.4.2 Rational Unified Process

The Rational Unified Process (RUP) is a complete software engineering process. It


provides guidelines for carrying out every aspect of software development activities
with the goal to "ensure the production of high-quality software that meet the needs
of its end users within a predictable schedule and budget [Booch et al., 1999]." The
RUP is not one process, but a process framework that can be adapted and extended
to suit the needs of different organization and different type of project . The key
practices of the RUP are to:
■ develop software iteratively.
■ systematically elicit, organize, and manage changing requirement .
■ use component-ha ed architecn1re.
■ visually model software u ing UML.
■ continuously verify oftware quality.
■ control changes to oftware.
The empha i of the RUP i on building models rather than paper document . In
the RUP there are nine model that collectively cover all the important decision that
go into ~i ualizing, specifying. construcring, and documenting a . oftware-intensive
system [Booch et al., 1999]:
1. Business model: E tabli he an abstraction of the organization
2. Domain moclel: Establi he the context of the sy tern
3. Use case mo /el: E tablishe · the 'Y tem · functional requirements
4. Analysis model (vptimwl): E tabli hes an idea de ign
14 • Object-Oriented Software Development

5. Design model: Establishes the vocabulary of the problem and its solution
6. Process model (optional): Establishes the system's concurrency and synchro-
nization mechanisms
7. Deployment model: E tablishes the hardware topology on which the system is
executed
8. Implementation model: Establishes the parts used to assemble and release the
physical sy tem
9. Test model: Establishes the paths by which the system is validated and verified
The RUP i use case driven . Use cases defined for system requirements are the
foundation for all other development activities, including design, implementation,
and te ting. The RUP is architecture centric. The main focus of early iteration of the
development process is to produce and validate an executable architecture prototype,
which gradually evolves to become the final system in later iterations.
The process structure of the RUP can be illustrated in a two-dimensional chart
as hown in Figure 1.2. One dimension represents the time in terms of phases and
itera.tion . The other dimension represents the process work.flows. The chart shows
roughly the amount of time or attention devoted to each process work.flow during
various phases and iterations.
A process workjl.oH consists of a sequence of activities that produce a set of
anifacts, or deliverables, which can be project plans, design models, source code,
tests, and documentations. The RUP defines nine process work.flows:

1. Business modeling: Describes the structure and dynamics of the organization


2. Requireme nts: Describes the use case-based method for eliciting requirements
3. Analysis and design: Describes the multiple architectural views

Figure 1.2 Organization along time


Phases
Rational Unified Workflows I Inception j I~- El-ab-o-ra-ti-on~ I I Construction Transition
Process. (From
Kruchten [2000]
c Business modeling
The Rational Uni-
fied Process, An -
(1)
C
0
CJ
Requirements ~
t:
I

Introduction. Analysis and design


Addison-Wesley.)
Cl
C
.2 Implementation
~ 1
:..-::::::::::: t:- - -~ I
ca
C Test
0
.:: Deployment
<1l
·E Configuration and
:g, change management
0 Project management _............_,. ......__.. ..._~
I
; ,m,,m,i....- .. . . . . . . . .~;. . . . . . . . . . . . .
I I

Environment ~~====::~r;::;::~~==~:,,-::=======-4r========--
lnitlal
~ - - - ~ - ~ ~ #1
I
IElabllElabl 1cons111c onstj[Const j
#2 #N
[l'ra;,1
~ #2
1Tran \

Iterations
1.4 Iterative Development Processes • 15

4. Implementation: Takes into account software development, unit test, and integra-
tion
5. Test: Describes test cases, procedures, and defect-tracking metrics
6. Deployment: Covers the deliverable ystem configuration
7. Con.figuration management: Controls change to and maintains the integrity of a
project's artifacts
8. Project management: Describes various strategies of working with an iterative
process
9. Environment: Covers the necessary infrastructure required to develop a system

The RUP also defines four major phases:

l. Inception: Establishes the business case for the project


2. Elaboration: Establishes a project plan and a ound architecture
3. Construction: Grows the system
4. Transition: Supplies the system to its end users

Each phase is further broken down into one or more irerations. Each iteration goes
through the various process workflows (de cribed earlier) and i a complete develop-
ment cycle that results in the release of an executable product. The phase serve as the
controlling framework of the iterations. Iteration in different phases have different
emphases on process workflows as illu trated in Figure 1.2. For example, iteration
in the inception phase focus more on business modeling and requirements, while it-
erations in the construction phase focu more on implementation and configuration
management.

1.4.3 Extreme Programming

Extreme Programming (XP) i a light\ eight pro e \ ith an empha i on producing


high-quality executable code throughout the development pro e _. Since executable
code is the mo t important part of the ultimate deli erable of the oftware develop-
ment proces e , the XP pro e fo u e on e. ecutable od from the ery beginning.
Hence the name Extreme Programming. E/treme Programming i an iterative proce
with small iteration . Each iteration often le ts no more than a few day. to a few
weeks. The very fir t iteration \ ill produ e a minimum skeletal, and executable
in1plementation of the y tem to be de el peel. Ea h ub~equent iteration hould
enhance or improve upon the deli erable of the preceding iteration and produce a
new executable reka' e of the tem until the entire tern L ompleted. Extreme
Programming emphasize~ maintaining high quali in the code delivered by each
and every iteration, e p "cially in tenn of the exten ibility and maintainability. The
focu of earh iteration an b either nh ncement or refa taring. E11hanceme11ts intro-
due ne\ fun tionalitie ' r features. Refm:rori11g re true tu re the code to improve the
quality, in luding xten ibilit nnd maintainability, and lhe structure of the oftware
' YSt m while maintaining its behn ior. E, treme Programming depend heavily on
~--'-============================---------
16 ■ Object-Oriented Software Development

refactoring to maintain and improve qualities and to facilitate changes and enhance-
ments.
The core of XP consists of the following key practices:
Planning game: Start with a simple a plan for each iteration, and continually
refine the plan as necessary.
Frequenr and small releases: Make frequent and small releases starting as early
as possible. Each iteration, that is, the duration for producing a release,
bould not be longer than a few weeks.
Metaphor: U e metaphor to start development and communicate with the
customers.
Simple design: Make design as simple as possible. Refactor later if changes are
nece sary.
Test first: Write unit test before writing code.
Refactoring: Refactor to make the system simpler and clearer or to reduce
duplication.
Pair programming: Write all production code in pairs.
Collective ownership: Anyone may change code anywhere in the system to
improve it.
Continuous imegrarion: Integrate as soon as a task is complete.
40-hourweek: Teams are more productive if the members stay fresh and energetic
than if they work overtime.
On-sire customer: Have a customer available on-site and full time.
Coding standards: Adopt common standards and conventions for naming, source
code fonnaning, documentation, and so on.

Undoubtedly, some of the practices are unique to XP, such as pair programming.
Extreme programming and RUP share a lot in common. In some sense, XP can be
viewed as a minimalistic form of the RUP. One of the key differences between the two
is that the RUP emphasizes building object-oriented models using modeling notations
defined in UML, while XP emphasizes producing executable code. However, building
object-oriented model using UML is often done in XP as welJ. Several commonly
used notations of UML will be discussed in Chapter 2. Unit testing and continued
integration will be discus ed in Chapter 6. Refactoring will be one of the main topics
of Chapter 7.

CHAPTER SUMMARY

• Software development is difficult, time-consuming, and costly. The main causes


of problems in software development include the complexity, longevity, evolu-
tion, and high u er expectations of software systems.
Further Readings • 17

• The weU-known waterfall model of software development consists of the fol-


lowing phases: requirements analysis, design, implementation and unit testing,
integration and system testing, and maintenance.
• Software development lacks some key characteristics of welJ-established engi-
neering disciplines, including analysis of designs, nonrecurrence of failures, and
codification of knowledge. Software development is an immature field of engi-
neering.
■ The desirable qualities of software systems include usefulness, timeliness, re-
IiabiHty, maintainability, reusability, user friendliness, and efficiency. Of these
qualities, maintainability is the most crucial and deserves the most attention
during development. Factors contributing to maintainability include flexibility,
simplicity, and readability. The object-oriented development approach focuses
primarily on improving the maintainability and reusability of oftware systems.
■ An iterative object-oriented development process consists of a number of suc-
cessive iterations. Each iteration deals with a relatively small increment of the
system being developed. The system is developed incrementally. The iterative
process continues until the entire system is complete. Two of the common itera-
tive development processes are the RUP and XP.
■ The common activities in object-oriented development proce e include con-
ceptualization, object-oriented analysis and modeling, object-oriented design,
implementation, and maintenance.

FURTHER READ I NGS

Beck, K. (2000). Extreme Programming £rp/ained. Addi on-W ley.

Brooks, F. P. (1975). The Mythical Man Mo11th-Essays on Sofnl'are Engineering.


Addison-Wesley.

Brook , F. P. (1987). "No Silver BuUet- E ence and Accident of Software Engi-
neering," IEEE Software 20(4).

Jacob on. I., G. Booch, and J. Rumbaugh ( 1999). The Unified Sofnvare Development
Process. Addi on-Wesle .
Kruchten, P. (2000). The Rational U11ified Proass. A11 Imrodu tio11, 2nd ed. Addison-
We ley.

Pre man, R. . (1997). Software Engineering: A Practitioner's Approach. McGraw-


Hill.
ommer ille, I. (_QO l ). ofMare Engi11eeri11g, 6th ed. AddL on-Wesley.
18 • Object-Oriented Software Development

EXERCISES

1.1 Search the Web or librarie to find out detail of 1.3 Search the Web or libraries to find out whether
some of the catastrophic failure of computer it is permissible to use software engineer as a
systems who e cause ha,·e be-en attributed to professional title without ce1tification in your
software failures. including the one mentioned country or state, and what the rationale is.
in this chapter.
1.2 Search the Web or librarie to find out details
of some failed oftware development projects
and the cause .
Object-Oriented Modeling
Using UML

CHAPTER OVERVIEW
In this chapter, we discuss the basic principles, concepts, and techniques of object-
oriented modeling. We introduce a number of commonly used notations in the Unified
Modeling Language (UML), including class diagrams, object diagrams, sequence dia-
grams, and use case diagrams. We conclude the chapter with a case study of object-
oriented analysis and modeling.

PRINCIPLES AND CONCEPTS

In thi ection, we di u, the ba i con ept and the prin iple , of object-oriented
development. \Ve also introduce some ~ imp le graphi al notations in the Unified
Modeling language (U IL) (Booch et al .. 1999] 1 for des ·ribing obje t-orientcd
models. We u e a sub ·et of U IL notations with minor adJptati ns in synt, x for the
ake of con, isten , ith Ja a.

l. Ul\ IL is a stand:.ml for objc~t-orienteJ moJ ling no1~11i 1ns cnJol'\ed by the Object I\ 1:magcmcnt Group
(0 •IG), nn indu ·trial ron ·t,rtium on obj~ct tc~hnologics.

19
-
20 ■ Object-Oriented Modeling Using UML

2.1.1 Objects and Classes

Terms and Concepts


Objects and classes are two of the fundamental concep~s in object-orie~ted _develo~-
th obiect and clas es can be viewed from two different perspecuve~. (1) their
ment· Bo J • • • the real
representation in the object-oriented models, and (2) ~e1_r 111te1p~etatw,~ m
world. The repre entation of objects and classes_ dealt w1~ m the obJ~ct-onen~ed mod-
el (including programs) is only an approximauon of the mte~retallon of obJects and
classe in the real world. We can define objects and classes m tenns of ~ow th~y are
interpreted in the real world, as well as how they are represented in the obJect-onented
model.

Interpretation in the Real World Representation in the Model

Object An object repre ents anything in An object has an identity, a state,


the real world that can be distinctly and a behavior.
identified.
Gass A class represents a set of objects A class characterizes the structure
with similar characteristics and of states and behaviors that are
beha\'ior. These objects are called shared by all its instances.
the instances of the class.

Each object has a unique identity. The identity of an object distinguishes the
object from all other objects. The state of an object is composed of a set of fields, or
arrribules. Each field bas a name, a type, and a value. The behavior of an object
is defined by a set of methods that may operate on the object. In other words, a
method may access or manipulate the state of the object. Methods are sometimes
called operations, and we consider these two tenns to be synonymous. Each method
al.so bas a name, a type, and a value. The type of a method consists of the return type
and the list of parameter types of the method. The return type can be void if the
method does not return a value. The value of a method is the implementation of the
method often expressed as a sequence of statements, in languages like Java or C++.
The features of an object refer to the combination of the state and the behavior of the
objecL
Two objects are equal if their states are equal, that is, if the values of the
corre ponding fields of the two objects are equal. Two objects are identical if they are
the ame object, that is, if they have the same identity.
The value of the field of an object are mutable. Those methods of an object
chat do not modify the slate of the object are called accessors, and those methods
of an object char could modify the state of the object are called mutators. A mutable
object is an object whose state may be modified by some of its methods. A mutable
object may have different states at different times. An immutable object is an object
2.1 Principles and Concepts • 21

whose state may never be modified by any of its methods, that is, an object that has
no mutators. The tate of an immutable object remains constant. Objects are usually
mutable. However, immutable objects are quite useful too.
A class defines a template for creating or instantiating its instances, that is,
objects. The terms object and instance are often interchangeable. The class from
which an object is created is referred to a the class of the object, and the object
is referred to as an instance of the clas . In mo t object-oriented languages, including
Java and C++, in tead of defining the feature of individual objects, the features of
objects are defined in the class that instantiate the object . Specifically, a class defines
(1 ) the names and types of all fi eld and (2) the name . type , and implementations of
all methods. The values of the fields are not defined or fixed in the class definition. The
values of the fields are mutable. Each instance of the clas · ha its own state. Different
instances of the class may have different state . The implementation of methods are
defined in the class definition and are therefore fixed for a given object. In other word ,
the values of method of an object are immutable.
Let's look at a simple cla Point that repre ent points in a two-dimensional
space. The Java code defining the class is shown on the right-hand ide.

Class name Point class Point {


Fields x, y int x, y;
Method move public void move(int dx, int dy) {
// implementation
}
}

The Point class defines two field : x and y, and one method: move O. The type
of both fields is i nt . The return type of move () i void and the Ii t of the p:irarneter
types of move() i (int, i nt ), since it take two parameter both of type int.

UML Notation for Classes


The UML notation for cla se a rectangular box \ ith as man a three compart-
ments.

Class Name The top compartment . hows the cla s name.


field1
The middle omparunent contains the decl:irations of the fields of
...
fieldn the clas .

method,
The bon m comparunent contains the declaration. of the methods
... of the cla :
methodm

If, in ome context, the detail of the field - and method of the la s i. not important,
one may omit both the middle and the b tt m l'0mpa.rtments.
22 • Object-Oriented Modeling Using UML

The name of the field is required in a field declaration. Optionally, a field


declaration may also include the visibility, the type, and/or the initial values of the
2
field. In thi book, the Java yntax is used for field declarations:
[\lisibiliry] [7)p e] Name [ [ M11/t1jJ/icity ] ] [• InitialVa/11e]
Alternatively, field declarations can also be in the following standard UML
yntax:
[Visibility] Name [ [ M11ltiplicity ] ] [: 1)'pe] [• /11itia/Val11e]

Toe name of the method is required in a method declaration. Optionally, a method


declaration may also include the visibility, the return type, and/or the list of parameters
of the method. In this book, the Java syntax is used for method declarations:
[Visibility] [T)pe] Name ([Parameter , ... ] )

Alternatively, method declarations can be also be in the following standard UML


syntax:
[Visibility] Name ( [Parameter, . . . ] ) [:Type]

The visibility, or accessibility, of fields and methods defines the scope in which
features of classe are accessible. Visibility can be one of the following:

Public The feature is accessible to any class.


Protected The feature is accessible to the class itself, all the classes in the same
package,3 and all its subclasses.
Package The feature is accessible to the class itself and all classes in the same
package.
Private The feature is only accessible within the class itself.

The acces ibility of features will be discussed in more detail in Section 4.4.1. T he
Java and UML syntaxes for visibility are as follows:

Visibility Java Syntax UMLSyntax

public public +
protected protected #
package
private private

2. In 1his book. we use the following convention to defi ne syntax: the notation Foo (e g •n , 1 ) d t
. I bI . I bol h . f .., •1/ e eno es a
nonlcnmna ~ym o ; 1em11na sym s ~re~ own 111 bold ace Courier font (e.g., •). The entities between
square brackets ! J (e.g., (T)peJ) arc opllonal.
J. Packages arc discussed laler in this section fp. 25] and in Section 4.5 Ip. 134].

• I•
2.1 Principles and Concepts • 23

The multiplicity specification of a field specifies whether an object may have mul-
tiple occurrences of the field. The multiplicity specification is defined in Section 2.2.2
[p. 33].
Each parameter of a method can be specified using the Java syntax as follows:

Type Name

Alternatively, a parameter can be specified using the UML syntax as follows:

Name: Type

The following are some examples of field and method declarations:

Field declarations
Date birthday (Java syntax)
birthday : Date (UML syntax)

public int duration= 100 (Java ynta.'<.)


+duration : int = 100 (UML yntax)

private Student students [0 . . MAX_SIZE] (Java yntax)


-students[0 .. MAX_SIZE] :Student (UML yntax)

Method declarations
void move ( int dx, i nt dy) (Java yntax)
-move (dx: i nt, dy : int) (UML ynta"<.)

public int get Size ( ) (Java ynta"<.)


+get Size ( ) : int (UML yntax)

The Point cla s shown earlier can be repre ented in UML as follm • at different
levels of detail.

Full details in Java syntax:

Point
private int x
private int y
public void move(int dx, int dy)
-
24 ■ Object-Oriented Modeling Using UML

Full details in UML syntax:

Point
-x:int
-y:int
+move(dx:int, dy:int)

Abbreviated fonns:

Point
X
y
move()

UML Notation for Objects


The UML notation for objects is a rectangular box with one or two compartments.

The top compartment shows the name of the


objectName : ClassName object and its class. The object and class names
are underlined to distinguish the object notation
from the class notation.

fieldn = valuen The bottom compartment contains a list of the


fields and their values.

There are a number of variations for the contents of the top compartment:

• objectName
Omission of the colon and the class name denotes an object named obj e ctN ame
whose class is of no interest.
• : ClassName
Omission of the object name denotes an anonymous object of class ClassName,
which can be identified only through its relationship with other objects.

The fields and their values in the bottom compartment are described with the
foUowing syntax:

Field• Value

The bottom compartment may be omitted altogether if the attributes and values of an
object are of no interest
2.1 Principles and Concepts • 25

For example, instances of the Point class, with the states (0, 0) and (24, 40),
can be represented graphically as follows:

Point pl= new Point();


p1:Point p2:Point pl.x = O;
pl.y = O;
X=O X=24 Point p2 = new Point();
y=O Y=40 p2 .x = 24;
p2 .y = 40;

The Java code segment, on the right, shows the creation of the instances and the
assignment of the states.

Message Passing Objects communicate with one another by means of message


passing. A message represents a command sent to an object-known as the recipient
(also known as the receiving object or the receiver) of the message-to perform a
certain action by invoking one of the methods of the recipient. A message consists
of the receiving object, the method to be invoked, and (optionally) the arguments to
the method. Message passing is also known as method invocation. The following is
a message to instruct the recipient, point p1, to move IO units and 20 units in the x
and y directions, respectively, by invoking the method move() .

Recipient pl
p1.move(10, 20) Method move()
Arguments (10, 20)

Packages

Style Convention Package Names


Package names are all in lowercase letters, uch as j ava. awt. event.
Packages intended to be widely available should use the re erse of the Internet
domain as the prefix of the package name so that it will be unique globally- for
example, edu. depaul. cs.

Classes are often grouped into a package. Package. an be organized into a hierarchy.
In other words, a package may contain cla e and -ubpackage . It i important
to point out that all clas e in the ame package mu t be clo ely related, since all
feature of a cla , except tho e that are private. are ac e ible to all cla e in the
ame pa kage. Detail. for u ing pa .kag ' in Java, ill be di cus ed in Section 4.5. l .
-
26 ■ Object-Oriented Modeling Using UML

Agure 2.1 java.awt


[Java.lang
UML notation of
packages. (b)
Point

(a)

UMl Notation of Packages


The UML notation of packages is shown in Figure 2.1. A package is represented by a
rectangular container wilh a tab at lhe upper left corner. The classes and subpackages
that belong to a package are sometimes depicted within the rectangular container. In
that case, the package name is placed in lhe tab, as in Figure 2. 1(a). When the package
contents are not included, as in Figure 2.l(b), the package name can be placed inside
the rectangular container.

2.1.2 Principles

In this section, we discuss a number of important principles in the object-oriented


development approach.

Modularity
One of the fundamental principles of the object-oriented approach is the principle of
modularity. It is intended to control the complexity of large-scale systems through the
use of the divide-and-conquer technique.

Pri~ Modularity
A complex software system should be decomposed into a set of highly cohesive but
loo ely coupled modules.

Decomposition of complex software systems into modules is one of the most


intriguing tasks in oftware development and is more an art than a science. The reason
i !.hat most of the entities in a software system are intricately interconnected like a
web and mu t be un1.angled. The basic cri teri a for decomposition are two of the best
known and mo t elu ive concepts in software development-cohesion and coupling.
• Cohesion refers to the functional relatedness of the entities within a module.
• Coupling refer to the interdependency among different module .
2.1 Principles and Concepts • 27

A system may be extremely complex in its totality, but a modular decomposition


of the system aims to break it down into modules o that

■ each module is relatively small and simple (that is, hjghly cohesive); and
■ the interactions among modules are relatively simple (that is, loosely coupled),
ensuring that- by examining the module within, not without-each module will
be well-behaved and that, if all the modules are well-behaved, the entire system
also wiU be well-behaved.

Typically, modular decompositions are hierarchical (that i • a module may contain


other modules).
The concepts of modules, cohesion, and coupling all predate the object-oriented
approach. The forms of modules have evolved over time. In the tructured develop-
ment approach, the modules take the form of routine and function . In the object-
oriented approach, modules take the form of clas es and package .

Abstraction
In its purest sense, abstraction means separating the e ential from the none ential
characteristics of an entity. The result is a simpler but sufficiently accurate approx-
imation of the original entity, obtained by removing or ignoring the none enrial
characteristics. The abstraction principle in software development can be described
as follows:

Principle Abstraction
The behaviors, or functionalities, of a module bouJd be cbara terized in a uccinct
and precise description known as the co11tractual interface of the module. In other
words, the contractual interface capture the e en e of the behavior of the module.
The contractual interface is an ab traction of the module.

We can view a module a a senice provider and other module that u e the
services provided by the module as cliems of the module. \: e an view the contractual
interface as the service contract bet\ een the ervice pro idcr and it. clients.
service contract need only de cribe what e.rvi es ·ru1 be pro ided, not how the
service are to be provided. Therefore, de pite the fact that the rvice to be pro ided
are very complex, the , ervice ontract may be very imp le. ith a imple ervice
contract and an as uran .e by the . ervice provider of h noring the ontracl. the client
need only undectand the · imple contra t in order to u ·e the comp le , service .. The
contractual interfa e alto, s the client to u, e the . ervice , and not be oncemed with
the complexity of the er ice ·. In other , ord~. the comple. it of the module i hidden
within it.
Let us onsider the exrunpk of th telephone . The mechani m for providing
telephone . er ice i , a rather comp le. one. lt in olws routing and connecting calls,
28 • Object-Oriented Modeling Using UML

convening voice to electronic signals and back to voice, transmitting the signals. in
analog or digital mode, and possibly encrypting and decrypting the signals f~r secunt;
reason . However, telephone users (that is, the clients of a telephone service) don t
need to understand the mechanics of a phone system. All the users need to understand
i the manual that comes with the telephone set, which includes instructions on dialing,
speaking. and hanging up. The user's manual in this case is the contractual interface
of the telephone service, and it serves as an abstraction of the telephone service from
the user's perspective.

Encapsulation
A closely related and complementary principle is encapsulation, which stipulates that
the clients need know nothing more than the service contract while using the service.

Principle Encapsulation
The implementation of a module should be separated from its contractual interface
and hidden from the clients of the module.

Hence this principle is also known as information hiding. Encapsulation is in-


tended to reduce coupling among modules. The less the clients know about the im-
plementation of the module, the looser the coupling between the module and its clients
can be. An important benefit of encapsulation is that, if the clients know nothing be-
yond the contractual interface, implementation can be modified without affecting the
clients, so long as the contractual interface remains the same.
Telephone service is a good example of an app}jcation in which the contractual
interface and implementation are separated. In the past, signals were transmitted in
analog mode. Over time, telephone service bas been upgraded until, nowadays, the
signals can be transmiued in digital mode with encryption. Although the implementa-
tion of telephone service bas changed, the contractual interface remains the same. The
only effects on telephone users are that they enjoy better sound quality and greater
security.
If a contractual interface is completely separated from implementation, the con-
tractual interface can exist on its own. A contractual interface without any implemen-
tation associated with it is known as an interface in Java terminology. A module can
be represented by two separate entities: an inte,face that <;Jescribes the contractual
interface of the module and a class that implements the contractual interface.

Polymorphism
Several different service providers can honor the same contractual interface. More-
over, these service providers can be interchanged without affecting the clients. The
2.2 Modeling Relationships and Structures • 29

ability to interchange modules dynamically without affecting the clients is known as


polymorphism.4
Let us take the telephone service example one step further and consider cellular
telephone service. Digital cellular service uses more advanced technologies but has
smaller service regions than does the more e tablished analog cellular service. An
analog/digital dual-mode cellular phone is an example of polymorphism. It provides a
single contractual interface for using the phone bat employs two different technologies
to provide the service. Users need not be concerned with-and certainly are not
affected by- which technology is u ed to provide the service at any given moment.
The dual-mode cellular phone dynamically switches (a soft switch) between the digital
and analog modes as the user crosse the boundary of the digitaVanalog service
regions. We discuss polymorphism in more detail in Section 5.2.2 [p. 165].

2.2 MODELING RELATIONSHIPS AND STRUCTURES

In this section, we introduce the UML class diagram for modeling the tatic tructures
of object-oriented software systems and various types of relation among the cJas es.
Class diagrams are the most common diagrams used in object-oriented modeling. A
class diagram consists of
■ a set of nodes that represent classes and interface ; and
■ a set of links that represent relationship among clas es.

The following relationships among clas es can be modeled in clas diagram :

■ Inheritance, including exten ion and implementation


■ A sociation, including aggregation and compo ition
■ Dependency
The graphical notation for clas e i di cu ed in Section 2.1.1. Tbe graphical nota-
tion for variou relationship are di cu ed in the next four ub ection .

2.2.1 Inheritance

Inheritance i one of the mo t important relationship in object-oriented modeling.


Inheritance define a relation hip among cl es and interface . More pecifically.
there are evenu form of inheritan e relation:
■ The t.tte11sio11 relation between two classes. Whenclas C2 extend cla Cl, class
C2 i known as a subclass of la Cl. and la Cl i knm n a a superclass of
cla s C2.

4. The word poly111orphis111 ml!un · an i:nLity , ilh multiple~ m1 ·. In lhis particular context, it refer to a
conlructual inlcrfm:i: , ilh multiple interchangeable impkmcntntions.
--

30 • Object-Oriented Mod~ling Using UML

■ The txtension relation between two interfaces. When interface 12 extends inter-
face l 1, interface 12 is known as a subinterface of interface l 1, and interface l 1
is known as a superinterface of interface 12.
■ The implementation relation between a class and an interfacq. When class C2
implements interface 11, class C2 is known as an implementation of interface
11, and interface 11 is known as an inte1face of class C2.

UML uses a different terminology for inheritance relationships. The extension relation
is also known as specialization, and the inverse relation is known as generalization
in UML. The implementation relation is also know as realization in UML.
Graphically, the inheritance relation is represented by a link from the subclass/
subinterface to the superclass/superinterface with a hollow triangle pointing toward
the superclass. The extension relation is represented by a solid link, and the imple-
mentation relation is represented by a dashed link, as shown in Figure 2.2. In class
diagrams, the regular class, field, and method names are shown in upright roman fonts,
as in MyClass. The names of interfaces and its methods are shown in italic fonts, as
in Mylnterface.
Conceptually, inheritance models the is-a(n) relationship in the real world; that is,
if C2 is a subclass/subinterface/implementation of Cl, then every instance of C2 is an
instance of Cl, and everything that applies to instances of Cl also applies to instances
of C2. The ex.tension relation between two classes is commonly associated with the
notion of reusing or sharing the implementation (that is, the fields and methods) of a
superclass by its subclasses. The extension relation between two interfaces represents
the expansion of the service contract. The implementation relation does not connote
reuse of implementations, but rather the implementation of a contractual interface by
a class.
As an example, let us consider the following set of classes that represent different
groups of srudents in a university. The class diagram is shown in Figure 2.3.

F"tgure 2.2
Superclass Superinterface Interface
6.
UML notation fOf'
lnhattana r~la-
tionships.
Subclass Subinterface Implementation

extension extension implementation


of classes of interfaces of interfaces
2.2 Moddlng Relationships and Structures • 31

Figure 2.3
Student
Class diagram: in-
heritance relation
among classes rep- Nondegree Undergraduate Graduate
resenting student
groups.

Master PhD

Class Description

Student Students in general


Graduate Graduate students
Mast er Graduate students pursuing a master's degree
PhD Graduate students pursuing a PhD degree
Undergraduate Undergraduate students
Nondegree Nondegree students

It is easy to see in this example that inheritance can be interpreted ru an is-a


relation. For example, the Graduate class i a subclass of Student, a e ery graduate
student is a student, and everything that applie to a student al o applie to a graduate
student. The set of students, that is. the et of the in tance of the Student class,
is a superset of the set of graduate rudents, that i . the et of the instance of the
Graduate class.
A class may inherit from multiple uperclasse . Thi capability i often referred
to as multiple inheritance. However, many object-oriented programming languages.
including Java, support only a re tricted form of inheritance known a single inher-
itance, in which each clas may inherit from only one uperclas . Java supports a
limited fonn of multiple inheritance by allowing cla_ e to implement multiple in-
terface . We di cu i sue~ related to ingle and multiple inheritance in Section 5.3.2
[p. 179].

Levels of Abstraction
Cla. se and interface repre ent ab traction , and the inheritance relation hip orga-
nizes the cla ses and interfa e into different level of ab traction.
32 ■ Object-Oriented Modeling Using UML

Principle Lerels of Abstraction


Ab traction can be ordered into different levels. The higher the level, the more
oeneral the ab traction i . The lower the level, the more specialized the abstraction is.
0

1n other words, the superclasses represent more general abstractions and the
subclasses repre ent more specialized abstractions. Consider again the example of
tudents hown in Figure 2.3. The inheritance hierarchy shows different levels of ab-
straction of students in a university. The Student class represents the most general
ab traction of tudents, whereas its subclasses represent various specialized abstrac-
tions of tudents. The leaf classes (that is, classes with no subclasses) represent the
most pecialized ab tractions of students.

2. 2-2 Association

Associations represent general binary relationships between classes. The association


can be used to model a \ ariety of relationships between classes, and the association
relationship can also be implemented in a variety of different ways. It is common that
eitherone or both classes in an association relation contain direct or indirect references
Lo the other class. The graphical notation for association is a solid line between the
two classe involved in the association with an optional label and optional adornments
anached to either end, as shown in Figure 2.4. Figure 2.5 shows several associations
among the Student, Faculty, and Course classes.
The graphical notation of an association may have an optional label that consists
of a name and a direction drawn as a solid arrowhead with no tail. In Figure 2.5,
reach and enroll are the names of the association between Faculty and Course, and
between Student and Course, respectively. The direction arrows next to the names
indicate the direction of association with respect to the name. For example, the arrow
next o the enroll association means that "a student enrolls in a course," not that "a
course enrolls in a tudent." By default, it is assumed that the direction of associations
is from left to right and from top to bottom.
The graphical notation of an association also allows an optional role name and
an optional mulripliciry specification to be attached to either of the classes involved
in the as ociation. In Figure 2.5, adviser and advisee are the role names associated

Figure 2.4
Class1I
- - - - ~ role
name
I Class2
role ~-----'
UML notation for
association rda-
tionship.
2.2 Mod~ing Relationships and Structures • 33

Figure 2.5
Student 1-•-_ _e_n_ro __
_ll_► -1
Course
Class diagram: as- advisee •
sociation relation-
ships.
• teach

1
1
Faculty
adviser

with Facult y and Student, respectively, in the association between Faculty and
Student . The role name may also have an optional vi ibility designator, that is +,
#, - ,or-.
The multiplicity specification is a comma-separated sequence of integer intervals.
An integer interval can be one of the following:

l . .u specifies a closed, that is, inclusive, range of integers from the lower bound
I to the upper bound u. Both the lower and upper bound are integer literals.
The upper bound may also be the asterisk character ( ), which indicates an
unlimited upper bound.
i specifies a singleton range that contains integer i, which is an integer literal.
* specifies the entire nonnegative integer range: 0. 1. 2. 3.. ..

Here are some examples of multiplicity spedfication :

0 . ·* 0 or more
1. · * 1 or more
2 .. 5 2 to 5
2, 5, 7 2, 5, and 7
1, 3, 5. ·* I, 3, and 5 or more

In Figure 2.5, the enroll a ociation i man -to-man ; that i a tudent may enroll
in any number of course . and a ourse ma have any number of tudent · enrolled
in it. The reach association i one-to-man ; that i ·. ea h ourse has onl one faculty
member to teach it, but a fa ult member ma tench an number of course . The
adviser-advisee a ·ociation L al o one--to-man ; that i , ea b . tudenl ha. one advi er,
but an advi er may ha e any number of ad i ee ·.
The graphical notation of an a iation ma al o indi ate the navigation of
the a o iation. If there i, a dire t or indirect refere nee from cl Cl to clas C2,
then it is navigabl from Cl to C2. n as , iation ma b nn igable in one or both
dire tions. By default, an a. s iation is a · ·urned to be navigable in both direction .
If nn a o iation is only navigable in one dire tion, it mu t be explicitly hown with
34 • Object-Oriented Modeling Using UML

a navioation arrow at the end of the association link pointing in the dired\on that
is navi;able. The navigation arrows should not be confused with the name direction
arrow. They are independent. ln the class diagram shown in Figure 2.5, the adviser-
ad-visee association is only navigable from an advisee to an adviser, not from an
adviser to advisees.

2.2.3 Aggregation and Composition


Aggregation is a special fonn of association. It represents the has-a or part-whole rela-
tionship. Aggregation is simply a structural relationship that distinguishes the whole,
that is, the aggregate class, from the parts, that is, the component class. Aggregation
does not imply any relationship in the lifetime of the aggregate and the components.
A stronger form of aggregation is called composition, which implies exclusive owner-
ship of the component class by the aggregate class. Under composition, the lifetime of
the components is entirely included in the lifetime of the aggregate. In class diagrams,
aggregation is indicated by a hollow diamond at the end of the aggregate class. The
composition relationship is indicated by a filled diamond; see Figure 2.6. It is obvious
that the aggregation diamond may only appear at one end of the link. Since aggrega-
tion is a special form of association, the discussions on the names, multiplicity, and
navigation of association also apply to aggregation.
Figure 2.7 shows an example of the aggregation relationships among the Uni -
versi ty College, Department, Faculty, and Student classes. All relationships
shown in the diagram are aggregation relationships, since
■ a srudent is a part of a department
■ a faculty member is a part of a department.
■ a department is a part of a college.
■ a college is a part of a university.
However, the nature of the first two part-of relationships is somewhat different from
the last two part-of relationships. A student or a faculty member may exist without
being a part of a department. The lifetimes of the students and the faculty members are
independent from the lifetime of the department to which they belong. On the other
hand, a department exclusively belongs to a college, and a college exclusively belongs

Flsure 2.6
Aggregate Aggregate
UML notations for role role
aggregation rda- name
tlonshlps.
name
role role
Component Component
aggregation composition
2.2 Modeling Relationships and Structures ■ 35

Figure 2.7 1 1
University - ----1* 1
College - - ---1· Department ~>-----l* Student
Class diagram:
1 1
aggregation rela-
tionship. chairman-of A A member-of
1 1.:
Faculty

to a university. The lifetime of a department may not exceed the lifetime of the college
it belongs to, and the lifetime of a college may not exceed the lifetime of the university
it belongs to. The part-of relationships between a university and its colleges and
departments are stronger than the part-of relationships between a department and its
students and faculty members. The former are considered composition relationships.
The distinction between aggregation and composition is entirely conceptual.
There is no distinction in how aggregation and composition relationships are im-
plemented. The design models in this book will not distinguish aggregation and
composition, and only the aggregation notation will be used.

2.2.4 Dependency

Dependency is a relationship between entities such that the proper operation of one
entity depends on the presence of the other entity, and change in one entity would
affect the other entity. A common form of dependency i the use relation among
classes. In other words, class C1 depends on class C2 if C1 u e C2 in place uch as
the parameters, local variables, or return types of its methods. The graphical notation
for dependency relationships is a dashed line with an arrow pointing in the direction
of dependency, as shown in Figure 2.8.
The class diagram shown in Figure 2.9 illu trates the dependency relationship.
The Registrar class has a number of method for adding and removing course in
the course schedules and enrolling and dropping tudent in cour e . The e methods
take parameters that are in tance of CourseSchedule, Course, and Student
classes. Therefore, the Registrar cla u e , that i , depend on, CourseSchedule,
Course, and Student cla e .

Figure 2.8
Class1 ~----------------1 Class2
UML notations for Class1 depends on Class2.
dependency rela-
tionships.
36 • Object-Oriented Modeling Using UML

Figure 2.9 Registrar

Class diagram: de- void addCourse(CourseSchedule s, Course c)


void removeCourse(CourseSchedule s, Course c) Course
pendency relation-
ships. Course findCourse(String title)
void enroll(Course c, Student s)
Student
void drop(Course c, Student s)

MODELING DYNAMIC BEHAVIOR

Class dia!mlllls model the tructure of software systems in terms of static relationships
~

among classe . However, another important aspect of software systems-d)namic


behavior-involves the interactions among objects and the ordering of events and
actions related to the objecL In this section, we discuss two of the commonly used
graphical notations in UML for modeling the dynamic behavior of software systems:
the sequence diagram and the state diagram.

2_3.1 Sequence Diagram

Sequence diagrams depict object interaction by highlighting the time ordering of


method invocations. Toe notations for sequence diagrams are shown in Figure 2. 10.
The y-axis of a equence diagram represents time in the downward direction. The
objects that participate in the interaction are laid out on the x-axis represented by
columns. The object that initiates the interaction is usually placed in the leftmost col-
umn, with increasingly subordinate objects shown on the right. The object represented
by each column is shown at the top of the column, and a vertical dashed line shows
the lifeline of the object. The rectangular bars over the lifeline indicate the focus of
control of the object, that is, the duration in which one of the methods of the ob-
ject i being executed. The solid horizontal links indicate the creation of new objects
or invocation of methods. The dashed horizontal links indicate the return of method
invocation . Method invocations are arranged in ascending order of time vertically
downward.

Figure 2.10
l Obj
1
: c\ an object
<<create>>
creating a new object
UML notations

0
I

for sequence method (param)


diagrams. focus of control invoking a method
I
I
I

: lifeline ◄- --- -- - - -- ---- returning from a method invocation


2.3 Modeling Dynamic Behavior • 37

Figure 2.11
I:PrinterQueue j
I
A sequence dia- I

gram: printing a <<create>> I


I
r - - - - - - - - - ~ pj : PrintJob I
document. I
I
submit (myDoc) I
I

add (this)

assi gnJobNo 0

-- - ------------------- --►

◄ --------- -------- --- --- ---


pri nt ()

Figure 2.11 shows a sequence diagram that depicts the flow of execution when
a client issues a request to print a document. It indicates the following equence of
method invocations:

1. An object cl i ent creates an instance of PrintJob.


2. The object client invokes the submit () method of PrintJ ob to print a
documentation (myDoc).
3. The Print J ob object adds itself to a queue which i an in tance of Printer-
Queue.
4. The PrinterQueue object invoke the assignJobNo O method of Print Job
to assign a number to the new print job being added to the queue.
5. The ass ignJobNo() method return .
6. The add () method returns.
7. The submit() method return .
8. Sometime later, when the print job become the first in the printer queue, the
print () method of Print Job i invoked to print to document.
9. The print() method return .

2.3.2 State Diagram

State diagrams depict the flo\ of control u ing the on ept of state and transitions.
State diagram, are generaJizati n. of the traditional finite rate machines (FSM).
A state i a ondition or siniation in the life of an obje t during which it ati fies
ome condjtion. performs ome a tion . or v rut' f r ome e ent . A tra11sitio11 is a
relation. hip between t\ o tate indi ating that an object in the fir t tale (the source
state) , ill perform certain a tion · and enter the econd state (the de tination state)
38 • Object-Oriented Modeling Using UML

when a specified event occurs and certain conditions are _sati~fied. The graphical
notation for the ba ic elements in state diagrams are shown m Figure 2.12. States are
drawn as round-cornered rectangles. Each stale has a name. Transitions are shown
as link from the ource tale to the target states. The labels of transitions are in the
following fonn:

[faent-Usr] [ [G11ard]] [/ Action]

An event i an occurrence of a stimulus that can trigger a state transition. Each


tran ition is labeled with a Ii t of the names of the events that can trigger the transition.
When there i more than one event name in the label of a transition, the event names
are separated by vertical bars (I), for example, e 1\e2 I . . . \e11 • When a transition has
no triggering events, it is triggered without an event, and it is known as a triggerless
transition. The label of a transition may also include a guard and an action. The
guard in the label of a transition is a boolean condition that must evaluate to true for
the transition to take place. The action in the label of a transition specifies the action
to be carried out when the transition is made.
The life of an object begins in a designated initial state. It follows a series of
transitions to reach various states. The life of the object ends when it reaches a.final
state. The following are the rules for state transitions:

l. lf there is a triggerless transition originating from the current state, the transition
is triggered and the object enters the destination state of the triggerless transition.
2_ Otherwise, the object waits for some events to occur. When an event occurs,
a. if there is a transition originating from the current state such that
(I ) the event that occurred matches one of the events specified in the label
of the transition, and
(2) all the guards specified in the label of the transition evaluate to true,
then the transition is triggered, the action specified in the label of the tran-
ition, if any, is performed, and the object enters the destination state of the
transition.
b. Otherwise, the object remains in the current state.

Figure 2.12
( State ) a state source state
UML notations for
s~diagram. events [guard] / action
• an initial state

a transition
@ a final state destination state
__....._______
2.3 Modeling Dynamic Behavior ■ 39

Each state may aJso have an entry action and an exit action, which can be specified
as follows:

entry / Action 1

exit / Action2

When an object enters a state, the entry action associated with the state, if any, is
performed. When the object leaves a state, the exit action associated with the state, if
any, is performed. I

Nested State Diagram In state diagrams, states can be nested, known as composite
states or superstates. The notations for the nested tales are shown in Figure 2.13. The
nested states, or substates, can be either sequential or concurrent. A composite state
with sequentiaJ substates contains a ne ted state diagram. When a tran ition enters
such a composite state, it enters the initial state of the ne ted tate diagram. At a given
moment, an object is in exactly one of the sub tates. For example. in Figure 2. 13(a), an
object can be in either state S11 or S12. A composite tate with concurrent ub tate is
used to model concurrency. Each concurrent substate contain a tate diagram. When
a transition enters such a composite state, it simuJtaneou ly enters the initial tates
of the state diagrams in each concurrent substate. At a given moment, an object is
simultaneously in one of the states of the nested tate diagram in each concurrent
substate. For example, in Figure 2.13(b), an object can imultaneou l be in one of
states S21 a or S21 b and one of states S22a or S22b. When a tran it:ion originate
from a composite state, it means that the transition may originate from an of the
states enclosed in the composite state.
Figure 2. 14 is a state diagram de cribing the operation of a cellular phone.
Operation starts in the initial state, which lead to the Off tate. The power-on event
triggers a transition that leads to the On tate. A the On tate i a ompo ite late.
the cellular phone enters the initial tate of the tate diagram n ted in the On tate
which leads to the Standby state. The tran ition labeled power-off originate from

Figure 2.13
so S1 S2
UML notations for S21
nested states in a S21a 1--~ S21b
state diagram.
S22
S22a 1-----'~ S22b

(a) sequential substates (b) concurrent substates


40 • Object-Oriented Modding Using UML

Figure 2.14
On
~---.. incoming call
A stat~ diagram: Standby r-----
operation of a power-<>n
cdular phoM. keypad-unlock keypad-lock

end
Active

0-91#1* Ringing

connection-fail
power-off
keypad-unlock

~ - -- ---------=:,,.I Talking
connection-succeed
Entry: LCDOn 0
Exit: LCDOf f ()

another composite Active state. Hence this transition may originate from any of the
states inside the Active state. Note that the Active state bas both entry and exit actions
associated with it. Thus, whenever the cellular phone enters the Active state, the
LCDOn () method (that is, the entry action) is invoked to tum on the LCD screen,
and whenever the cellular phone leaves the Active state, the LCDOff O method (that
is, the exit action) is invoked to turn off the LCD screen to conserve the battery. The
st.ate diagram nested inside the Active state depicts the flow of both making a call and
receiving a call To make a call requires the following sequence of events:

■ From the Standby state, unlock the pad (the keypad-unlock event) to enter the
Dialing state in Active.
■ In the Dialing state, dial the phone number by using the number keys (0- 9) or #
or *, and the cellular phone remains in the Dialing state.
■ From the Dialing state, push the t alk key (the talk event) to enter the Connecting
state.
■ From the Connecting state, if the connection succeeds (the connection-succeed
event), the cellular phone enters the Talking state. If the connection fails (the
connec1io11-fail event), the cellular phone returns to the Dialing state.
To receive a call requires the following sequence of events:

■ From the Standby state, an incoming call (the incoming-cal/ event) triggers a
tran ition that leads to the Ringing state in Active .
■ From the Ringing state, push the talk key (the wlk event) to enter the Ta lking
tate.
2.4 Modeling Requirements with Use Cases • 41

Note that the transition labeled end originates from the composite state Active. Hence
from any state in the Active state, pushing the end key (the end event) triggers this
transition, and the cellular phone enters the Dialing state. Similarly, from any state
in the Active state, locking the keypad (the keypad-lock event) triggers the transition
labeled keypad-lock, which leads to the Standby state.

MODELING REQUIREMENTS WITH USE CASES

Use cases and use case diagrams are used for modeling the requirements of the systems
to be developed and for deriving object-oriented models from the requirements.
Although use cases are not essential in object-oriented software development, they
provide important links between requirements and object-oriented models. Therefore,
use cases and use case diagrams play an important role in the RUP and UML.

2.4.1 Terms and Concepts

Use cases describe the externally observable behavior of system functions, in the form
of interactions between the system to be developed and the external entities, known
as the actors, of the system. Each actor represents a role played by a set of external
entities that interact with the system. Actors may represent roles played by human
users of the system or by other systems. Use cases are intended to describe what the
system does, not how the system does it.
Each use case has a name and a set of scenarios. One of the scenario is the main
scenario. Optionally, a use case may have any number of alternative and exceptional
scenarios. Each scenario is a sequence of interactions between the actors and the
system. The main scenario describes the normal flow of events and the outcome of
the use case; that is, it describes what will happen when all the precondition are met.
Sometimes, there are a number of alternative flow of events and outcome that are
also considered to be normal. The e alternative flows of events can be described as
alternative scenarios that complement the main scenario. The exceptional scenarios
describe the flows of events and the outcome when certain preconditions of the
normal scenarios are not met, or when errors or exceptions occur during the normal
flow of events.
Scenarios of use cases are usually de cribed informally. Following are some of
the commonly used fonnats for de cribing a scenario:

• A paragraph that de cribes the flow of events in the cenario


• A two-column table with one column describing the input events generated by
actors, and th other olumn describing the response produced by the system (see
example , hown in Figure 2.19).
..___.-.: : :=========================~------,==,-
42 • Obje:ct-~nmt Modeling Using UML

2.4.2 Use Case Diagrams

A use case diagmm consists of

■ use cases,
■ actors. and
■ relationships among actors and use cases.

The graphical notation for use cases and actors is shown in Figure 2.15. Several types
of relation hips can be represented in use case diagrams:

■ The extension, or generalization, relationship among actors.


■ The association relationship between actors and use cases.
■ The dependency relationship among use cases. Furthermore, there are two types
of dependencies among use cases: include and extend.

The extension relationship among actors is similar to the extension relationship


among classes. An actor representing a general group of users can be extended, or
specialized, by another actor representing a more specialized group of users. For
actors a1 and a 2, a1extends a 2 means that the set of external entities represented by a 1
is a subset of the set of external entities represented by a2 . Let us consider the example
in Figure 2.16, in which the actor User represents the set of all users of the system
to be developed, while the actors Student, Faculty, and Administrator each represent
a more specific set of the users of the system, that is, the set of students, faculty,
and administrators, respectively. The sets of students, faculty, and administrators are
all subsets of the set of all users, hence, the extension relationship among the actors
shown in Figure 2.16.
Actors are related to use cases via the association relationship. An actor is
associated with a use case if the actor is a participant in the interactions described
in one of the scenarios of the use case.
There are two types of dependencies among use cases: include and extend. For
use cases Ct and c2 ,

■ Ct includes c2 means that use case c2 is part of use case ci, and
■ c2 extends c I means that use case c2 is a special case of use case c 1•

Agure 2.15
User ~ an actor
UML notations for
actors and UK
caKS.

8 a use case
-
42 • Object-Oriented Modeling Using UML

2.4.2 Use Case Diagrams

A use case diagram consists of

• u e cases,
• actors, and
• relationships among actors and use cases.

The graphical notation for use cases and actors is shown in Figure 2.15. Several types
of relationships can be represented in use case diagrams:

• The extension, or generalization, relationship among actors.


• The association relationship between actors and use cases.
• The dependency relationship among use cases. Furthermore, there are two types
of dependencies among use cases: include and extend.

The extension relationship among actors is similar to the extension relationship


among classes. An actor representing a general group of users can be extended, or
specialized, by another actor representing a more specialized group of users. For
actors at and a 2, at extends a 2 means that the set of external entities represented by a 1
is a subset of the set of external entities represented by a 2 . Let us consider the example
in Figure 2.16, in which the actor User represents the set of all users of the system
to be developed, while the actors Student, Faculty, and Administrator each represent
a more specific set of the users of the system, that is, the set of students, faculty,
and administrators, respectively. The sets of students, faculty, and administrators are
all subsets of the set of all users, hence, the extension relationship among the actors
shown in Figure 2.16.
Actors are related to use cases via the association relationship. An actor is
associated with a use case if the actor is a participant in the interactions described
in one of the scenarios of the use case.
There are two types of dependencies among use cases: include and extend. For
use cases Ct and c2 ,

• Ct includes c2 means that use case c2 is part of use case Ct, and
■ c2 extends Ct means that use case c2 is a special case of use case Ct.

Figure 2.15

UML notations for


actors and use
cases.
u~,x an actor

8 a use case
2 .4 Modeling Requirements with Use Cases • 43

Figure 2.16

Extension relation-
ships among ac-
tors.

Student Faculty Administrator


Let us consider the use case diagram shown in Figure 2.17. Association relation-
ships between the actors and the use cases indicate that
■ a student may participate in use case Check grades.
■ a faculty member may participate in use cases Get Roster and Enter Grades.
■ an administrator may participate in use case Verify grades.
■ any user may participate in use case Validate User.
Furthermore, the include relationship indicates that use case Validate User is part of
all the other use cases shown in the diagram. The extension relationship indicates that
use case Enter Grades is a special case of use case Get Roster, since entering grades
requires getting the roster with an editable grade field.

Figure 2.17

Dependency rela- Check


<<include»
tionships among
use cases.
grades ------ ---
Student <<include>>
--- --- ~
,
<<include>>, / User
, I

, ,, I
I

, I
,' <<i nclude>>
I
Faculty Enter I
I
grades I
I

Verify
grades

Administrator
• Object-Oriented Modeling Using UML

2.4.2 Use Case Diagrams

A use case diagram consists of

■ use case ,
■ actors, and
■ relationships among actors and use cases.

The graphical notation for use cases and actors is shown in Figure 2.15. Several types
of relationships can be represented in use case diagrams:

■ The extension, or generalization, relationship among actors.


■ The association relationship between actors and use cases.
■ The dependency relationship among use cases. Furthermore, there are two types
of dependencies among use cases: include and extend.

Toe extension relationship among actors is similar to the extension relationship


among classes. An actor representing a general group of users can be extended, or
specialized, by another actor representing a more specialized group of users. For
actors a1 and a 2, a I extends a 2 means that the set of external entities represented by a 1
is a subset of the set of external entities represented by a 2 . Let us consider the example
in Figure 2.16, in which the actor User represents the set of all users of the system
to be developed, while the actors Student, Faculty, and Administrator each represent
a more specific set of the users of the system, that is, the set of students, faculty,
and administrators, respectively. The sets of students, faculty, and administrators are
all subsets of the set of all users, hence, the extension relationship among the actors
shown in Figure 2.16.
Actors are related to use cases via the association relationship. An actor is
associated with a use case if the actor is a participant in the interactions described
in one of the scenarios of the use case.
There are two types of dependencies among use cases: include and extend. For
use cases c 1 and c2,

■ c 1 includes c2 means that use case c2 is part of use case Ct, and
■ c2 extends c, means that use case c2 is a special case of use case Ct.

Figure 2.15

UML notations for Userx an actor


actors and use
cases.

8 a use case
2.4 Modeling Requirements with Use Cases • 43

Figure 2.16

Extension relation- ~ User


ships among ac-
tors.

+
Student Faculty Administrator

Let us consider the use case diagram shown in Figure 2.17. Association relation-
ships between the actors and the use cases indicate that
• a student may participate in use case Check grades.
• a faculty member may participate in use cases Get Roster and Enter Grades.
• an administrator may participate in use case Verify grades.
• any user may participate in use case Validate User.
Furthermore, the include relationship indicates that use ca e Validate User i part of
all the other use cases shown in the diagram. The extension relation hip indicate that
use case Enter Grades is a special case of use case Get Roster, since entering grades
requires getting the roster with an editable grade field.

Figure 2.17

Dependency rela- Check


tionships among grades ---«include»
---
use cases.
Student «include»
--- --- ----- ,, I
<<include>> ,' I User
,' I
I
' I

' '' I
,' <<include>>
I
Faculty Enter I
I
I
I

Verify
grades
Administrator
• Object-Oriented Modeling Using UML

CASE STUDY: AN E-BOOKSTORE

ln this case tudy, we develop an object-oriented model for an online bookstore to


illu trate some of the activities in the object-oriented development approach.

2.5.1 Conceptualization

The conceptualization phase is not intended to establish complete system require-


ments. Rather it is to establish the vision and core requirements of the system. A
prototype could be developed as a proof-of-concept demonstration and to validate
important assumptions.
The core requirements of the e-bookstore are to allow its customers to browse
and order books, music CDs, and computer software through the Internet. The main
functionalities of the system are to provide infonnation about the titles it carries
to help customers make purchasing decisions; handle customer registration, order
processing, and shipping; and support management of the system, such as adding,
deleting, and updating titles, updating customer infonnation, and the like.
This core requirements statement allows us to start the analysis phase. Many
aspects of the requirements need further elaboration and refinement, which can be
done in later iterations.

2.5.2 use cases


Based on the core requirements of the system, we can start to identify the main actors
and the key use cases. In an iterative development process, the initial set of actors and
use cases rarely needs to be complete or exhaustive. It is common that more actors and
use cases will emerge in later iterations when the requirements are further elaborated
and become more complete. It is also common that the actors and use cases will be
ex.tended in later iterations. The initial set of actors and use cases should focus on
the main functions to be provided by the system and deal with the most common
scenarios. The goals of the initial use case analysis should be as follows:
L To define the key functions and main scenarios, so that these key function
, cenarios can be implemented in early iterations and secondary functions and
exceptional scenarios can be deferred to later iterations.
2. To identify the actors and use cases to be elaborated or extended in later iterations.
The initial use case analysis of the e-bookstore results in the use case diagram
hown in Figure 2.18. Clearly one of the key actors of the sy tern is the Customer,
and one of the key u e case is Shop, which we will elaborate later. A related use
case al o involving the cu tomers is Registration, which is a necessary step before
customers can tart shopping. From the core requirements of the system, it is al o
easy to ee I.hat another important aspect of the e-bookstorc is the management of the
2.5 Case Study: An E-Bookstor~ • 45

Figure 2.18

Use case diagram


of e-bookstore.

Customer

System Inventory
administrator manager
L----------------~

e-bookstore. Although we decide to focus on the customer side of the sy tern first, it is
also important to identify most of the important actors and use cases in the y tern early
on. The elaboration and implementation of these u e case can be deferred to later
iterations. The main actor for the management side is identified a Manager. Ho, ever,
it also appears that there are two categories of distinct management tasks: one deal
with the management of the catalog of the merchandi e offered by the e-bookstore,
and the other deals with the management of the inventorie and the proce ing of
the orders. Therefore, we further identify two extended actors of Manager: Catalog
Manager and Inventory Manager, which are as ociated with u e cases corre ponding
to their main tasks: Manage Catalog and Process Order. respectively. A common
function for both customers and managers i to Jog on to the y tem. So it i reasonable
to define Logon as a separate use case that i incJuded b other u e c e involving
customers or managers. The logon function al o leads u to n function that i not
explicitly specified but implied in the requirement , that i . the managemenr of the
accounts for both the customers and managers. Therefore, a ne, u. e ca e Manage
Account i identified. Since the ta ks of managing the a ount are di tin t from aJl
the other ta ks, it i also rea onable to introduce a ne\ a tor, System Administrator,
to be a ociated with the ne, u e ca~e.
The use ca e Shop will be the focu of the early iteration. U. e cru e Shop i
de cribed in Figure 2. t9. A u ·e case u. uaU ha. a pre ondition, which pecifies the
condition under whi h the u e ca i appli able. The main enari of use ca e Shop
i described u ing a two- olumn table, in\ hi h th " left-hand column describes the
input events generated b a tors and the right-hand olumn de cribc. the re ponse
produc d by the ' ystem.
-----============================:=====~;;;;.;;;::=~~ ..
46 • Object-Oriented Modeling Using UML

Figure 2.19 Use Case: Shop

The scenarios of Actor: Customer


use case Shop.
Precondition: The customer is already registered.
Main scenario:

Input Events from Actor Customer System Events and Responses

Log on Display a welcome message and request


customer ID and password
Enter customer ID and password Authenticate customer
Customer authentication succeeds
Repeat the following until done:
Search and browse titles Show information about the titles
Select a title to buy Add the title to the shopping cart
Done with shopping and check out Display shopping cart contents and
shipping/billing addresses
Confirm order and payment method Validate payment method
Payment validation succeeds
Process order, issue an electronic receipt,
and notify warehouse for shipping
Log off

Alternative scenario:
The customer saves the shopping cart without checking out.
Exceptional scenario:
Customer authentication fails; repeat the logon procedure.
Exceptional scenario:
Payment validation fails~prompt the customer for new payment information.

2.5.3 Object Models

tdentifylng Classes
The first step in building an object model of the system is to identify the classes
inv_olved !n the use ca~es. This step _is perhaps one of the more perplexing tasks in
obJect-onented modeling. The questions arc What can /Je a class? and What f eatures
(that is, fields and methods) should a class have?
2.5 Case Study: An E-Bookstore • 41

The answers largely depend on the domain and the problem to be solved. The
following are some simple guidelines. Classes represent entities, not actions, and can
represent many different types of entities, including
• physical objects, such as equipment, devices, and products;
• people, such as students, faculty, and customers, and the roles they play;
• organizations, such as universities, companies, and departments;
• pl~ces, such as buildings, rooms, and seats;
• events, such as mouse clicks, service requests, and purchase orders; and
• concepts, such as multidimensional spaces, transactions, and weather maps.
Actions should be modeled as the methods of classes. A simple rule is that class names
should be noun phrases and that method names should be verb phrases.
Only the classes and features of classes that are relevant to the problem to be
solved need be included in the model. One of the most straightforward methods of
identifying classes is the following:
• Underline the verbs and nouns in the description of the requirements and use
cases.
• The nouns are the candidates for classes or attributes, and the verbs are the
candidates for methods.
By identifying the nouns in the core requirements of thee-bookstore, we can at
least identify the foUowing classes:

Class Description

EBookstore The entire system


Customer The customers of the e-bookstore
Book Books, a type of merchandise offered by the e-bookstore
MusicCD Music CDs, a type of merchandise offered by thee-bookstore
Software Computer software, a type of merchandise offered by thee-bookstore

Furthermore, by analyzing the de cription of the u e case Shop, we can identify


the following additional classe :

Class Description

Shoppi ngCart A temporary IL t of title that a cu tomer intends to buy


Order An order placed by a cu tomer
Addr ess Customer's addre .
----======================:=::::::=====---===-===•
Object-Oriented Modeling Using UML

Identifying the Features of Classes


In this phase, we try to identify all the relevant fields and methods for each class that
we have identified from the use case analysis. For example, for thee-bookstore, let's
start with the Customer classes. We identify the fields by analyzing the requirements
and the use cases to find the relevant fields associated with customers.

Customer
name
customerlD
password
shippingAddress
billingAddress

The list of fields may still be incomplete, but it is a start. As we go through more
iterations, additional fields that belong to the Customer class should become clear.
At this stage, it isn' t necessary to completely specify the type or visibility of each
field. The key issue here is what fields a class should have.
Toe fields shippingAddress and billingAddress in Customer represent
addresses. This observation naturally leads to a new class Address. The shipping-
Address and billingAddress fields are both instances of the Address class.
To identify the fields of the Address class, we simply rely on common sense and
knowledge of actual forms of addresses.

Address
street
city
state
country
postalCode

Now, let us move on to classes representing merchandise items, including books,


mu ic CDs, and computer software.
2.5 Case Study: An E-Bookstore • 49

Book
MusicCD
title Software
author title
publisher artist title
yearPublished publisher publisher
edition yearPublished yearPublished
volume volume version
ISBN ISBN ISBN
price price price

Obviously, some fields are common to all three classes. The commonalities are
not coincidental because when calculating the total price of a mixed order of books,
CDs, and software, each item should be treated the same way. The three classes then
-
should be subclasses of a common superclass, and the common fields of the three
classes should belong to their common superclass. Thus, another way of identifying
classes is by identifying the commonalities among existing classes and extracting
the commonalities to a common superclass. This process is known as generalization.
The following are the revised classes after extracting the common fields to the Item
superclass:

Item
title
publisher
yearPublished
ISBN
price

'~

I I
Book MusicCD Software

author artist version


edition volume
volume

Identifying Relationships among Classes


Now let u consider the ShoppingCart clas , which eems imply to contain a set
of it;ms. However. _ome customer might want to order multiple copies of the same
item. A • hopping cart therefore hould be abl_e to repre ent t~e quantity of each item
ordered. Thu. , an auxiliary cla Order Item 1 needed. Each mstance of Order Item
---c==========================~=====-=~=------==•
50 • Object-Oriented Modeling Using UML

contains the item and the quantity ordered. An instance of ShoppingCart contains
a set of instances of Order Item. \ 1

1 '

ShoppingCart .. A
[V Orderltem

Orderltem items[*] Item item


int quantity
addltem(ltem item, int quantity)
removeltem(ltem item)
changeQuantity(ltem item, int quantity)
I~
I Item I
The Order class represents orders placed by customers. Each order contains the
set of items that have been ordered, which are stored in the shopping cart, along with
the information about the customer, various fees, the total amount of the order, and the
payment method information. The Payment class represents information on methods
of payment, such as credit card type, card number, expiration date, and so on.

*
1
Order \ C~stomer \
Customer customer
1 1
ShoppingCart items \ ShoppingCart \
float salesTax
float shippingFee
1
float total
Payment payment
* \ ,Payment I
I

Now, let us consider the main class EBookstore, which represents the entire
system. Obviously, the system has to keep records of a variety of things, including
all the items on sale, all the customers, and all the orders that have been placed. The
relationships among the classes identified so far are shown in the following diagram.

EBookstore Customer

1 1
Order

Item
• 1
Orderltem ShoppingCart

Book MusicCD Software


Chapter Summary • 51

This step completes the initial version of the object model of thee-bookstore. An
initial object model captures the most important and obvious aspects of the system
but is often incomplete. Several iterations are usually necessary to derive a complete
object model. A useful technique for deriving complete analysis models is through
the analysis of responsibilities of classes and collaboration among classes. The idea is
to enumerate and analyze all the use case scenarios, assign responsibilities to classes,
and identify the collaborators of each class. This usually leads to identifying new
responsibilities for existing classes or identifying new classes. The process continues
until all the responsibilities have been assigned and all the collaborators have been
identified.
In the e-bookstore example, additional responsibilities and classes can be identi-
fied by analyzing additional use cases and scenarios, such as new customer registra-
tion, and by elaborating the requirements for inventory management. order process-
ing, and so on. The perivation of a more complete object model of the e-bookstore is
left as an exercise.

,,

■ Object-oriented modeling involves describing the essential and relevant aspects


of the problem domain and the problems to be solved in terms of objects, classes,
and relationships among them.
■ An object represents anything in the real world that can be di tinctly identified.
An object has an identity, a state, and a behavior. A class represents a set of
objects with similar characteristics and behavior. The e objects are called the
instances of the class. A class characterize the structure of tate and behaviors
that are shared by all its instances.
■ The fundamental principles of object-oriented development are modularity, ab-
straction, encapsulation, and levels of ab traction. Their purpo e i to reduce
complexity and enhance flexibility.
■ The basic criteria for decompo ition are two well-known but elu ive concept in
software development: cohe ion and coupling. Cohe ion refers to the functional
relatedness of the entities within a module. Coupling indicate the interdepen-
dency among different module .
■ Cla diagrams are the mo t common diagram for modeling the tatic tructures
of object-oriented software y tem and various type of relation among the
cla ses. The relation hip among clns e and interface depicted in class diagrams
include inheritance (which include exten ion and implementation), association
(which includes aggregation ,Uld compo ition), and dependency.
■ The dynamic behavior · of software y tem can be modeled by sequence dia-
gram and tate diagrams. The , equence diagram depict object interaction by
highlighting the time ordering of method invocation . The tale diagrams depict
the flow of control u. ing the con ept of tates and transition .
---■===========:::::::==::::::::===::::==::::::::::::========~=•

Object-Oriented Modeling Using UML

■ Use case and u e case diagrams are used for modeling the requirements of
the system to be developed and•for deriving object-oriented models from the
requirement . They provide important links between requirements and object-
oriented model . U e cases describe the externally observable behavior of a
system function, in the fonn of interactions between the system to be developed
and the external entities, known as the actors of the system. Use cases are intended
to de cribe what the ystem does, not how the system does it.

FUITHII IIADIMeS

Booch, G. (1 994). Object-Oriented Analysis and Design with Applications. Addison-


We ley.

Booch, G., J. Rumbaugh, and I. Jacobson (1999). The Unified Modeling Language
User Guide. Addison-Wesley.
Fowler M., and K. Scon (1997). UML Distilled: Applying the Standard Object
Modelling Language. Addison-Wesley.
Larman, C. (2002). Applying UML and Patterns, 2nd ed. Prentice Hall.

OMG (2001). OMG-Unified Modeling Language, vol. 4, http:/ /w.TW. omg. org
/uml
Rumbaugh, J., l Jacobson, and G. Booch (1999). The Unified Modeling Language
Reference Manual. Addison-Wesley.

IXIICIIII
2.1 Based on your experience of using bank ATM a. Determine the actors and use cases of the sys-
machine . precisely model the behavior of an tem, and describe the relationships among them
ATM machine u ing tate diagrams. The stale using use case diagrams.
diagrams bould de cribe at least the following b. Elaborate each of the use cases. Describe the
scenario : scenarios in each of the use cases.
• Cash depo it c. Identify the classes of the system, and describe
• Cash withdrawal the responsibilities and the features of each
• Checking account balance class.
d. Determine the relationships among the classes,
and u e class diagrams to depict these relation-
2.2 Based on your experience of using portable ships.
mu ic CD player , preci ely model the behavior
of a portable mu ic CD player u ing state
2.3 An airline ticket reservation ystem. The
diagram .
sy tem should allow a customer to specify
Develop an object-oriented model for each of the the origin and destination of travel, preference
following y tetru de cribed in E.xerci cs 2.3- 2.5. on the departing and returning dates, the

CHAPTER 3

Introduction to Java

CHAPTER OVERVIEW
In this chapter, we discuss the key cha@cteristics of Java and examine its run-time archi-
tecture. We use two very simple programs, an application and an applet, to illust@te the
basic structure of Java programs and use of the Java development toolsand environment.

Java is an object-oriented programming language that wa developed by a re earch


team led by Jame Go ling at Sun Micro y tern . Ja a i uirable for developing a
wide range of applications, including graphical user interface (GUI ) and multime-
dia, network and di tributed application , databa e and enterpri. e applications, and
numerical computation . Java' popularity i due partly to the fact that it is largely
based on proven technologies. It key characteri tic are that the language is

• Object-oriented. Java i one of the mo t recent in a family of object-oriented


programming languages. It not only upport object-oriented programming in
th strongest ense, a , do malltalk and Objective-C, but it al o prohibit many
·'bad" programming tyle and practice ,.

55
56 •
---
Introduction to Java

■ Distributed. Java is designed for developing di tributed applications, which con-


sist of multiple autonomous programs residing on different computers, or hosts,
in a computer network and cooperating with one another. It provides a variety of
mechanism to support communication and interoperability for both server and
client application .
■ Platform independent. The most unique characteristic of Java is that compiled
Java program can run on almost all platform with functionally identical behav-
ior. Thi capability make Java an ideal choice for operating in heterogeneous
network , uch a the Internet, and eliminates the need for porting programs to
different platform .
■ Secure. The Java run-time environment can execute Java programs in a so-called
sandbox, which consists of a limited set of resources on the host computer. Java
program can be strictly confined within the sandbox, so that a potentially mali-
c ious program cannot penetrate, compromise, or otherwise cause harm to the host
computer. This capability is essential to distributed or Web-based applications,
o n both the client and server side.
From the software development perspective, Java is a superior programming
language because it supports many features that facilitate the development of large-
cale and reliable software systems. These include strong type checking, packages,
exception handling, and automatic memory management (i.e., garbage collection).
The e features make software development u sing Java much more effective and
manageable than using many other languages.

3.1 AN OVERVIEW OF THE JAVA 2 PLATFORM

The fir t official release of Java, JDK 1.0, took place in January 1996. Since then,
there have been a number of major releases of Java, each of which offered significant
enhancements and improvements to the preceding release. JDK 1. 1 was released in
February 1997. It introduced many significant changes from JD K 1.0, some of which
are not backward compatible with JDK 1.0. Today, JDK 1.0 is obsolete, while JDK
1. 1 remain at the core of the subsequent releases and is compatible with subsequent
re lea e . A number of key technologjes were introduced in JDK 1.1, including security
and authentication, object serialization, remote method invocation (RMI), and Java
bean s. These technologies have become the foundation of many other technologies
introduced in later relea es. In many ways, JDK 1.1 was the coming of age for Java.
One of the most significant milestones of Java was the release of the Java 2 Platform,
that is, JDK 1.2, in December 1998 (Figure 3 .1). The Java 2 Platform introduced
three separate edit.ion of Java: Standard Edition (12SE), Enterprise Edition (12EE),
and Micro Edition (12ME). 1 Each Java platform edition defi nes a set of tec hnologies

I. J2EE and J2ME were announced in December 1998 but were not released until June 1999 at the Ja va011 e
Developer Co 11f erence .
3.1 An Overview of the Java 2 Platfonn ■ 57

Figure 3.1 Service and enterprise


computers
The Java 2 Platform.
(From RJggd et al. Desktop and personal
(2001 ]. Program-
ming Wireless De- '.
Optional
computers

vices with the Java High-end consumer


packages
2 Platform, Micro devices
Edition. Addison-
Optional
Wesley.) packages Low-end consumer
devices
Java2
Enterprise Smart
Java2 Personal profile ] cards
Edition
Standard
(J2EE)
Edition ----'--
(J2SE)
Foundation profile [ MIDP ]
~

'--___, _ __ [ CDC ]8 Java card


APls

Java virtual machine

--
Java 2 Micro Edition (J2ME)

that aim at a particular category of computing devices: Java virtuaJ machine that fit
the type of computing device, libraries and Application Program Interface (APls)
specialized for the type of computing device, and tools for deployment and device
configuration.

Java 2 Platform Standard Edition The Java 2 Platform Standard Edition (J2SE) is
for developing application that run on de ktop computer . Such computer usually
have reasonably powerful proce sor with an adequate amount of memory and disk
space and serve a single user.
J2SE include the following component :

■ Java virtuaJ machine to execute Java program .


■ Development tool , including compiler, debugger, profiler, and documentation
generator.
■ The core API , including core language upport, utilitie , input and output, net-
working, preferen e , olle tion , ecurity, lo ale upport, logging, Java bean ,
XML proce ing, and native interfa e.
■ Graphi al user interfa e (GUI) APt~, in luding AWT, Swing, two-dimen ional
graphi s, and sound supp rt.
58 • Introduction to Java

■ Integration APls, including remote method invocation (RMI), database connec-


tivity (JDBC), naming and directory services (JNDI), and common object request
broker architecture (CORSA).
■ Deployment tool : Java Web start, and Java plug-in.

Thi book primarily focuse on technologie in J2SE.

Java 2 Platform Enterprise Edition Java 2 Platform Enterprise Edition (J2EE) is


for developing erver applications that erve the needs of customers, suppliers, and
employee of enterpri e and organizations of all izes. Such applications u ually run
on powerful erver and can erve a large number of users simultaneou ly.
J2EE i built on top of J2SE and i a superset of J2SE. It includes the following
component in addition to the components in J2SE:

■ Java ervlets and Java server pages (JSP).


■ Enterpri e Java bean (EJB).
■ E-mail and messaging services.
■ Tran action managemenL

Java 2 Platform Micro Edition Java 2 Platform Micro Edition (J2ME) is for de-
veloping applications that run on small, resource-constrained devices, such as palm-
top computers, cellular phones, and television set-top boxes. Compared to desktop
computers, such devices usually have much less powerful processors, very limited
memory pace, and small screens.
J2ME defines two configurations to target two broad categories of devices:

■ Connected Device Configuration (CDC) for high-end consumer electronic de-


vice such as TV set-top boxes and automobile entertainment/navigation systems.
Such device u ually have several megabytes of available memory with high-
bandwidth network connections.
■ Connected, Limited Device Configuration (CLDC) for low-end information de-
vices such as PDAs and cellular phones. Such devices may have only a few
hundred kilobytes of available memory with low-bandwidth, intermittent net-
work connections.

Each configuration also define a number of profiles to addre s the special need
of pecific families of device . One of the profiles in CDC is the Personal Profile, al o
known a Per onal Java, which upports a subset of the clas librarie upported by
J2SE. One of the profiles in CLDC is the Mobile Information Device Profile (MIDP)
for cellul ar phones.
A number of Java virtual machines are available on different types of device
including the KYM , the K-Virtual Machine, which runs on a number of PDAs and
cell phones.
3.2 The Java Run-Time Architecture ■ 59

3.1 THE JAVA RUN-TIME ARCHITECTURE


---- Java is designed for distributed computing in a heterogeneous network environment,
such as the Internet and the World Wide Web. The primary goals of Java's run-time
architecture design are platform independence, security, and efficiency.

Platforms
The Internet is a heterogeneous, open, and self-organizing anarchy. It consists of a
vast number of computers with different CPUs running different operating systems.
The combination of the CPU and operating system of a computer is referred to as the
platform of the computer,2 which defines the key characteristics of the operating envi-
ronment of that machine. Popular platforms include the Sun Spare CPU with the Sun
Solaris operating system (Sparc/Solaris); the Intel Pentium CPU with Linux operating
system (Intel/Linux); the Intel Pentium CPU with the Microsoft Windows operating
system (Intel/Windows); and the PowerPC CPU with the MacIntosh operatjng system
(PowerPC/MacOS).
Programs usualJy are platform specific; that is, a program developed and com-
piled for one platform will not automaticalJy run on a dilfereot platform. unJe s the
two platforms are compatible or one platform can emulate the other.3 Mo t of the time,
when a program needs to run on different platforms, special version of the program
must be developed and compiled for each environment. Converting a program to run
on a different platform is called porting. In general, porting i a nontrivial task. One
of Java's advantages is that it is platform independent; Java programs are designed to
run on different platforms without porting.

Security
Owing to the open nature of the Internet, computers connected to the Internet are all
susceptible to attack from malicious program in a\ ide variety of form . An attacker
who manage to break into a computer could acce or modify data. eave drop on
messages includjng e-mail and online tran action u ing credit card , or even cau e
irrecoverable damage to the oftware and hardware on the computer.
A completely ecure but open environment i unattainable. However, some effec-
tive methods in network technology and cryptography ha e been developed to reduce
sigruficantly the ri ·k of ecurit breache . The Java run-time environment is de igned
to prevent potential ecurity breaches while nmning Java programs on both the client
and erver machines.

2. omctimcs. the phrases /1e1n/m1rt' ple1tfon11 and svfrwart! p/11tjc>m1 :.ire usc<l to refer to the CPU and the
opcr;lling s stem of the rnmputcr, I'\' ·pcctively.
3. There arc II number of DO /Winu0, · emulators for MacO and olaris.
60 • Introduction to Java

3.2.1 Program Execution Models

There are two conventional models for executing programs: compilation and inter-
pretation.

Compilation
In compilation, a compiler tran lates, or compiles, the source code of a program into
machine code, allowing the program to be executed directly by the operating systems
and the hardware. The compilation approach is invariably used in programming
languages intended for developing large-scale, efficient, and reliable systems, such as
C, C++, Ada, and Eiffel. The compilation approach has several important advantages:

■ Modem compilers perform extensive static analysis, such as strong type checking
and datafiow analysis, to detect many potential errors in programs at compile
time.
■ Modem compilers apply sophisticated optimization techniques, such as common
expression extraction and loop strength reduction, before generating machine
code. Optimization produces efficient code and eliminates the need for hand
optimization, so programmers can focus on the functionality and maintainability
of the programs, not their efficiency.
■ Executing machine code generated by compilers is far more efficient than inter-
preting source code directly.
■ For commercial software vendors, software can be delivered to customers in
binary executable form, thus protecting the source code as a trade secret.

The main deficiency of the compilation approach is that the executables are
platform dependent. Two options are available for allowing a program to run on
different platforms, but neither one is appealing. The first option is to port and compile
the ource code for each platform. However, porting source code to different platforms
is tedious, time-consuming, and error-prone. In addition, the number of different
platforms is potentially large, and porting to all of them may not be economically
feasible. This approach is adopted for the widely available Netscape browser, which
is mostly written in C and ported to every major platform.
The econd option is to provide source code and porting instructions to allow
customers to port programs themselves to the platforms they use. This approach shifts
the burden to the customers and causes the original developer to lose control over
software quality and compatibility. This approach also exposes the source code to
outsiders. However, it is often adopted for noncommercial or open-source software
products, such as the Linux operating system. There are large-scale and industrial-
strength software systems that have been ported to different platforms in this fa hion.

Interpretation
In interpretation, an interpreter directly par es and executes the ource code of a
program without generating machine code. The main advant age of the interpreta-
tion approach is it quick turnaround of edit- run cycles while developi ng the code.
3.2 The Java Run-Time Architecture ■ 61

Figure 3.2
Java source code

Execution of Java
programs.
Java compiler platform
independent

Java byte code

,------- --- --- - ,-


'I 'I
byte-code I
I byte-code : Java chip ,
Interpreter compiler I
L------ ------- •
I

Java machine
CPU1 native
machine code
platform
Java virtual machine dependent
- - - - - ' - - -- I
CPU2 :
L----=--_J I
- - --- - -- -- -- - -'
Java virtual machine

Thus the interpretation approach is often adopted for programming language used
for prototyping, or rapid application developmem (RAD), uch a BASIC, LISP, and
Smalltalk.4 This technique requires no compilation, and error can be di covered only
at run time. The interpretation approach i platform independent becau e programs ex-
ist only in their source form. However, oftware product developed with interpretive
languages must be delivered a ource code. The main di advantage of the interpre-
tation approach are the lo of tatic analy i and code optimization and the much
slower execution speeds than those generally achieved with compiled executable .

3.2.2 Java Virtual Ma,:hine

Neither of the conventional approache to program execution an accompli h platfom1


independence and effi iency. Java adopt a different approach to executing program ,
which i a compromi e between the conventional ompilation and interpretation
approaches, a illustrated in Figure 3.-. Java program. are executed in two stage .

4. omc of these languages ul ·o ha e l'Ompiler ·. fore. ample, licrosoft Visual BASIC includes both an
interpreter an<l a compiler.
62 • Introduction to Java

Stage 1-Compilation of the Source Code to Byte-Code


Java is a compiled language. However, unlike the conventional compilers, the Java
compiler compiles the source code to the machine code of the Java virtual machine
(JVM) [Lindholm and Yellin 1996]. The JVM is an abstraction of the CPU of a
real computer. It shares many common characteristics of a real CPU and thus can
be implemented on a real CPU in a relatively straightforward way. The machine code
of the JVM is known as byte-code.

Stage 2-Execution of Byte-Code


Byte-code is platform independent and can be executed on different platforms. There
are three ways of executing Java byte-code:

1. Interpretation. On a given platform, an interpreter of the Java virtual machine


interprets the NM instructions and executes them.5 This approach is adopted in
original NM from Sun, known as the classic JVM.
2. J/T compilation. On a given platform, a Ju st-in-Time (JIT) compiler compiles the
Java byte-code to the native machine code on the fly and then executes the native
machine code. Most modern NMs support TIT compilation.
3. Direct execution. Special platforms can be built by using Java chips, which
use Java byte-code as their native machine code. On such platforms, Java byte-
code can be executed directly without a compiler or interpreter. Java chips are
most commonly used in embedded electronic devices, such as TV set-top boxes,
personal digital assistants (PDA), mobile information devices (MID), and cellular
phones.

The Java execution model essentially maintains all the advantages of the compila-
tion approach and accomplishes platform independence. The execution of byte-code
is far more efficient than the direct interpretation of source code, but it still is not as ef-
ficient as the direct execution of native machine code.6 With further improvements in
byte-code optimization and compilation, as well as improvements in the JVM, the ef-
ficiency of Java will approach that of compilation languages targeting native machine
code, such as C++.

Java Byte-Code
Java byte-code refers to the instructions of the Java virtual machine. A JVM instruc-
tion consists of a I-byte opcode, short for operation code (hence the name byte-code),
and zero or more operands, which are the parameters of the instruction. Operands

5. Sometimes, we si mply use lhe lcnn Java vinual machine Lo refer to the interpreter of lhe Java virtual
machine.
6. J?va ~asn' t lhe fi~l to_be based on this approach. P-code for PASCAL developed al the Un iversity of
Callfom1u _ut San Diego in the lale 1970s was lhe first platform-independent, or porwble, intermediate
code for high-level languages. A few other languages. uch as Emacs LISP, also compi le source code to
byte-code.
3.2 TM Java Run-Tlme Architecture • 63

vary in length, and the number and lengths of the operands are determined by the
opcode.

opcode (1-byte)
operand1
operand2

The core of the NM involves a loop that can be described roughly as follows :
do {
fetch the opcode byte of the current instruction;
fetch the operands, depending on the value of the opcode byte;
execute the instruction;
} while ( notdone );

The JVM executes byte-code much as a simple reduced-instruction-set computer


(RISC) CPU does, using several 32-bit registers.

■ The pc (program counter) register contains the address of the next instruction to
be executed.
■ The opt op register points to the top of the operand stack.
■ The vars register points to a set of local variables of the current method.
■ The frame register points to the execution environment structure.

The JVM also uses a garbage-collected heap to store aJJ objects at run time.
The JVM instruction talce operands from the operand stack, operate on them, and
return the results to the stack. The operand tack is 32 bits wide. The JVM in tructions
perform the following functions:

■ Stack manipulation
■ Array management
■ Arithmetic and logical operations
■ Method invocation and return
■ Exception handling
■ Synchronization of multiple thread ,

A detailed pec ification of the JVM and the format of the . class file are
presented in Lindholm and Yell in [ 1996].

Applications and Applets


We will first deal with two t pes of Java program : applications (app for short) and
applets. An application i a full -fledged program with full acce to y tem resource .
An applet i a program embedded in a \Vcb page \ ith re lricted acces to ystem
rcsourc to pre ent se urity brcache · to the ho t that nm the applet.
>4 • Introduction to Java

A Java application must be explicitly invoked, like any other program, by issuing
a command on the command line or electing a command from a menu. Therefore,
an app can be invoked only by an authorized u er of a host, unle s the ecurity of
the host has already been compromised. Java imposes no restrictions on the behavior
of apps and their access to the host environment. Presumably, authorized users are
responsible for the consequences of executing applications.
Applet are embedded in Web pages and are invoked automatically when the
Web page containing the applets are loaded by Java-enabled browsers. Most of the
time, a user is unaware of the contents and behavior of the applets embedded in a Web
page before it i loaded in a browser. Thus, ensuring that executing applet will not
compromise the security of the host on which the browser and the applet are running
is imperative.

Security of JVM
Java virtual machine provides several layers of defense against potential security
attacks through applets.

Shielded memo,y addresses: The Java language does not allow direct manip-
ulation of memory addresses. There is no way for someone to access a
specific memory cell or forge a pointer to an address. Memory allocation
and deallocation are handled automatically and transparently by the JVM at
run time.
Verification of byte-code: The JVM enforces verification of the byte-code before
it is executed. Verification involves loobng for any improper structures and
control flows in the byte-code, any violation of access restrictions, and any
violation of the type system. The purpose of the verification is to ensure that
the byte-code is the output of a legitimate Java compiler, is generated from
a consistent version of the source code, and has not been tampered with.
Run-time security manager: While executing an applet, the JVM consults a
security manager whenever a potentially insecure operation is about to be
performed. The security manager decides whether to allow the operation.
The security manager is customizable to allow implementation of different
security policies. In fact, browsers from different vendors implement slightly
different security policies.

The security policies enforced by the security manager are configurable. Com-
monly implemented security policies include the fo11owing restriction on applet :

• An applet usually is not allowed to read or write files on the ho t that i executing
it. Some browsers allow certain exceptions.
• An applet is not allowed to communicate with hosts other than the one that it
comes from .
• An applet i not alJowed to start other programs, execu te operating sy ·te m
commands, or inquire about certain ystcm propertie of the host.
3.3 Getting Started with Java ■ 65

These Java security measures form the so-called sandbox, within which all
applets are confined. McGraw and Felton [1997] discuss the security issues involvin
Java applets in detail. Schneier [1996] gives an excellent general introduction t~
cryptography and computer security.

3.3 GETTING STARTED WITH JAVA


I

In this section, we present two simple Java programs-an app and an applet- to
illustrate the basic structures of Java programs and the fundamentals of compiling
and running Java programs.

3.3.1 A Simple Java Application

As the Internet has reached almost everywhere on Earth, the next logical step would
be an interplanetary Internet. NASA is planning to have the first Internet host beyond
planet Earth online early in the twenty-first century. Imagine bow exciting it would
be to receive a greeting from a close neighbor-Venus.

EXAMPLE 3.1 The "Hello from Venus!" Application.

PURPOSE
To illustrate the basic structure of Java programs.
DESCRIPTION
Thi application displays a greeting message: Hello from Venus! The me age is in
plain text and is written to the standard output (that i , the command con ole from
which the application is invoked).
SOLUTION
Assuming that you have in tailed the Java Development Kit on your y tern, here are
the teps to build and run the Java application.
1. Type in and save the following Java ource code in a file named Hello• java.

"Hello from Venus!" application: Bello ._Java


II FIiename: Hello.java
I"*
* A Java app that prints the message: "Hello from Venus!"
*/
public class Hello {
public static void main (String[] args) {
System.out.println ("Hello from Venus!");
r System.out refers to the standard output ·/
}
}
66 • Introduction to Java

2. Compile the Java source code, using the Java compiler j avac: 7

venus¼ javac Hello . java

If the compilation is successful, a file named Hello. class will be generated.


This is the Java byte-code file.
3. Execute the application by invoking the Java virtual machine j ava:

venus¼ java Hello


Hello from Venus!

Note that the argument of j ava is the class name, not the file name, that is,
without the extension . j ava or . class.

Basic Structure of Programs


The "Hello from Venus!" app illustrates two essential elements of Java programs:

1. A program-application or applet--comprises one or more classes. In this case,


the program comprises a single class named Hello.
2. An application must contain a class that includes the main () method. Similar to
the main O function of C and C++ programs, the main() method is the entry
point of a Java application. The main() method must be declared as shown in
Example 3.1.

public static void main (String[] args) {


{body of the main() method)
}

In this case, class Hello has only one method-the main () method. (See
Section 4.4.5 [p. 113] for discussion of the main O method.)

The main () method of the Hello class simply prints out the following message
to the console, or the standard output:

Hello from Venus!

System. out refers to the standard output. The println () method prints out its
argument as a string with a new-line character appended to the end.

Source Files
Java programs are stored in files . Unlike many other languages, Java enforces certain
rules about how classes should be placed in files and how files should be named. A
simplified version of the rules is as follows:

■ All Java source files must have the extension . j ava.

7. Throughout Lhis book, venus¼ is used Lo stand for the command prompt in a console or shell window.
3.3 Getting Started with Java • 67

■ Usually, each file should contain a single class. However, Java allows a single
file to contain multiple classes, but with certain restrictions. (See Sections 4.5.1
and 6.1 [p. 134] for details.)
■ The file name should match the class name, including the capitalization. Java is
case sensitive.

These rules make it easy to locate the source code of a class by simply listing or
searching the file names.

Comments
There are three types of comments allowed in Java programs:

C-style comments: C-style comments are delimited by /* and */. C-style


comments can span multiple lines. All characters between the delimiters,
including the delimiters, are ignored by the Java compiler.
C++-style comments: C++-style comments begin with// and extend to the end
of the line. C++-style comments are short one-liners. All characters from//
to the end of the line are ignored by the Ja~a compiler.
Java documentation comments: Documentation comments are delimited by /**
and*/. Documentation comments serve a specfal purpose. They are used
by a documentation generation utility called j avadoc to generate HTML-
style documentation automatically by extracting information from the source
code. AJI characters between/** and */, including the delimiters, are ignored
by the Java compiler. (We discuss the use of documentation comments and
the j avadoc tool in Section 4.5 [p. 134].)

Example 3.1 illustrates the use of all three types of comments.

3.3.2 A Java Applet

Java applications must be invoked from command con oles. Java applets can be
embedded in Web pages. They are downloaded via the World Wide Web and invoked
by Java-enabled browsers.

EXAMPLE 3.2 The "Hello from Venus!" Applet

PURPOSE
To illustrate the basic structure of Java applet and the ba ic graphics capability of
displaying text and choosing font, size, tyle, and color.

DESCRIPTION
This applet display. the greeting me sage Hello from Venus! graphically. It consists
of a text message and an image of the planet Venu .
68 • Introduction to Java

SOLUTION
Here are the steps to build and run the Java applet:
1. Type in and save the following Java source code in a file named
HelloFromVenus.java.
Hello from Venus! ap let: BelloFromVenus. ja.ve:
import java.awt.•; -"')~(CA.Ql\\(S _ '" b o,-:}--
V11port ⇒ ~va.applet . Applet; --=) nlC\-ycQ_~ ~ v ~'
public class HelloFromVenus extends Apple {
public void paint (Graphics g) { ,
Dimension d = getSize ( ) ; comr'YKtlVc.Q:l-e. u) ¥) fG~
r L
.' 6 ~e:
~

g.s~tColor(Color.b~ack); . / ~\-r::e.-i\'Y'("f\\
g.fillRect(O,O,d .width,d.height); .~ ·
g . setFont(new Font("Sans-serif", Font.BOLD, 24));
g. setColor (new Color (255, 215, 0)) ; // gold color
g . drawString("Hello from Venus!", 40, 25);
_g . draw Image (get Image (getCodeBase ( ) , "Venus. gif") ,
~J l'nS ~¾ -\-0 :::C(-e,a) 20, 60, this);
}
}

2. Compile the Java source, using the Java compiler to generate the byte-code file
HelloFromVenus.class.
venusi. javac HelloFromVenus.java

3. Type in and save the following HTML source in a file named HelloDemo. html.
We may name the HTML file anything we want to.

HTML IOUl'Ce: BelloDemo. htal


<html>
<head>
<title> Hello from Venus Applet </title>
</head>
<body bgcolor=black text=vhite>
<center>
<applet code•"HelloFromVenus.class"
vidth•300 height•360>
</applet>
</center>
<hr>
<a href• "HelloFromVenus.java">The source.</a>
</body>
</html>

Be sure that the HTML file, the Java source file , and the byte-code file are in the
same directory. The image of planet Venus is stored in a separate file named
Venus. gif. Be sure to place the image file in the same directory with the other
files.
3.3 Getting Started with Java ■ 69

Figure 3.3

The Hello from


Venusf applet.

'
'Applet started.

4. One way to view the applet is to use the applet viewer in JDK:

venus¾ appletviewer HelloDemo .html

Note that the argument of appletviewer is the HTML file name.


5. Another way to view the applet is to use a Java-enabled browser, such as the
Netscape Communicator:

venus¾ netscape HelloDemo .html

The result of the applet is shown in Figure 3.3. •


Basic Structure of Applets
The "Hello from Venu !" applet illu trate . everal e ential elements of Java applets.
In general, an applet must extend the Applet class (either directly or indirectly),
does not need a main() method, and should have the paint O method. An applet is
quite different from an application. It i • indirectly invoked through the applet viewer
or, more commonly, n Java-enabled browser. An applet is not a stand-alone program.
Therefore the main( ) method is not needed. An appkt i. in oked in an applet context,
70 ■ Introduction to Java

Figure 3.4 X
(0,0)
j ~
Viewing area of
applets and the
Java coordinate
system.
y height

I I

- --
Width

usually a browser. An applet must interact with its applet context according to a set
of prescribed conventions (that is, contracts).
The contract regarding the paint () method of an applet includes the following: 8
■ Each applet is assigned a rectangular region on the Web page in which it i
embedded. The dimension of this rectangular region is set in the <applet> tag
in the HTML file.
■ An applet implements the paint () method to paint the appearance of the applet
in the rectangular region. The origin (0, 0) is located at the upper left corner of
the rectangular region, as shown in Figure 3.4.
• The paint () method is invoked by the applet context whenever the applet is
active and the rectangular region designated to the applet becomes visible. This
statement implies that the paint () method will be invoked when the applet is
initially loaded in a browser.

Later, we discuss in detail most of the language features and classes involved in
this applet. A cursory understanding of what is going on in the paint () method of
the HelloFromVenus applet is sufficient at this point. Line by line, each statement
in the paint () method
1. gets the dimen ion of the rectangular region designated to the applet;
2. sets the pen color to black;
3. fills the entire rectangular region with black color, which paints the background;
4. ets the font to ans serif, boldface style, and 24-point size;
5. set the pen color to gold;
6. draw the text me age at the top in gold color; and
7. draw the image of the planet Venus below the mes age. The image i tored in
a file named Venus. gif .

~- T~e paint O m~lhod is declared in the Component eta s, which is a superclass o f Applet . Sec
ccuon 5.5 for deLa1ls about the paint () method.
3.3 Getting Started with Java • 71

Embedding Applets in Web Pages


Applets are embedded in Web pages by using the applet tag, which is included in
the official specification of HTML.9 The simplest form of the <applet> tag is
<applet code = byrecode-filename
width = pixels
height = pixels>
</applet>

consisting of three required attributes, which are key-value pairs.

Attribute Description

code Specify the name of the byte-code file containing the applet
width Specify the width of the applet in pixel
height Specify the height of the applet in pixels

The <applet> tag for the HelloFromVenus applet i highlighted in the


HelloDemo . html file in step 3 of Example 3.2. For a complete description of the
<applet> tag, see "Summary of the Applet Tag" [p. 645].

Placing Applets on the World Wide Web


To make your applets accessible on the World Wide Web, you imply put Web pages,
the applet class files, and other related files (e.g., image or audio file ) on a host that
is connected to the Internet and has a Web server (that is, an HTTP server).
An applet may involve several files. For example, an applet might have everal
class files, as well as image and audio file . To run an applet that re ides on a
remote server requires that all the related file be downloaded to the host that is
running the applet. By default, a ingle connection to a remote HTTP erver will
download only one file. Therefore, an applet that involve multiple files requires
several connection to the ame erver in order to download all the file needed to run
the applet. Multiple connection to the remote erver involve ignificant overhead and
may cause ubstantial delay in downloading the fil e . An archive and compression
utility, called jar, i provided in the JDK. U ing j ar, we can pack all the fi le involved
in an applet into a ingle compre ed archive fi le. Hence, the fi le needed to run an
applet can be downloaded with a ingle connection to the remote erver, reducing
download time and network load.
The Hello from Venu ! applet contain two file : the cla file Hell oFromVenus
. class and the image file Venus . g i f . To take ad antage of the j ar utility, we fir t
pack these two file ,
venus¾ jar cf Hello.jar HelloFromVenus .cl ass Venus . gif

9. Rnggctt cl al. (_000) give · n rnmpklc and definitive de cription of HTML 4.0, the current tandard.
The most up-lo-dnlc info rmation about HTML can be obtnincd from the w -' Consortium al 11•11•w. wJ .org.
72 • Introduction to Java

which creates a Java archive (JAR) file named Hello.jar .10 In order to use the JAR
file, we must specify the JAR file name in the <applet> tag, using the archive
attribute:

<applet code="HelloFromVenus.class
archive•"Hello . jar"
width=300 height=350>
</applet>

A browser that supports the archive feature will download the JAR file instead
of the class file. The class and image files will be extracted from the JAR file.

COMMON PROBLEMS AND SOLUTIONS

Symptoms Possible Causes and/or Fixes

You are unable to invoke JDK tools such Be sure that the PATH environment variable
as javac, java, or appletviewer. includes the JDK bin directory.
You get deprecation messages while The programs contain features that are
compiling programs. deprecated. The programs were developed
for an older version of Java compiler than
the one you are using. As of version 1.4,
deprecation is only a warning, so programs
using deprecated features should still run
without problems. However, you should
remove deprecated features from programs
because they may not be supported in future
JDK releases.
Your applets run fine when you use the Be sure that the version of JVM in your
applet viewer but do not run in a browser. browser is equal to or higher than the version
of JDK you used to compile your programs.
When transferring files to a Web host via
FfP, be sure that you transfer the class
files in binary mode and that the file names
are intact, not truncated or converted to all
uppercase.

10. Use of command-line utility jar is similar to use of the UNIX tar utility. Its syntax is
jar [option s] destination input-files
See JDK tools documentation for details.
Further Readings
• 73

CHAPTER SUMMARY

• Java is object-oriented, distributed, platform independent, and secure.


• The Java 2 Platform consists of the Standard Edition (J2SE) for deskto 1·
. · h E • .. P c tent
app I1cat1ons, t e nterpnse Ed1t1on (J2EE) for server applications, and the M'
Edition (J2ME) for applications on mobile, handheld, and embedded device~~ro

• The primary design goals of the Java run-time architecture are platform indepen-
dence, efficiency, and security.

• The Java run-time ~chitecture is a compromi e between the conventional ap-


proaches of executmg programs through compilation or interpretatjon. Java
source code is compiled to byte-code and executed by the Java virtual machine
(JVM).

• The JVM is an abstract computing machine that execute Java byte-code. It can
be implemented as interpreters, just-in-time (ITT) compilers, or Java chip -
hardware implementations of the JVM. The in truction of the Java virtual ma-
chine are called byte-code. The Java compiler compiles Java source code to Java
byte-code and stores the resulting code in files with the extension . class.

• The two common types of Java programs are application and applets. Applica-
tions (apps) are full-fledged Java program with full acce to y tern resources.
Applets are programs that are embedded in Web page with re tricted acce to
system resources to prevent break-ins to the ho t that run the applet .

FURTHER READINGS

Arnold, K., and J. Gosling (2001). The Java Programming language, 3rd ed.
Addison-We ley.

Ka em, N., and Enterpri e Team (2000). Designing Enre,prise Applicarions wirh
Java 2 Platform, Emerprise Edirion. Addi on-\Ve ley.

Lindholm, T., and F. Yellin ( I996). Th e Ja\'{I Virwal Machine. Addi on-Wesley.

d
McGraw, G., and E. W. Felten ( 1997). Jal'(l ecuriry- Hosrile App/ers, Holes, all
A111idotes. John \Viley & on. .

Riggd, R., A. Taivalsaari and M. Vandenbrink (200 I). Programming Wireless De-
vices with the Ja\'(l 2 Platform, Micro Edition. Addi on-Wesley.
74 • Introduction to Java

EXERCISES

3.1 Write a Java application similar to the 3.2 Develop an applet similar to the HelloFrom-
Hello . Java application in Example 3.1. This Venus applet in Example 3.2. Your new applet
new program should contain a class named should display an image of your choice. Change
MyFavori tePoem. It should call System . out the color, size, and textual display of the image
. println () to display a poem that you like. as necessary.
(Hint: Look ahead to the next chapter for infor-
mation on the Java printing features that can be
used to display this poem neatly and elegantly.)
Elements of Java

CHAPTER OVERVIEW
In this chapter, we discuss the basic elements of Java, including operators and expres-
sions, data types, garbage collection, control structures, class decla@tion, pa@meter
passing, packages, and exceptions. We also discuss some of the more commonly used
classes in the Java class library. We present several simple programs to demonstrate string
manipulation and basic input and output ope@tions. Finally, we develop a simple ani-
mation applet.

Java i a recent entry in the evolution of object-oriented programming language .


It has benefited tremendously from the le on learned from ir predece, ors, most
notably C++ [Ellis and Strou trup 1990: Strou trnp 1994: Strou trup 1997), Small-
talk [Goldberg 1985; Goldberg and Rob on 1983], Objecti e C [Pin on and Wiener
1991], Modula3 [Harbi on 1992], Eiffel [Meyer 1992: Meyer 1997], and Ada [Booch
1987]. Java is a well-de igned language compri ing con truct that have been proven
effective in other language . Java bear , ome resemblance to ir direcl ance tor-
e++. However, their . imilaritie are largely ntactical and uperficial, whereas their
differences are more fundamenral . One of the mo r striking differences between Java
and C++ is in lhe ir design philosophie . C++ is de igned ro be a rather comprehen-
sive, expressive, m1d permi . ive language. A high priority for C++ i to allow vel)'
efficiellf implementalion. Many of ir fearure · \ ere developed with good intention

75
76 • Elements of Java

and sound justifications but are often subject to misuse and bad programming prac-
tices, partly because of backward compatibility with C. In contrast, Java is designed
to be ( 1) more selective and restrictive-to prevent bad programming practices, even
at the cost of sacrificing expressiveness and convenience in some cases; (2) easy to
use, and versatile; and (3) suitable for a variety of tasks, including object-oriented
programming, numeric computation, and system programming.
Java differs from other object-oriented programming languages in several impor-
tant respects:
■ Unlike C++, Java is purely object-oriented. All nonstatic methods in Java are
polymorphic (i.e., virtual in C++ terminology). It does not allow global variables
and stand-alone functions, as C++ does.
■ Unlike Smalltalk, in which everything is an object, including values of primitive
types, such as int and char, in Java, values of primitive types are not objects.
■ Unlike C++, Java does not support multiple inheritance in general. Like Object-
ive-C, only a limited form of multiple inheritance, accomplished by the use of
interfaces, is supported in Java.
■ Unlike most other procedural and object-oriented programming languages, in
Java, the goto statement was eliminated.
■ Unlike most other programming languages, Java utilizes 16-bit characters instead
of 8-bit characters to support internationalization.
■ Java supports exception handling, automatic memory management (i.e., garbage
collection), and multithreaded programming. These mechanisms are considered
to be essential in object-oriented programming.
Perhaps the most notable feature of Java is its support for distributed computing.
Unlike most other programming languages that depend on platform-dependent add-on
utilities to support distributed computing, Java provides built-in langu·a ge constructs
and standard libraries for distributed computing.

LEXICAL ELEMENTS

Lexical elements are the basic building blocks of programming languages. We begin
with a look at Java's characters, identifiers, and literals. Then we discuss the operators
and expre sions used in Java.

4 .1 .1 Character Set

Java programs are written in Unicode, 1 an international standard of 16-bit character


ets that contain encoding of characters of most languages used in the world today.

I . Tire
.
Unicode
. .
Standard
.
2.0 is defined in !The Unicode Consortium

1996] • un·11:0
. de reI ated ·1111•or-
mauon. rncludrng vanous tables and programs, can be obtained from lllc u,11· d C .
CO C UnSllf'tl Ulll 'II
http : //wvw . unicode . org. '
4.1 Lexical Elements • 77

The commonly used 7-bit ASCII character set is equivalent to the first 128 characters
of Unicode, known as the ISO Latin-12 character set, also known as IS0-8859-1.
The Java development environment can be localized to accommodate many different
locales. A locale is a country or region with distinct characteristics in culture and
language. The most widely distributed version of the Java Development Kit (JDK)
is localized to U.S. English. It performs the conversion between ASCII and Unicode
characters on the fly; that is, the U.S. English version of JDK reads and writes ASCII
files by default.

4.1.2 Identifiers

Identifiers are used in Java programs to denote the name of classes, methods, variables,
and so on. A Java identifier can begin with a letter, followed by letters or digits. In
Java, letters include the characters in the alphabets of all languages in Unicode, the
underscore (_), and the doIJar sign ($). Examples of Java identifiers are

MyClass value! _aField A$B

Because Java programs are written in Unicode, you may u e the following as
identifiers in Java:3

jifj" 1tOAUyo><JcrtKO

4.1.3 Primitive Types and Literals

The following primitive types are defined in Java:

■ Boolean type: boolean


■ Integer types: byte, short, int, and long
■ Character type: char
■ Floating-point types: float and double

The constant values of each primitive type are expre ed a lirerals.

Boolean Type
The boolean type consi t of two boolean literal : true and false. In Java, the
boolean type i not compatible with integer type . The re ult of comparisons are
of type boolean, not integer as in C and C++. The condition of control statements,
uch as if and while tatements ( ee Se tion 4.3.5 [p. 96] and 4.3.6 [p. 96]), are
e pe t d t be of type boolean, not integer type .

2. I O ·1ands for lntcma1ional tuntlardization Organization.


3. The ·c un: the word '"mullilingunl" in Chinese nnd Greek, respec tively.
--- -
78 • Elements of Java

Integer Types
Java provides several integer types of different sizes.

Type Size Minimum Value Maximum Value

byte 8-bit -128 127


short 16-bit -32768 32767
i nt 32-bit - 2147483648 2147483647
long 64--bit - 9223372036854775808 9223372036854775807

However, Java provides no unsigned integer types. Integer literals can be written
in decimals, octals, and hexadecimals.
• A decimal integer literal begins with a nonzero decimal digit, followed by
decimal digits (e.g., 30).
• An octal integer literal begins with a leading zero (0), followed by octal digits
(e.g., 036).
■ A hexadecimal integer literal begins with a leading Ox or OX, followed by
hexadecimal digits (e.g., 0x1E and OX1e ).
The default type of integer literals are int . An integer literal may be followed by an
integer type suffix, 1 or L, to indicate that it is of type long (e.g., OxlL). The uppercase
letter Lis preferred, since the lowercase letter 1 could be easily mistaken as digit 1
(one).

Aoating-Point Types
Java provides two floating-point types.

Type Size Description

fl oat 32-bit Single precision IEEE-754 floating-point


double 64--bit Double precision IEEE-754 floating-point

Floating-point literals are written as a decimal number with an optional exponent


part.
Digits . [Digits) l(elE) Signedlnteger)

Some examples of floating-point literals are


23 . f .5 0.0 3.1415 le- 9 le+9 1E12
4.1 Lexical Elements • 79

The default type of floating-point literals is double. A floating-point literal may


be followed by a floating-point type suffix to indicate its type:

■ Letter f or F indicates that the type of the floating-point literal is float.


■ Letter d or D indicates that the type of the floating-point literal is double. (This
is optional.)

Some examples of floating-point literals with floating-point type suffix are

1e-9f 1e+9f 1E12F 1e-9d 1e+9d 1E12D

Character Type
Most programming languages provide a single-byte (8-bit) character type that is ca-
pable of encoding up to 256 printable characters and control characters. Although
it is adequate for Western languages, which all have small alphabets, it is quite
inadequate for some Oriental languages, such as Chinese and Japanese. Modem Chi-
nese consists of more than 15,000 characters, of which some 6,000 are in common
daily use. For languages with large alphabets, a double-byte character type capa-
ble of encoding as many as 65,536 printable characters and control characters is
needed.
In the past, the vast majority of commercial software was developed and available
only in English. As nations' economies become more and more global, the huge
market potentials of non-English-speaking countries, such as the People's Republic
of China, have prompted commercial software vendors to port their software to
other languages. Microsoft, for example, released its Windows operating ystem in
several different languages simultaneously. Owing to the different size requirements
of characters, however, porting the English version of software (v here 8-bit characters
were used) to Oriental languages (where 16-bit characters are necessary) bas proved
to be extremely tedious and costly.
To support the internationalization of software. Java provide a single character
type char that is 16-bit unsigned and uses the Unicode character encoding.

Character Literals
ASCil characters can be written directly (e.g., a or C). Non-ASCII characters can be
written in their hexadecimal or octal codes.

■ In hexadecimal code, \ u i followed by four hexadecimal digits, as in \ u00E6


(re in French), and \u5496 \u5561 ( •~~, "coffee" in Chinese).
■ In octnl code,\ i foJlowed by one to three octal digit , as in \040. Octal character
ode may not exceed \377 (\u00FF).
------- ... · •-

-
80 • Elements of Java

The following special characters can be written by using escape sequences.

Escape Sequence Unicode Description

\n \uOOOA New line


\t \u0009 Tab
\b \u0008 Backspace
\r \uOOOD Carriage return
\f \uOOOC Form feed
\\ \u005C Backslash
\' \u0027 Single quote
\" \u0022 Double quote

Character literals such as the following appear between single quotes:


' a' ' C'
'\u00E6' '\u5496' '\u5561'
'\040'
'\n ' '\t' ' \\' '\' I
1\11 I

String Literals
The String type is not a primitive type in Java. It is a class. However, string constants
can be written as literals. A string literal consists of a sequence of characters, including
escape sequences, enclosed by a pair of double quotes, as in the following examples.

String literal Value

"A string l iteral" A string literal


"\u5469 \u5561" ,jrll;j~
"\" A quote\"" "A quote"

See also "String Concatenation" in Section 4.1.4 [p. 84] and Section 4.4.8 [p. 118)
for operations on strings.

4.1.4 Operators and Expressions

Java provides operators that are very similar to those in C and C++. These operators
and expressions are summarized in Table 4.1 . The leftmost column indicates the
precedence of the operators, with l as the highest precedence and 14 the lowest.
A 11u111eric type is either an integer or a floati ng-point type. All operator that can be
4.1 Lexical Elements ■ 81

applied to integer types can also be applied to type char. All the binary operators,
except the assignment operators, are left-associative. The assignment operators are
right-associative. Note the following examples:

Expression Interpreted As Reason

u + (v * w) * has higher precedence than +.


X - y + Z (x - y) + z + and - have the same precedence and are left-
associative.
a = b = c a = (b = c) Assignment operators are right-associative.

Arithmetic Operators
The following arithmetic operators can be applied to all integer and floating-point
types.

Infix Binary Prefix Unary

+ Addition + Positive sign


- Subtraction - Negative sign
* Multiplication
/ Division
% Remainder

Integer addition, subtraction, and multiplication follow the cu tomary rules.


Integer division truncates the result toward zero. The integer remainder i defined as

X 4 y == X - (x / y) * y

The following examples show integer division and remainder and their relation-
ship:

Expression Result Relationship

7 I 3 2 -- * 2 + l
7--
7 ¾3 1
(-7) I 3 -2 (- 7) = = 3 * (- 2) + (- 1)
(-7) %3 - 1
82 • Elements of Java

TAILl4.1
Operators and Expressions

Expression Operand Types Description

1. exp++ Numeric Postfix increment; result is the value before


exp-- Numeric Postfix decrement; result is the value before
2. ++exp Numeric Prefix increment; result is the value after
--exp Numeric Prefix decrement; result is the value after
+e.xp Numeric Unary positive
-exp Numeric Unary negative
-exp Integer, boolean Bitwise complement
!exp Boolean Logical negation
3. expl * exp2 Numeric Multiplication
expl / exp2 Numeric Division
exp I i. exp2 Numeric Remainder, modulus
4. expl + exp2 Numeric Addition
String String concatenation
expl - exp2 Numeric Subtraction
5. exp I « exp2 Integer Left shift, filling with Os
exp 1 » exp1 Integer Signed right shift, filling with the highest
bit
expl »> exp2 Integer Unsigned right shift, filling with Os
6. expl < exp2 Numeric Less than
exp!> exp2 Numeric Greater than
expl <= exp2 Numeric Less than or equal to
exp I >= exp2 Numeric Greater than or equal to
7. exp1 == exp2 Any Equality
exp1 != exp2 Any Inequality
8. exp I & exp2 Integer, boolean Bitwise and
9. expi A exp2 Integer, boolean Bitwise exclusive or (xor)
10. exp 1 I exp2 Integer, boolean Bitwise inclusive or
11. exp1 && exp2 Boolean Conditional and
12. exp1 11 exp2 Boolean Conditional or
13. exp 1? exp2 : exp3 exp 1: boolean Conditional expression
exp2, exp3: any
4.1 Lexical Elements ■ 83

Operators and Expressions (continued)

Expression Operand Types Description

14. var= exp Any Assignment


var+= exp Numeric, string
var -= exp Numeric var op= exp
var*= exp Numeric is equivalent to
var/= exp Numeric var= (var) op (exp) except that
var%= exp Numeric var is evaluated only once
var<<= exp Integer
var >>= exp Integer
var >>>= exp Integer
var !i.= exp Integer, boolean
var-= exp Integer, boolean
var I= exp Integer, boolean

Java integer arithmetic never overflows or underflows. lf a value exceed the


range of its type, it will be wrapped modulo the range.
For expressions x / y and x %y, an Ari thmeticException i thrown when y
is O (see Section 4.6 [p. 139] for a discussion of exceptions).
Java floating-point arithmetic conforms to the IEEE-754-1985 tandard [IEEE
1985]. One advantage of this type of arithmetic is that an exception will not be
generated under any circumstance. In other words, your program will not crash, even
when you divide a floating-point number by zero. The IEEE-754 tandard define
two magic numbers: infinity and NaN (repre enting 11ot a number). Floating-point
arithmetic is closed with the addition of infinity and NaN. Two rule govern floating-
point multiplication and division:
1. If neither operand is NaN, then the re ult are a follow :

X y X I y X * y

Finite ±0.0 ± ±0.0


Finite ± ±0.0 ±
±0.0 ±0.0 NaN ±0.0
± Finite ± ±
± ± NaN ±
±0.0 ± ±0.0 NaN
-
84 • Elements of Java

2. If either operand is NaN, then the result is NaN.

The floating-point remainder is defined much the same as the integer remain-
der, or

X t. y == X - (x / y) * y

See 'The Float and Double Classes" [p. 130] and "Mathematical Constants and
Functions" [p. 13 l] for floating-point constants and commonly used mathematical
functions.

String Concatenation
The + operator can also be used to concatenate two strings. If one of the operands is
a string and the other is of some other type, the nonstring operand will be converted
to a string representation of its value and concatenated to the string operand, as in the
following examples:

Expression Result

"object" + 11
-
11
+ "oriented" Object-oriented
"object" + '-' + "oriented" Object-oriented
"Mail Stop"+ 205 Mail Stop 205
123 + ' ' + "Oak Street" 123 Oak Street

The conversion of primitive type values to their string representations follow s


customary conventions. The conversion of objects to their string representation
is carried out by the toStringO method, which we discuss in Section 4.4.8
[p. 122].

Increment and Decrement Operators


The increment operator++ and the decrement operator -- can be applied to all integer
and floating-point types. Both operators can be either prefix or postfix. For example,
i ++ and ++i increment the value of i by 1; i -- and --i decrement the value of i
by 1. The result of the postfix increment or decrement expression is the value of i
before the increment or decrement, respectively. The result of the prefix increment or
decrement expression is the value of i after the increment or decrement, respectively.
For example, if we let the initial value of i be 8, the effects of the postfix/prefix
and increment/decrement expressions are as shown in the following table. (These
expressions are evaluated independently, not consecutively.)
4.1 Luical Elements • 85

Expression Result of the Expression Value of i Afterward

i++ 8 9
++i 9 9
i-- 8 7
--i 7 7

Relational Operators
The equality operator == and the inequality operator ! = can be applied to any type.
The comparison operators < Oess than), <= Oess than or equal to), > (greater than),
and>= (greater than or equal to) can be applied only to numeric types. All relational
expressions yield boolean results. The meaning of relational expressions on prim-
itive types follows customary conventions. We discuss the meaning of equality and
inequality of reference types in Section 4.2.3 [p. 91 ].

Logical Operators
The logical operators ! (negation), && (and), and 11 (or) can be applied to operands
of type boolean. The binary logical operators are conditional. That is, in expression
exp 1 && exp2, exp2 is evaluated only when exp 1 evaluates to true. Similarly, in
expression exp 1 11 exp2 , exp2 is evaluated only when e.--cp 1 evaluate to false.

Bitwise and Shift Operators


Bitwise operators can be applied to both integer and boolean types. The following
bitwise operators are supported in Java:

Bitwise Expression Description

-x Bitwi e complement of x
X &y Bitwise and of x and y
X I y Bitwise inclusi e or of x and y
X - y Bitwi e exclu ive or of x and y

Unlike the conditional logical operators, when the bitwise operators &, \, and - are
applied to operands of type boolean, both operand are always evaluated.
-
86 • Elements of Java

The shift operators can be applied only to integer types. The following shift
operators are supported in Java:

Shift Expression Description

x « k Shift the bits in x k places to the left, filling in with Obits on the
right-hand side.
x » k Shift the bits in x k places to the right, filling in with the highest
bit (i.e., the sign bit) on the left-hand side.
x >» k Shift the bits in x k places to the right, filling in with Obits on
the left-hand side.

The following examples illustrate use of the bitwise and shift operators:

Expression Description

-OxO All bits are set to 1.


Oxl « k All except the kth bit are set to 0.
-(Oxl « k) All except the kth bit are set to 1.
x « k The result is x * 2k.
x >> k The result is x/2k.

Conditional Operators
A conditional expression takes the form

where exp 1 must be of type boolean, and exp2 and exp 3 can be of any type. The
value of this conditional expression is exp2, if exp 1 evaluates to true, and exp 3, if
exp 1 evaluates to false . Examples of conditional expressions include the following:

Expression Description

(x < y) ? x : y The mjnimum of x and y


( x >= O) ? x : - x The absolute value of x
4.2 Variables and Types ■ 87

Assignment Operators
An assignment operator is either the simple = or one of these:
+= -= I= <<= >>= >>>= I=
These operators are formed by concatenating a binary operator with =. The assignment
expression

var op= exp

is equivalent to
var= (var) op (exp)

except that var is evaluated only once in the original expressfon. When var is a
simple variable, these two expressions are always equivalent. However, they may
yield different results when var has side effects. These typically occur when array
indexes are involved. Let us assume that a is an integer array and that the initial value
of i is 2. The assignment expressions a [i ++] += i and a [i ++] = a [i ++] + i will
have different effects:

Expression Effect

a[i++] += i a[2] = a[2] + 3


a[i++] = a[i++] + i a[2] = a[3] + 4

The folJowing are examples of assignment expressions:

Expression Description

b i= (Oxl « k) Set the kth bit of b to l.


b &:= -(Oxl « k) Set the kth bit of b to 0.

VARIABLES AND TYPES

A type denotes the et of all the legal value of that type. A variable refer to a location
in memory where a value can be tored. Each variable is associated with a type. The
variable type re tri ts the values that the variable may hold. Variable and their types
are declared in variable de larations.
88 • Elements of Java

4.2.1 Variable Declarations

The basic syntax of variable declarations is


Type VarName 1 [•lnitia/Va1"e 1]. VarName 2 [•lnitialValue 2] .•• ;

It declares one or more variables, VarName 1, VarName 2 , •.. , to be of type Type.


When a declaration contains more than one variable, the variables are separated by
commas (, ). Optionally, each variable can be assigned an initial value. A variable
declaration determines the type and the scope of the variables in the declaration at
compile time. We discuss the scope of variables in more detail later (see Sections 4.3 .3
[p. 95] and 4.4.6 [p. 116]).
Java supports two kinds of types: primitive types and reference types. We dis-
cussed all the primitive types in Section 4.1.3 [p. 77]. A primitive type variable holds
a value of the type, whereas a reference type variable holds a reference to an object
I
or array.

Default Initial Values


Each type has a default initial value. These values are used in initializing objects and
arrays. The default initial values of various types are summarized as follows:

Type Default Initial Value

Integer 0
Aoating-point O. O
char \uOOOO
boolean false
Reference null

4.2.2 Type Compatibility and Conversion


Type compatibility is an important relationship between types.

Definition 4.1 Type Compatibility


Type Ti is compatible with type T2 if a value of type Ti can appear wherever a value
of type T2 is expected, and vice versa.

Type conversion is the conversion of values of one type to values of another type.
We discu s lbe conversion of primitive types here and the conversion of reference
4.2 Variables and Types • 89

types in Section 5.2.2. Conversions between different numeric types are allowed.
There are two different forms of conversion.

Definition 4.2 Widening and Nallowing (of Numeric Types)


Converting a numeric type of a smaller range to a numeric type of a larger range is
called widening. Converting a numeric type of a larger range to a numeric type of a
smaller range is called narrowing.

The sizes of ranges of numeric types are ordered from small to large:

byte short int long float double

Examples of widening include converting int to long or int to double.


Examples of narrowing include converting long to int or float to long. On the
one band, widening is carried out implicitly whenever necessary. On the other band,
narrowing may result in overflow or loss of precision. Therefore, explicit casts are
necessary for narrowing.
inti= 10 ;
long m = 10000L;
doubled= Math . PI; // the value 3.1415926...
i = (int) m; // narrowing, cast necessary
m = i; // widening, no cast necessary
m = (long) d ; // narrowing, cast necessary
d = m; // w idening, no cast necessary

4.2.3 Reference Types

A reference type is a class type, an interface type, or an array type. A reference


variable (i.e., a variable of a reference type) may bold reference to objects or arrays:•
A reference variable may also hold a special value, null, which indicate that no
object or array is being referenced.
References in Java are implemented a 32-bit pointers. Reference variable do
not directly hold values, as variable of primitive type do. In tead, reference variables
hold indirect references to object or array in tances, as illu trated in Figure 4.1. Java
references are very different from pointer in C and C++ in two re, pect . First, in
C and C++, pointer can be cast to any type, modified through pointer arithmetics,
and assigned arbitrary values. In Java, none of that i allowed: Java prohibits the
direct manipulation of reference variable . Second. in C and C++. pointers usually
reference chunk of memory that are dynamically allocated from n heap. Programmer
assume full responsibility for managing allocation and deallocation of memory. In

-1. Anny ure actunlly objt'l'l' too; sec i:ction 5.- ,'.! [p. 1701.
-
-
90 • Elements of Java

Figure 4.1 Primitive type Reference type

Primitive and refer- int i; Point p;


ence types.
i = 100; 1 100 p = new Point() ; ~ : . . _ __ __J

p : Point
X = 0
Garbage-collected y = 0
heap

.•····

Java, reference variables point to memory space that is dynamically allocated from
a garbage-collected heap. Programmers are thus freed from the responsibility of
managing the deallocation of memory.

Garbage Collection
Garbage collection refers to a mechanism that automatically deallocates unreferenced
or unreachable objects (i.e., garbage). It is one of the most important features of Java.
Garbage collection not only simplifies the programming task, but it also eliminates
one of the major sources of faults in programs involving memory leaks and dereferenc-
ing invalid pointers, which are common and notoriously difficult to trace and remove.
Garbage collection significantly reduces the amount of effort and time associated with
programming, testing, and debugging.
Whether to use garbage collection is perhaps one of the most hotly debated issues
in programming language design. Despite its obvious and significant advantages,
garbage collection imposes rather severe penalties on the performance of programs
of which it is a part.
Most garbage collection techniques involve exhaustive examination of the entire
memory space, either all at once or rotating through segments of the memory space
one at a time. Even with various improvements, such as generational garbage collec-
tion, garbage collection techniques are inherently time-consuming. Those that do not
involve exhaustive exarnination of memory space, such as reference counting, often
incur per-use penalties (i.e., a small penalty every time a variable is referenced), which
can become a significant cumul ative penalty. Techniques such as reference count-
ing also suffer from various limitations, such as circular references. In additi on, the
garbage collection proces could start at any moment, the timing of which is unpre-
4.2 Variables and Types ■ 91

dictable and usually beyond the control of the programmer. Therefore, programming
languages containing garbage collection are not suitable for developing hard real-
time systems that require the assurance of completing certain tasks within a specified
period of time.
To summarize, Java does not require the programmer to keep track of objects
created and destroy these objects when no longer needed. Java uses garbage collection
to manage automatically the deallocation of objects. Garbage collection does impose
some additional demands on hardware components. But, with the continuing increase
of CPU speed and drop in CPU and memory costs, the penalty on performance
becomes at least acceptable, if not negligible, for the vast majority of software
systems. The benefits of garbage collection far outweigh any penalty on performance
imposed by this mechanism.

Creating Objects and Arrays


Declaring a variable of a primitive type creates a storage location for its values.
However, declaring a variable of a reference type does not create a storage location
for an object or an array. Only a storage location for the reference is created. The
object or array to which the reference variable refers must be created. or allocated,
using the new operator or an initializer.

Point p; II declare a reference variable of type Point


p : new Point O ; II create a new instance of Point
int[] ia; II declare a reference variable of an array of integers
i a : new int[3]; II create an integer array of size 3

See Sections 4.2.4 [p. 91] and 4.4.2 [p. 104) for the details of creating objects
and arrays.

Equality of References
For two reference variables rl and r2, the expres ion rl = r2 te ts to the equality
of the two references, not the equality of the swtes of the objects or array referred
to by them. In other words, r1 == r2 test the identity of rwo objects or arrays. To
test the equality (of the state ) of two object or array , the equals () method hould
be used, such as rl. equals (r2) . See the di cu sion of the equals O method in
Section 6.3.2 [p. 228].

4.2.4 Arrays

An array variable holds a reference to an array. An array i a fixed-size sequential


collection of element of identical type . Array are created with either the new
operator or array initializers. The torage pa e for array is a.llocated from the
garbage-collected heap.
92 • Elements of Java

One-Dimensional Arrays
The elements in a one-dimensional array are indexed by the integers O to n - 1,
where 11 is the size of the array. A one-dimensional array of size n can be created
and initialized with either of the following methods:

1. Using the new operator:

new Type [ 11 ]

In this case the size of the array must be specified, and all the elements in the
array are initialized to the default initial values (see Section 4.2.1 [p. 88]) based
on the type of the elements.
2. Using the one-dimensional array initializer:

The values u0, u 1, ... , un-l are the initial values of the elements in the array. The
size of the array is determined by the number of initial values provided in the
array initializer.

The following are some examples of creating and initializing one-dimensional


arrays:

int ial D = new int [3] ; Create an integer array of size 3; all elements
are initialized to 0.
int ia2 D = { 1, 2, 3}; Create an integer array of size 3; elements
are initialized to 1, 2, and 3, respectively.
Point pa □ = new Point [10] ; Create an array of points of size 10; all
elements are initialized to null.

Let a be a one-dimensional array; the common operations on arrays are as


follows:

Operation Description

a. length The length of array a


a[i] The ith element of array a

Examples 4.1 [p. 97], 4.2 [p. 98], and 4 .9 [p. 133] illustrate the use of one-
dimensional arrays.
One of the main differences between the arrays in Java and those in C and C++ is
that Java arrays are always bound-checked at run time. Array bound-checking will au-
LomaticaJly detect a major source of faults in programs, that is, exceeding the bounds

4.3 Statements • 93

of arrays while accessing or manipulating their elements. An Index0ut0fBounds-


Exception will be thrown at run time when a program attempts to access an array
with an out-of-bound index (see Section 4.6 [p. 139] for a discussion of exceptions).

Multidimensional Arrays
A multidimensional array is treated simply as an array of arrays. Let a be a k-
dimensional array; the elements of a can be accessed using the following syntax:

a U1J U 2 J ... [ik J

where n 1, n 2 , ..• , nk are the indices.


A k-dimensional array can be created with either of the following methods:
1. Using the new operator:
new Type [ n I J [ n 2 J . . . [ n k J

The size of each dimension is n 1, n 2 , ••• , n k, respectively. All the elements in the
array are initialized to the default initial values (see Section 4.2. l [p. 88]) based
on the type of the elements.
2. Using the k-dimensional array initializer:

where each / 1, fi, ... , Ik is a (k - })-dimensional array initializer.

The following are examples of creating and initializing two-dimen ional arrays:
double matl[J [J = new double[4J [SJ;

Create a 4 x 5 two-dimensional array. All elements are initialized to 0.0.


int mat2[J [J = {{1, 2, 3} , {4, 5, 6}};

Create a 2 x 3 two-dimensional array. The element are initialized a


mat2 [OJ [OJ = 1, mat2 [OJ [lJ = 2, mat2 [OJ [2] = 3
mat2 [1J [OJ = 4, mat2 [1J [1J = 5, mat2 [1J [2J = 6

Example 4. 10 [p. 133] illustrate the u e of a two-dimen ional array.

4.3 STATEMENTS

The syntax and emantics of ' tatement in Java are nearly identical to those of C and
C++. There i one exception: The goto tatement was eliminated. A early as 1966,
Bohm and Jacopini [ 1966] pointed out that the goto tatement i. unneces. ary. That is,
any program containing goto ·tatement , can be convened to a functionally equivalent
program without goto tatements. ln 196 , Dijk tra made perhap. the best known
a ertion about the goto statement. con ·idering it to be hannful [Dijkstra 1968].
More than 30 years later. the goto statement remain in almo t every programming
--- -
94 • Elements of Java

language in use because occasionally it is useful. However, the designer of Java


eliminated the goto statement 5 by providing some sensible alternatives: multilevel
break and continue (see Section 4.3.7 [p. 99]) and exception handling (see Section 4.6
[p. 139]).
There are two major categories of statement in Java: the simple statement and the
compound statement. Simple statements are the basic building blocks of statements.
Compound statements are statements that are composed of other statements. Simple
statements include expression statements, local variable declarations, break state-
ments, continue statements, and return statements. Compound statements include
statement blocks, selection statements, loop statements, and try-catch statements.

4.3.1 Expression Statements

Assignment expressions and increment/decrement expressions can be made into


statements by appending a terminating semicolon. For example, the following are
expression statements:
X = 5;
m <<= k;
i ++;
- - j;

Expressions for object creation and method invocation can also be made into
statements by appending a semicolon to the expressions, as in
pointl = new Point() ;
poi ntl.move ( lO, 10);
i a = new int[3] ;

We discuss object creation and method invocation in Sections 4.4.2 [p. 104] and
4.4.4 [p. 107].

4.3.2 Statement Blocks

A statement block simply consists of a sequence of statements or local variable


declarations (which are also considered statements) enclosed by a pair of braces,
{ } . The statements in a statement block can be either simple or compound and are
executed sequentially:
{
Statement 1
Statement 2

Staremenln
}

5. The word goto remains a reserved word in Java, although it is unused.


4.3 Statements ■ 95

For example,

{
inti= O;
prime[i++] = 2;
prime[i++] = 3;
prime[i++] = 5;
}

4.3.3 Local Variable Declarations

Local variable declarations are also simple statements. They must be terminated by a
semicolon (; ). Local variable declarations can occur anywhere in a statement block
and can be intermixed with other statements. The variables declared are called local
variables. Each variable declaration has a scope, which is the extent of the code in
whkh the variable is visible, that is, accessible. The scope of a local variable begin
at its declaration and extends to the end of the immediate enclosing tatement block.
For example,

int i; // i's scope begins here


i = 10;
int j = 10; // j's scope begins here
i += j;

} // both i's scope and j's scope end here

Local variables are not automatically initialized. They mu t be a igned values


explicitly. It is a compilation error if a local variable is u ed before it ha been as igned
a value.

4.3.4 The return Statement

The return tatement terminate the execution of a method and return control 10
its caller. The yntax of the return tatement i

return [fapressio11] ;

The type of the expre ion mu t match the return type of the method that contains
the return tatement. The return tatement may not have an ex pres ion if the return
type of the method that contain the return tatement i void. (See Section 4.4.4
[p. l 07] for a di cu sion of method .)
-
96 • Elements of Java

4.3.5 Selection Statements


Java has two types of selection statements: the if statement,
if ( Condition ) if ( Condition )
Statement Statement 1
else
Statemellf2

and the switch statement,


svi tch ( Expression ) {
case CaselAbel 1 :
Statement 1

case Casel.Abeln :
Statementn
default :
Statemenrn+I
}

The expression following the svi tch keyword must be an integer expression.
The case labels in switch statements are different from statement labels. Case labels
must be constant integer expressions. Each branch in a switch statement may have
one or more labels. Although the default branch is optional, you should include the
default branch at the end.
The execution of a switch statement begins with evaluation of the integer
expression and comparison of the result with each case label sequentially. The first
branch with a matching case label will be executed. If none of the case labels match
the result of the expression, the default branch will be executed. If the default branch
is absent. the entire switch statement is skipped. Each branch is usually terminated
by a break statement, in which case execution continues with the statement following
the switch statement. If a branch is not terminated by a break statement, execution
will fall through and continue with the next branch of the switch statement. This
f all-through feature allows several branches to share common code.

4.3.6 Loop Statements

There are three types of loop statements:


The while wop
while ( Condition ) Statemellt

The do- while loop


do
Statemellt
while ( Condition )
4.3 Statements • 97

The for loop


for ( lnitExpr ; Condition ; lncrExpr )
Statement

The do-while loop shown is equivalent to the following:

Statement
vhile ( Condition )
Statement

wherein the two occurrences of Statement refer to the same statement.


The for loop shown is equivalent to the following:

lnitExpr;
vhile ( Condition ) {
Statement
lncrExpr;
}

The following are two simple examples using the loop statements to manipulate
one-dimensional arrays.

EXAMPLE 4.1 The Sum of an Array of Integers

PURPOSE
To illustrate the for loop statement and one-dimensional arrays.

DESCRIPTION
The following program calculates the sum of the values in the array.

SOLUTION
The sum of an~ of In :Saa.java
public class Sum {
public static void main(String[]args) {
int a[] : 1, 2, 3, 4, 5, 6, 7, 8 , 9, 10;
int sum== O;
for (inti: O; i < a.length; i++) {
sum+= a[i);
}
System.out.println("The sum is : 11
+ sum);
}
}

Running the program produces the following re ults:

venus¾ java Sum


The sum is: 55 •
98 • Elements of Java

EXAMPLE 4.2 Sorting a One-Dimensional Array Using Bubble Sort

PURPOSE
To illustrate nested loop statements and one-dimensional arrays.

DESCRIPTION
The following program- using the bubble sort algorithm-sorts the array in ascend-
ing order and prints the sorted array to the standard output.

SOLUTION

public class BubbleSort {


public static void main(String[]args) {
int a[] = { 21, 9 , 45, 17, 33, 72, 50 , 12, 41, 39 };
for (inti= a . length ; --i >= O; ) {
for (int j = O; j < i; j++) {
if (a~] > a~+l]) {
int temp= a[j] ;
a [j] = a[j + 1] ;
a[j + 1] = temp;
}
}
}

// print the sorted array


for (int k = O; k < a.length ; k++) {
System.out.println("a[" + k + "]: " + a[k] );
}
}
}

Running the program produces the following results:

venus% java BubbleSort


a[O] : 9
a[l]: 12
a[2]: 17
a[3]: 21
a[4]: 33
39
a [6] :
a(6] :41
a[7] : 45
a[8] : 50
a[9] : 72
4.3 Statements • 99

4.3.7 The break and continue Statements

Statement Labels
Each statement can have an optional label, which is simply an identifier. A labeled
statement has the syntax

[StatementLobel : ] Statement

Labels can be used in break and continue statement . In Java, although it is


legal to attach a label to any statement, it is only meaningful to attach a label to a
statement block, a switch statement, or a loop statement. In the following program
segment, Loop1 and Loop2 are labels:

Loop1: while (i-- > 0) {


Loop2: while (j++ < 100) {
I I ...
}
}

Breaking and Continuing Loops


A break statement abruptly terminate an enclosing compound statement. A con-
tinue statement completes the current iteration of one of the enclosing loop tate-
ments. The syntax of the break and continue statements i

break [Statementlobel] ;
continue [Statementlobel] ;

An unlabeled break statement must be enclosed in a loop tatement or a switch


statement. It terminates the immediate enclo ing loop or switch tatement. An
unlabeled continue statement complete the current iteration of the immediate
enclosing loop statement and start a new iteration.
A labeled break statement mu t occur within a labeled compound ta1ement,
which can be a loop statement, switch tatement, or a tatement block. The labeled
break statement will transfer control to the tatemenc immediately following the
labeled statement. A labeled continue tatement mu t occur within a labeled loop
statement. The labeled continue tatement complete the current i1eration of the
labeled loop statement and tart a new iteration.
Labeled break and continue tatement can be u ed to break out of a loop that
not the immediate enclo ing loop tatement. as for e, ample in

outer : while (cond1) {


inner : while (cond2) {
II ...
break;
II . . .
}
}
100 • Elements of Java

With the preceding pair of nested loops, the break statement will break out of
the inner loop but not the outer loop. Using a break statement to break out of an inner
loop and outer loop simultaneously also is known as multilevel breaking. Sometimes,
multilevel breaking is desirable. Let us consider the two-dimensional array

double matrix[][);

We try to determine whether all the values are nonnegative. The following is a solution
in C and C++, which uses a goto statement to break out of the nested loop.
boolean nonNegative = true ;
for (inti= O; i < matrix.length; i++) {
for (int j = O; j < matrix[i) .length; j++) {
i f (matrix[i) [j) < 0.0) {
nonNegative = false ;
goto Done;
}
}
}
Done : // . ..

One way of eliminating the goto statement requires a new boolean variable,
isDone, and a more complicated loop condition for the outer loop.

boolean nonNegative = true;


boolean isDone = false;
for (inti= O; !isDone kk i < matrix . length; i++) {
for (int j = O; j < matrix[i) . length ; j++) {
if (matrix[i] [j] < 0 . 0) {
nonNegative = false;
isDone = true ;
break; // breaks out of the inner loop
}
}
}

ln Java, however, the labeled break statement can be used to break out of the
outer loop.

boolean nonNegative = true;


OuterLoop :
for (inti• O; i < matrix.length; i++) {
for (int j • O; j < matrix[i) .length; j++) {
if (matrix[i) [j) < 0 . 0) {
nonNegative = false;
break OuterLoop; II breaks out of the outer loop
}
}
}

Using the labeled break statement, we can accomplish the same task without
introducing unnecessary boolean variables.
4.4 Class Declarations • 101

4.4 CLASS DECLARATIONS

Classes are the basic compilation units, that is, units that can be compiled individually,
of Java programs. A Java program consists of one or more class declarations. A class
declaration defines a class, which also defines a reference type.

4.4.1 Syntax of Class Declarations

A class declaration consists of the class name and a sequence of fields. methods,
and nested class declarations. The fields of a class are also known as the variables
or attributes of the class. The syntax of class, field, and method declarations is
summarized as follows:

[ClassModi.fiers] class ClassName


[ extends SuperC/ass]
[implements lnterface 1 , lnterface2 . . . ] {
ClassMembe rDeclarations
}

A class declaration may begin with a list of class modifiers, which are umma-
rized as follows:

<none> When no modifier is present, by default, the clas i accessible by all


the classes within the ame package (see Section 4.5.1 [p. 135]).
public A public class is accessible by any cla .
abstract An abstract class contains ab tract methods (see Section 7.2.1 [p. 252]).
final A final class may not be extended, that is. have subclas e .

Each file can contain only one cla s that i declared public. The name of the file
and the name of the public class it contain must coincide, and the file mu t have
the extension . j ava. For example, the cla declaration of public cla Point must
be stored in a file named Point . j ava. Each file may al o contain any number of
nonpublic classes.
The extends clau e pecifie the upercla of this clas . The implements
clause specifies the interface being implemented by thi cla s. We di cu extending
classes in Section 5.2 [p. 163], and implementing interface in Section 5.3 [p. I76).
The body of a class declaration con i t of a Ii t of member declarations. Member
declaration can be declaration of fields, methods, and nested classes. The fiel~,
method, and ne ' ted cla declaration can be intemuxed. The order of declarations is
immaterial to the compiler. Thu , they hould be ordered in a way that is most logical
and comprehen ible.
102 • Elements of Java

A method declaration takes the form

[MethodModijiers] ReturnType MerhodName ( [ParameterList] ) {


Statements
}

A field declaration takes the fom1

[FieldModijiers] Type FieldName 1 [= lnitializer 1], FieldName2 [= lnitializer2] . . . ;

A nested class declaration has the same syntax as the top-level class declaration .
Each class member declaration begins with an optional list of modifiers. Modi-
fiers that can be applied to method, field, and inner class declarations include

<none> When no modifier is present, by default, a member is accessible by


all the classes within the same package (see Section 4 .5.1 [p. 135)).
public A public member is accessible by any class.
protected A protected member is accessible by the class itself, all its subclasses
(see Section 5.2 [p. 163)), and all the classes within the same package.
private A private member is accessible only by the class itself.
static A static field is shared by all instances of the class. A static method
accesses only static fields (see Section 4.4.5 [p. 110)). A static nested
class does not have an implicit reference to the enclosing class (see
Section 4.4.6 [p. 114]).
final A final method may not be overridden in subclasses (see Section 5.2.3
[p. 174]). A final field has a constant value, which may not be changed
(see Section 4.4.5 [p. 112]).

Modifiers that can be applied only to method declarations include

abstract An abstract method defers implementation to its subclasses (see


Section 7.2.1 [p. 252)).
synchronized A synchronized method is atomic in a multithread environment
(see Section 11.2.1 [p. 557]).
native A native method is implemented in C and C++.

Modifiers that can be applied only to field declarations include

volatile A volatile field may be modified by nonsynchronized methods


in a multithread environment (see Section 11.2.1 [p. 557)).
transient A transient field is not part of the persistent state of the instances
(see Section 8.4.1 [p. 377]).

~nly one public, protected, or private modifier may appear in the modi-
fiers hst for each member. Member accessibility is summarized in Table 4.2.
4.4 Class Declarations • 103

Accessibility of Class Members

Public Protected Package Private

The class itself Yes* Yes Yes Yes


Classes in the same package Yes Yes Ye No '
Subclasses in a different package Yes Yes 0 0

Nonsubclasses in a different package Yes No No 0

* Yes: Accessible.
t No: Not accessible.

Method Declarations
The return type of a method is required. If a method does not return a value, the return
type should be specified as void. A method may take a list of parameter declaration
separated by commas (the list could be empty). Parameters are declared in the form

[final] Type ParameterName

A fi nal parameter is one that cannot be assigned a value inside the method (see
" Pitfall: Final Parameters" in Section 4.4.4 [p. 109]).
The following class declaration declares a simple Point cla , which repre ents
points in a two-dimensional space.

public class Point {


public double x, y;
public void move (double dx, double dy) {
X += dx; y += dy ;
}
}

Style Convention Class and Object Names


Cla sand interface name hould begin with upperca e letters, a in Point .
Field and method name hould begin with lowercase letters, a in po int.
If a name con ists of multiple word , it is fom1ed by concatenating the words and
capitalizing each word except the fir t, a in

■ a long cla name, CheckBoxMenuitem.


■ a lo ng object name, printOptionMenu.
104 • Elements of Java

4.4.2 Creating and Initializing Objects

There are four ways of initializing the fields of a class: an explicit initializer in a
declaration, default initial values, constructors, or an initialization block.

Explicit Initializer
A field can be assigned an initial value in the declaration with an explicit initializer.
For example, the x and yin the Point class can be explicitly initialized as
public double x = 0 . 0, y = 0.0;
Default Initial Values
lf the fields are not explicitly initialized, they are implicitly initialized to their default
initial values (see Section 4.2.1 [p. 88]).

Constructors
Another way to initialize fields is to use constructors. Constructors are special meth-
ods that have the same name as the class; their return type must be omitted. A class
may have multiple constructors, provided that they take different numbers of argu-
ments or arguments of different types.6 The following is an enhanced Point class
declaration with two constructors.
public class Point {
public double x, y;
public Point() { If no-arg constructor
X = 0.0; y = 0 . 0;
}

public Point(double init_x, double init_y) {


x = init_x; y = init_y;
}

public void move(double dx, double dy) {


X += dx; y += dy;
}
}

A constructor that takes no parameters is known as a no-arg constructor (short-


hand for a no-arguments constructor). Instances of a class can be created with the
new operator followed by an invocation of one of the constructors of the class. For
example,
Point pl .. new Point O ; // using the no-arg constructor
Point p2 .. new Point (20. 0, 20 . 0); // using the other constructor

lf no constructor is provided for a class, a default no-arg constructor with an


empty body is provided implicitly. In other words, even if none of the constructors

6. Constructors can be overloaded: see Section 5. 1 Ip. 159].


4.4 Class Declarations • 105

of the Point class in the preceding example were provided, creating instances of the
Point class with the no-arg constructor would still be legal, as in

Point p3 = new Point();

The instance p3 would be initialized to (0, 0) by virtue of the default initial values.
When constructors are provided explicitly, the default no-arg constructor is no longer
provided implicitly. It must be provided explicitly if it is desired. In other words, if the
no-arg constructor of Point class were removed but the other constructor remained,
new Point O would be illegal. Hence, in general, the no-arg constructor should be
provided for all classes.

Initialization Block
Fields can be also initialized by means of an initialization block, which is a statement
block directly enclosed in the class declaration. The initialization block is executed
before the body of any constructor is executed. The initialization block can be used
to avoid duplicating identical statements for initializing fields in everal constructors.
These identical statements can be factored to the initialization block. This block is
needed only when the initializations are too complicated for initializers. such as those
that require loops. For example, the following Card and Deck classes model the cards
and decks used in card games such as bridge:

public class Card {


// the following are symbolic constants
public static final byte CLUBSUIT = O;
public static final byte DIAMONDSUIT = 1;
public static final byte HEARTSUIT = 2;
public static final byte SPADESUIT = 3;

public byte suit; // range: CLUBSUIT - - SPADESUIT


public byte rank; // range: 2 - -14
// 11 =Jack, 12= 0ueen, 13=Klng, 14=Ace
}

public class Deck {


public Card[) cards = new Card[52] ;
{ // initialization block
inti= O;
for (byte suit= Card .CLUBSUIT; suit<= Card .SPADESUIT ; suit++) {
for (byte rank,.. 2 ; rank<= 14 ; rank++) {
cards[i] = new Card ();
cards[i] .suit= suit ;
cards[i] .rank= rank;
i++;
}
}
}
// constructors and methods ...
}
106 ■ Elements of Java

The initialization of cards is more suited to the use of loops than enumerating
all 52 cards in an array initializer. The Deck class may have several constructors.
Placing the initialization of cards in the initialization block avoids duplication of
code in several constructors.

4.4.3 Accessing Fields and Methods

After an instance has been created, the fields and methods of the instance can be
accessed as follows:
objectReference .method (Param eters)
objectReference .field

For example,
Point pl= new Point();
double xl = pl . x; // the value is 0.0
double yl = pl. y; II the value is 0.0
p1 .move(10.0 , 20 . 0) ;
double x2 = pl .x; // the value is 10.0
double y2 = pl . y; // the value is 20.0

Comparison with C++


In C++, variables of a class type can be declared in three different ways.
Point pl; II an object
Point •p2; II a pointer to an object
Poi nt tp3; II a reference to an object

p1.x // access a field of an object directly


p2->x II access a field of an object through a pointer
p3 . x // access a field of an object through a reference

Memory space for object variables is automatically allocated and deallocated on


the stack. No explicit allocation using the new operator is necessary. Memory space
for pointer variables is also allocated on the stack. The memory space for objects
referenced by the pointer variable must be explicitly a11ocated on the heap, using
the new operator, and explicitly deallocated, using the delete operator. Reference
variables serve as aliases for objects either on the stack or on the heap.
In Java, a variable of a class type can be declared in only one way.
Poi nt pl ; // a reference to an object
p1.x ; JI access a field of an object through a reference

In tenns of Java reference variables, memory space for objects must always be ex-
plicitly allocated on the garbage-coJJected heap by the new operator. Java reference
variables are analogous to C++ pointer variables but use the syntax of C++ object
variables for accessing members. In Java, deallocation of memory space is automati-
4.4 Class Declarations ■ 107

cally handled through garbage collection. No explicit deallocation is necessary. Hence


there is no Java counterpart of the C++ destructor, whose main responsibiUty is to
deallocate memory space.7

4.4.4 Method Invocation and Parameter Passing

Implementing Methods
The body of a method is simply a block statement. If the return type of a method is
void, the return statements in the method body may not return values. If the return
type of a method is not void, all the paths of the method body must be terminated by
a return statement with an expression that matches the return type. The following
calculateitemTotal O method will cause a compilation error because the path
of quantity < 0 is not terminated by a return statement.

public class Purchase□rder {


II . ..
public double calculateitemTotal (double unitPri ce , int quantity) {
if (quantity>= 0) {
return unitPrice * quantity ;
}
}
}

To correct this error, we must add a return statement to return a value when the
quantity is negative, or an exception must be thrown ( ee Section 4.6 [p. 139)).
The local variables declared in method bodies are not automatically initialized to
their default initial values. Local variables must be explicitly initialized on all paths
that lead to their uses. For example, the following calculateitemTotal method
w ill cause a compilation error because the local variable total i not initialized on
the path of quantity < 0.

public class Purchase□ rder {


II . . .
publi c double calculateitemTotal (doubl e unitPrice , i nt quantit y) {
double total;
if (quantity>= 0) {
total= unitPrice * quantity ;
}
return total ;
}
}

7, When some cleanup must to be done before an object is dc:illoca1ed, such as closing files or flushing
changes to files, the f i nalize () method in the Ob j ect cl:is can be useJ .
-
108 • Elements of Java

This error can be corrected in one of two ways:

public class Purchase0rder {


II . . .
public double calculateitemTotal(double unitPrice, int quantity) {
double total= 0 . 0;
if (quantity>= 0) {
total= unitPrice * quantity; _
}
return total;
}
}

or

public class Purchase0rder {


II . ..
public double calculateltemTotal(double unitPrice, int quantity) {
double total;
if (quantity>= 0) {
total= unitPrice * quantity;
} else {
total= 0.0;
}
return total;
}
}

Parameter Passing
In Java, all parameters of methods are passed by value. In other words, modifications
to parameters of primitive types inside a method will be made on copies of the actual
parameters and will have no effect on the actual parameters themselves. Consider the
example of class Cl,

public class Cl {
public void inc(int i) { i++; }
}

and an invocation of the inc method,

Cl cl s new Cl();
int k • 1;
c 1. inc (k) ; // k ls still 1 afterward

For parameters of reference types, the fields of the actual parameters can indeed
be affected inside a method. Consider the example of class C2,

public class C2 {
public void pointlnc(Point p) { p.x++; p.y++; }
}
4.4 Class Declarations • 109

and an invocation of the point Inc method,

C2 c2 = new C2();
Point p = new Point(l0.0, 10.0) ;
c2. pointlnc (p) ; // now p is (11 .0, 11 .0)

Hence parameters of reference types can serve as in-out parameters. In order for
a parameter of primitive type to do so, it must be wrapped inside a class. Consider
the example of an integer wrapped in the IntRef class so that it may act as an in-out
parameter,

class IntRef {
public int val;
public IntRef(int i ) {val= i; }
}
public class C3 {
public void inc(IntRef i) { i .val++; }
}

and an invocation of the inc method,

C3 c3 = new C3();
IntRef k = new IntRef(l ) ;
c3. inc (k) ; // now k.val is 2

Usually, a method passes values back to its caller via the return value of the
method. In-out parameters are necessary only when the method need to pa multiple
values back to the caller.

Pitfall: Final Parameters


The notion of final parameters in Java is rather weak. A final parameter of a method
may not be assigned a new value in the body of the method. However. if the parameter
is of reference type, it is allowed to modify the object or array referenced by the final
parameter. Let us consider the following method:

void aMethod(final IntRef i) {


II ...
i = new IntRef (2) ; // not allowed
}

This code segment will cause a compilation error becau e it i not allowed to a. ign
a new value to a final parameter. However, the following method i allowed:

void aMethod (final IntRef i) {


II . . .
i . val++; II ok.
}

Java final parameter arc not the ame a const parameters in C++. Further-
more, there i no Java counterpart of C++ const method. , \ hich do not modify the
state of the receiving object.
11 0 • Elements of Java

4.4.5 Class (Static) Fields and Methods

By default the fields declared in a class are instance fields, which means that each
instance of the class carries its own copy of these fields, and modifications to the
fields of one instance will not affect any other instances. In contrast, class fields are
shared by all the instances of the same class. There is only one copy of each class field
for the class. Modifications to class fields will affect all the instances of the class. By
default the methods of a class are also called instance methods, which means that they
operate on a specific instance of the class when they are invoked. If a method accesses
only class fields, then it is called a class method. Class fields and class methods are
declared using the static modifier. Thus they are also often called static fields and
static methods.8
Instance fields and methods can be accessed only through an object reference.
Class fields and methods may be accessed through either an object reference or the
class name.

objectReference. classMethod (Parameters)


objectReference. classField

ClassName. classMetlwd (Parameters)


ClassName . classField

Class and instance fields and methods are subject to the same accessibility controls.
Class fields and methods should be accessed through class names, not object refer-
ences. Then instance fields and methods can easily be distinguished from class fields
and methods by the way they are accessed.
The following is a simple class Robot representing the position of robots. It con-
tains an instance field instanceMoveCount, which counts the number of moves
made by each instance of the Ro bot class. Its value can be retrieved by an instance
method getlnstanceMoveCount () . The Robot class also contains a class field
classMoveCount, which counts the total number of moves made by all instances of
the Robot class. Its value can be retrieved by a class method getlnstanceMove-
Count O. The method getClassMoveCount () is a class method because it accesses
only a class field.

public class Robot {


public int x, y;
private int instanceMoveCount;
static private int classMoveCount;
public Robot(int x, int y) {
this.x s x; this . y = y;
}

8. The term sr~ric refers~ the way class fields and melhods a.re implemented. Class fields and merhods a.re
more approp~ate tenns, since Lhey ~ nnote their behavior as being per-class versus per-instance. Although
the term are interchangeable, we will use Lhe term class fields and methods throughout Lhi s book.
4.4 Class Declarations • 111

public void move(int dx , int dy) {


x += dx ; y += dy;
instanceMoveCount++;
classMoveCount++;
}
public int getlnstanceMoveCount() {
return instanceMoveCount;
}
static public int getClassMoveCount () {
return classMoveCount;
}
}

The following code segment illustrates the invocation of static method .

Robot r1 = new Robot (0, O);


Robot r2 = new Robot (0, 0);
r1.move(20, 10);
r1 .move(10, 20);
r2.move(10, 10);
int count1 = r1 .getinstanceMoveCount () ; II count1 is 2
int count2 = r2. get InstanceMoveCount ( ) ; II count2 is 1
II The next three statements will get the same result.
int count3 = r1. getClassMoveCount () ; II count3 is 3
int count4 = r2. getClassMoveCount ( ) ; II count4 is 3
int counts = Robot . getClassMoveCount () ; II counts is 3

Initialization of Class Fields


Class fields can exist without any instances of the clas being created. In fact. class
fields are initialized before any of the instances of the clas are created and before any
of the class fields or method are acce ed. The lifetime of cla s field extend until
the program terminates. With re pect to lifetime. clas field are imilar to the global
variables of C and C++.
Class fields can be initialized in the following way :

1. With default initial values. Thi i how classMoveCount i initialized in the


preceding example. It i initialized to the default initial value 0.
2. With an explicit initializer in it declaration. The classMoveCount field in the
preceding example can also be initialized with an explicit initializer.

publ ic class Robot {


II . . .
static private int classMoveCount • 0;
II . . .
}

3. By the . tatic initialization bl k. The tatic initialization block i similar to


the instance field initialization block. except that it begin with the keyword

' .........______________________ ...


11 2 • Elements of Java

static. The classMoveCount field in the preceding example can also be


initialized with a static initialization block.

public class Robot {


II . ..
static private int classMoveCount;
static {
classHoveCount • O;
}
II . ..
}

Class fields should not be initialized in constructors, as constructors are executed


only when instances are created. Therefore, constructors are used for initializing
instance fields only.

Constants
In Java, constants can be declared as final class fields. For example, several integer
constants for specifying font styles are defined in the Font class.

public class Font {


public final static int PLAIN = 0;
public final static int BOLD = 1;
public final static int ITALIC= 2;
II .. .
}

We may also define constant objects. In the Color class, some commonly used
colors are declared as follows.

public class Color {


public final static Color BLACK = new Color(O, 0, O);
public final static Color BLUE = new Color(O, 0, 255);
public final static Color GRAY = new Color(128, 128, 128);
public final static Color WHITE = new Color(255, 265, 255);
public final static Color YELLOW= new Color(255, 255, O) ;
II . ..
}

Style Convention Constant Names

Name of constants should be in all uppercase lelters (e.g., the constants in the
Font and Color class).
When there are multiple words in the names, separate the words with underscores
{_) (e.g., STANDARD_RATE, MAXIMUM_CLASS _SI ZE).
4.4 Class Declarations • 113

The main () Method


Each class can have a special class method main () , which must be declared as
public static void main(String[] args) { . .. }

The main () method serves as the entry point of a Java application. Java applications
are invoked as
venus¾ j ava ClassName [arguments . . . ]

ClassName.main O is invoked to start an application. When the application


starts, no instance of ClassName has been created, so only a static method can be
invoked. Thus, the start-up method main() mu t be static.
Optionally, a number of arguments can be specified on the command line and
passed to the application when it is invoked. The command-line arguments, not
including j ava or the class name, are passed to the main () method through the
args parameter as an array of trings.

EXAMPLE 4.3 Command-Line Arguments

PURPOSE
To illustrate retrieving command-line argument passed to the ma i n () method of a
Java application.
DESCRIPTION
The following program prints the command-line argument to the tandard output.
SOLUTION
Print command line ~ t s : ~enta .java
public class Arguments {
publi c static void main(String [] args) {
if (args . length > 0) {
for (inti= O; i < args . length; i++) {
System.out.println ( "args[" + i + "] : " + args [i]) ;
}
} else {
System.out.println(" No arguments . ") ;
}
}
}

If the program i invoked a


venus¾ java Arguments foo bar

the result is
args[O] : foo
args[l]: bar
114 ■ Elements of Java

Singleton Classes
Some classes are not supposed to have more than one instance at a time. Examples
are the top-level window of an application and the timer of an entire system. Classes
that can have no more than one instance at a time are called singleton classes. Using
class fields and methods, we can ensure that no more than one instance of a singleton
class exits at any given moment. The following is a schematic implementation of a
singleton class:
public class Singleton {
static public Singleton getlnstance() {
return thelnstance;
}

protected Singleton() {
II initializing Instance fields
}

II other fields and methods


private static Singleton thelnstance = new Singleton();
}

The unique instance of the Singleton class is stored in a class field, the In-
stance. Note that the constructor of the Singleton class is protected. Therefore,
clients are prohibited from creating an instance of the Singleton class by doing

mylnstance = new Singleton();


The only way for clients to obtain an instance of the Singleton class is to use
the class method get Instance O:

mylnstance = Singleton . getlnstance();

The get Instance O ensures that a new instance of the class will be created only
when it is invoked the very first time. Hence, just one instance of the Singleton
class can be created!

4.4.6 Object Reference this

Instance methods operate on a specific instance of a class. This object instance is often
referred to as the receiving instance (also known as the receiver or the recipient ) of
the instance method, that is, the object instance through which the instance method is
invoked. Inside instance methods, a special object reference this is used to denote
the receiving instance. Since class methods operate on class fields only, the object
reference this may not be used in class methods.
The two common uses of this are (1) to pass the receiving object instance as
a parameter to other methods and (2) to access instance fields that are shadowed or
hidden, by local variables. '
4.4 Class Declarations • 11 5

Passing this as a Parameter


Let us consider the composition relationship between Faculty and Department, as
shown previously in Figure 2.7. The relationship can be implemented as follows:

public class Faculty {


protected Department dept;
protected String name ;
public Faculty(String n, Department d) {
name= n; dept= d ;
}

public Department getDepartment () {


return dept ;
}
II .. . other methods
}

Each faculty member has a name and a reference to the department to which the
faculty member belongs.

public class Department {


protected String name;
protected Faculty facultyList[J = new Faculty [lOO];
protected int num□ffaculty = O;
public Department (String n) {
name= n;
}
public void newFaculty(String name) {
facultyList[num□fFaculty++] =
new Faculty (name, this) ;
}

II . . . other methods
}

Each department has a name and a list of faculty member who belong to the
department. For consistency, it is important to maintain the following requirement
among the instances of the e two classe :

If a faculty member a occurs in the facul tyList of department d. then a. dept


should refer to d.

This requirement is maintained in the newFacul ty O method by passi ng the this


reference- the deparunent to which the nc::w faculty member belongs- a the param-
eter to the con tructor of the Faculty cla s.

. , __ _ _ _ _ _ _ _ _ _ _ _ _ _ , I
11 6 ■ Elements of Java

Accessing Shadowed Fields


A field declared in a class can be shadowed, or hidden, inside a method by a parameter
or a local variable of the same name, as in the following code segment:

public class MyClass {


int var; II an instance field
void methodl O {
float var; // the local variable var shadows the instance field
II . . .
}
void method2(int var) {
II the parameter var also shadows the instance field
II . ..
}
}

Java adopts the usual scope rule: When a name in an outer scope is shadowed
by a name in an inner scope, the name in the outer scope is hidden. In the case of a
shadowed instance field of a class, the shadowed field var can be accessed via the
receiving object reference this as this. var. In general, the shadowing of variable
names in outer scopes is a bad programming practice and should be avoided, as it is
a common source of program bugs. In contrast, the shadowing of instance fields of
a class from within its methods is an acceptable and common practice, if the local
variables are indeed copies of the fields of the same class. The purpose is to avoid the
confusion of introducing a new set of variable names to mirror the fields of the class.
The following program segment is another version of the Point class.

public class Point {


public double x, y;
public Point() {}
public Point (double x, double y) {
// the parameters x and y are simply the initial values of
II fields x and y of class Point
this . x = x ; this.y = y;
}
public void adjustPosition( .. . ) {
double x = this . x, y = this . y;
II do calculation using the local x and y
II commit the changes when done
this . x = x; this .y = y;
}

II . .. other methods
}

In the preceding constructor, parameters x and y represent the initial values of the
correspondi~g instance fields of class Point . The expressions this. x and this . y
refe_r to the instance field. In method adjustPosition(), the instance fields are
copied to the local variables with the ame names. Calculations are performed o n

..
4 .4 Class Declarations ■ 11 7

these local copies. Only when the calculations have been completed will the results be
committed to the instance fields of class Point. Therefore, no intennediate values are
ever stored in the instance fields. The advantages of this approach are to avoid leavino
e,
intermediate results in the instance fields in case an exception is encountered (see
Secti on 4.6 [p. 139]) and to avoid synchronizing an entire method of a multi threaded
program. Only the reading and writing of instance fields hould be synchronized. The
calculation portion can be unsynchronized (see Section 11 .2.1 [p. 557]).

Guideline Shadowing Variables


Avoid shadowing variables. Shadow only the instance fields with local variables that
serve as temporary copies of the instance fields in an instance method or con tructor.
Upon successul completion of the method, copy the local variable back to the
instance fields before leaving the method.

4.4. 7 Interfaces and Abstract Classes

Interfaces can be thought of as a special form of cla s, which declare only the
features to be supported by the class. Java interfaces provide no implementation.
Implementation is deferred to the classes that implement the interface. The yntax
of an interface declaration is

[ClassModifiers] i nt erface InterfaceName


[extends lnterface 1, lnterface2 • •• ] {
/11terfaceMemberDeclaratio11s
}

Interface member can be either abstract methods or con tant (that i , tatic and
final fields). Implementation of ab tract method i deferred to ubcla e . Ab tract
methods are declared as

MethodModifiers Ret11mType 1\1/etJrodName ( [Parameterlisr] ) ;

All interface are public, and all method and con tant declared in interfaces
are public. The public modifier can be omitted in interface de laration . Class
( ta tic) methods are not allowed in interface . Interface have no con truetor , and
no in tances of interface are allowed.
Here are two commonly u ed interface in Java: the Runnable interface,

public interface Runnable {


publ ic void run();
}
118 • Elements of Java

and the Iterator interface,


public interface Iterator {
public boolean hasNext();
public Object next();
public Object remove();
}

Classes can implement an interface by overriding the methods declared in the


interface. Classes may also declare abstract methods by using the modifier abstract:

abstract MethodModifiers Return Type MethodName ( [Parameterlist] ) ;

An abstract class is a class that includes or inherits at least one abstract method.
In other words, an abstract class is a class with partial implementation. Although the
presence of an abstract method in a class implies that the class is abstract, the class
must still be declared abstract explicitly; that is, the class modifier abstract must
be present for the sake of readability. Like interfaces, an abstract class may not have
instances. We discuss the implementation, extension, and use of interfaces in detail
in Chapters 5 and 7.

4.4.8 Strings

A string is a sequence of characters. The String type is not a primitive type in Java;
it is a class type. Java provides two classes to support strings: (1) the String class,
whose instances are immutable (that is, constant strings), and (2) the StringBuff er
class, whose instances are mutable strings. The rationale for distinguishing mutable
from immutable strings is that in a typical program most strings are immutable.
Immutable strings allow simpler implementation and a more compact representation
than do mutable strings.
The String class is special in the sense that it enjoys some unique privileges not
shared by ordinary classes.

• A string, that is, an instance of the String class, can be created using string
literals (see "String Literals" in Section 4.1.3 [p. 80]).
• Operators + and += can be applied to strings (see "String Concatenation" in
Section 4.1 .4 [p. 84]).

The characters in a string are indexed by integers Oto n - 1, where n is the length
of the string. The common operations on strings are summarized in Table 4.3.
The StringBuffer class provides an overloaded append() method that ap-
pends the text representation of its arguments to the string buffer.

String Comparison
For two string variables s 1 and s2, s 1 == s2 tests the equality of the two references
not the equality of the strings referenced by them. Thus s 1 == s2 is true if and
-----------~
4.4 Class Declarations • 119

TABLE 4.3
Methods of String Class

Method Description

s. length O Returns the length of tring s .


s. charAt ( i ) Returns the ith character of string s .
s . index Of ( c) Returns the index of the first occurrence of character c; returns
- I if c does not occur in s.
s . index Of ( c , i ) Returns the index of the first occurrence of character c after
(including) index i: returns - I if c does not occur in s after
index i.
s . indexOf ( s 1) Returns the index of the first occurrence of string s 1; returns
-1 if s 1 does not occur in s .
s . indexOf (sl, i) Returns the index of the first occurrence of tring s1 after
(including) index i ; returns -1 if s1 does not occur in s after
index i .
s . substring(i) Returns the substring of s from i to the end.
s. substring ( i , j) Returns the substring of s from i to j - 1. inclusive.
s. toLowerCase () Returns a tring with all the characters in s converted to
lowercase.
s. toUpperCase () Returns a string with all the characters in s converted to
uppercase.
s. trim() Returns a string with all the leading and trailing white pace of
s removed.
s . endsWith(sl ) Retumstrueifstringsli a uffixofs.
s . startsWi th(s1) Returns true if string s1 is a prefix of s .

only if both s1 and s2 reference the ame tring (that i . point to the arne memory
location). The equivalence of two tring can be te ted in everal ways:

s1. equals(s2)

returns a boolean value,

true if s1 and s2 arc identical in content (ca e-sen itivc).


false otherwise.
120 ■ Elements of Java

s1.equalsignoreCase(s2)

is the same as equals (), except that the comparison is case-insensitive.

s1 . compareTo(s2)

compares the two strings lexicographically, according to the Unicode; the comparison
is case-sensitive and returns an integer value of

<O if s1 is lexicographically less than s2.


0 if s1 and s2 are equal.
> O ifs 2 is lexicographically less than s 1.

The following code segment illustrates the differences between comparing string
references and string contents. Three strings strl, str2, and str3 are initialized in
different ways as follows:
String strl = "FooBar";
String str2 = strl;
String str3 = new String("FooBar");

The first two strings strl and str2 reference the same string object, while str3
is a different string object with the same contents.

strl~

~
str3~
str2

The following table shows different ways of comparing the strings and their results:

Expression Result
--
strl -- str2 true
strl -- str3 false
str1 . equals(str2) true
str1 . equals(str3) true
strl.compareTo(str2) O
strl.compareTo(str3) 0

Note that comparisons of the identity and equality of strings do not always yield
the same results. String comparison is a very common operation in many programs.
Comparing the identity of two strings takes far less time than comparing the equality
4.4 Class Declarations ■ 121

of tw? str_ings. ~he String class supports a canonical representation of string objects.
It mamtams an internal pool of unique string objects. A tring object can be interned
by calling the intern () method of the String class, which returns a string that has
the same contents as the original string, but is guaranteed to be from the internal pool
of unique strings. For interned strings, comparison of the identity and equality of the
strings always yield the same results.
The following example expands the preceding example to illustrate the differ-
ence between comparisons of lhe identity and equality of tring , and lhe effect of
interning:

public class StringComparison {


public static void main(String[] args) {
String strl = "FooBar";
String str2 strl;
String str3 = new String ( "FooBar" );
String str4 = "FooBar";
String str5 "Foo"+ "Bar";
String str6 = new String( "Foo") + new String (" Bar") ;

System.out.println("strl -- str2 : "+ (strl str2)) ;


System . out.println ( "strl -- str3: "+ (strl -- str3)) ;
System . out.println("strl str4 : "+ (strl -- str4)) ;
System.out.println("strl -- str5 : "+ (strl - str5)) ;
System.out . println("strl -- str6 : "+ (strl -- str6)) ;
System . out.println("str1.equals (str2) : "+ str1.equals (str2));
System.out . println ("str1.equals (str3) : " + str1.equals (str3));
System . out.println( "strl . equals (str4): "+ strl . equals (str4));
System.out.println ( "str1.equals (str5): "+ str1.equals (str5));
System . out.println ("str1.equals (str6) : " + str1.equals (str6));
System.out.println ("str1. compareTo (str2): " + strl .compareTo (str2));
System.out.println ("strl . compareTo (str3): "+ strl .compareTo (str3));
System.out . println ( "strl. compareTo(str4): "+ strl . compareTo (str4)) ;
System . out . println ( "strl . compareTo (strS): 11 + strl .compareTo (strS )) ;
System . out . println("strl .compareTo (str6): " + strl. compareTo(str6));
str2 = str2. intern () ;
str3 = str3 . intern () ;
str4 = str4. intern ();
strS = strS . intern ();
str6 = str6. intern O ;
System . out . pri ntln ( "Af ter interni ng");
System . out . println( "strl -- str2:
II
+ (strl -- str2));
System.out . pri ntln ( "strl -- str3:
II
+ (strl -- s tr3)) ;
System . out . println(" strl -- str4 :
II
+ (strl -- s tr4)) ;
System . out.println (" strl -- strS :
II
+ (strl -- s trS));
System . out . printl n ( "strl -- st r6:
II
+ (strl -- str6));
}
}
122 ■ Elements of Java

The results are

strl == str2: true


strl == str3: false
strl == str4: true
strl == str5: true
strl == str6 : false
str1.equals(str2): true
str1.equals(str3): true
str1.equals(str4): true
str1 .equals(str5): true
str1 . equals(str6): true
str1.compareTo(str2): 0
str1.compareTo(str3): 0
str1.compareTo(str4): 0
str1 . compareTo(str5): 0
str1.compareTo(str6): 0
After interning
strl = str2: true
strl = str3: true
strl == str4 : true
strl - str5: true
strl = str6: true

Note that all string literals are automatically interned (compare the results con-
cerning strl, str4, and str5). In most cases, interning strings will significantly
improve the performance of programs.

The toString () Method


Toe toString() method of a class allows it to define a string representation of the
instances of the class. For example, we can add the following toStringO method
to the Point class.

public class Point {


public double x, y;
II other methods .. .

public String toString() {


return II ( II + X + II ' II + y + II) II ;

}
}

If we assume that str is a string variable and that obj is a variable of class type,
the expression

str + obj

is interpreted as

str + obj.toString()
4.4 Class Declarations • 123

N?w, having added the toString () method to the Point class, we may do th
following. e

Point p = new Point (10.0, 20.0);


System . out . println("A point at"+ p);

The output will be

A point at (10.0, 20 .0)

String and Character Array


Unlike those in C and C++, strings in Java are not arrays of character , that is, char[].
However, strings can be converted to character arrays and vice versa.
char data[] = { 'F', 'o', 'o'}
String str = new String(data);

is equivalent to

String str = "Foo";

and

String str = "Bar";


char data[] = str.toCharArray();

is equivalent to

char data[] = { 'B', 'a', 'r'} ;

More sophisticated methods for string-char array conversion are available al o.

Reading and Writing Strings


We begin with an example of how tring can be read from and wrinen to the
standard input and output streams. The tandard input and output refer to the character-
based input and output a sociated with a command con ole or terminal window. The
standard input and output stream are declared a clas field in the System class,
which also contains declaration of other y temwide re ource .

public class System {


public static final Input Stream in; II the standard input
public static final PrintStream out ; II the standard output
public static final PrintStream err ; II the standard error output
II . ..
}

The Java input and output mechani mi. both er' atile and nontrivial (we discus
it in detail in Section 8.4 [p. 366)). We begin \ ith ome imple u e of the input and
output mechani m to read and \ rite tring .
........... -
124 • Elements of Java

EXAMPLE 4.4 Copying from Standard Input to Standard Output


-=====-i;;:amams-=a::a::i==-:::sa11_ __

PURPOSE
To illustrate reading and writing strings.

DESCRIPTION
The following program reads the characters read from the standard input line by line
and writes each line to the standard output.

SOLUTION
from standard
import java.io . *;
public class Copy {
public static void main(String[] args) {
try {
BufferedReader in= new BufferedReader(
new InputStreamReader(System . in));
String line;
while ((line= in.readLine()) != null) {
System . out.println(line);
}
} catch (I□Exception e) {}
}
}

We discuss the Buffered.Reader and InputStreamReader classes in Sec-


tion 8.4 [p. 366]. The readLine () method simply reads a line of characters from the
input stream. It returns null when the end of the stream is reached. We invoke the
program as follows:

venus¾ java Copy< infile .txt > outfile.txt

Here, the standard input and output are redirected to two files named in-
file. txt to outf ile. txt, respectively. The preceding invocation of Copy copies
infile. txt to outfile. txt.

EXAMPLE 4.5 Copying Text Files

PURPOSE
To illustrate reading and writing text files.

DESCRIPTION
The following program simply copies the contents of one text file to another text file
line by line. '
4.4 Class Declarations ■ 125

SOLUTION
Copy a text file: CopJ1extF1le. j ava
import java.io.*;
public class CopyTextFile {
public static void main(String[] args) {
if (args . length >= 2) {
try {
BufferedReader in= new BufferedReader (
new FileReader(args(O]) ) ;
PrintWriter out= new PrintWriter (
new BufferedWriter (
new FileWriter(args[l] )) );
String line;
while ((line= in.readLine ()) != null) {
out.println(line) ;
}
out.flush();
out. close() ;
} catch (IOException e) { }
}
}
}

Again, we discuss the FileReader, FileWri ter. PrintWri ter, and


Buff eredWri ter classes in Section 8.4 [p. 366]. This program talces two argument :
the first argument is the name of the input file, and the second argument is the name
of the output file. The following invocation copie a text file named inf ile. txt to
outf ile. txt:

venus¾ java CopyTextFile infile.txt outfile . txt

Working with Strings


We often need to divide strings into smaller piece , known a tokens, that are eparaled
by separators or delimiters. For example, it may be u eful to brealc tring that
represent sentences or paragraph into word that are eparated by pace or ome type
of punctuation. The colon-delimited record fom1at is a common fom1at for toring a
variety of tabular text data, in which each record i a ingle line of text that consi t of
field delimited by colons (: ). The following example contain rwo colon-delimited
records in an addres book:
Micbael:Owen:123 Oak Street :Chicago:IL:60606
James : Gosling : 456 Sun Blvd. : Mountain View :CA:45454

To extract the information stored in the colon-delimited record format, each record
need to be divided into fields. One way to break trings into token i to use the
indexOf () and substring () method of the String cla s.
126 • Elements of Java

EXAMPLE 4.6 Breaking Colon-Delimited Records

PURPOSE
To illustrate string manipulation.

DESCRIPTION
The following program reads colon-delimited records from the standard input and
breaks each record into fields.

SOLUTION
8nak colon-dellmlted records: BreakRecorda . j ava
import java.io.*;
public class BreakRecords {
public static void main(String[] args) {
BufferedReader in= new BufferedReader (
new InputStreamReader(System.in));
try {
String record, field;
char delim =' : ' ; II the delimiter
I for (int n = 1; (record= in . readLine()) != null; n++) {
I, • System.out . println( 11 Record 11 + n) ;
int begi n, end, i;
begin= O;
for (i = O; (end= record.indexOf(delim, begin)) >= O; i++) {
field= record.substring(begin, end);
begin = end + 1 ; // skip the delimiter
System . out.println( 11 \tField 11 + i + ": "+ field);
}
field = record. substring(begin) ; // the last field
System . out.println("\tField" + i + ": "+ field);
}
} catch (IOException e) { }
}
}

The following output of the program is obtained when the two name and address
records shown previously are used as input:

Record 1
Field 0: Michael
Field 1: Owen
Field 2: 123 Oak Street
Field 3: Chicago
Field 4: IL
Field 5: 60606
... .,.. .·

4.4 Class Declarations • 127

Record 2
Field 0: James
Field 1: Gosling
Field 2: 456 Sun Blvd.
Field 3: Mountain View
Field 4: CA
Field 5: 45454

The second approach breaks strings into tokens with the help of the StringTo-
kenizer class.

EXAMPLE 4. 7 Breaking Text into Words

PURPOSE
To illustrate the use of StringTokenizer.
DESCRIPTION
The following program reads text from standard input and break the text into words.
SOLUTION
Break text into words: Words. Java
import java.io.• ;
import java . util.• ;
public class Words {
public static void mai n (Stri ng[] args) {
BufferedReader in=
new BufferedReader (new InputStreamReader (System. in) ) ;
try {
String line , word ;
String delim = "\ t\n. , : ; ? !-/ () (J \ " \' " ; // spaces and punctuations
while (( line= i n . readLi ne ()) != null ) {
StringTokenizer st= new Stri ngTokenizer (line , delim) ;
while (st . hasMoreTokens ( ) ) {
System. out. pri nt ln (st .nextToken ());
}
}
} catch ( IOExcept i on e) {}
}
}

The con tructor call in Example -l.7 create an in tance of the StringTo-
kenizer cla s that discards the delimiters indkated by the tring delim. The
StringTokenizer class has two other con tructor . All three StringTokenizer
constructors are sunmuuized in the fo lio\ ing table. Parameter srr is the string to
128 • Elements of Java

be tokenized, delim is a string of delimiters, and keep is a boolean value indicating


whether the delimiter should be returned as tokens.

Constructor Description

StringTokenizer ( str) Tokens are delimited by one or more white


spaces, and the delimiters are discarded.
StringTokenizer(str, de/im) Tokens are delimited by any character in delim,
and the delimiters are discarded.
StringTokenizer(str, delim, keep) Tokens are delimited by any character in delim .
The delimiters are returned as tokens if keep is
true. Otherwise, the delimiters are discarded.

The methods of the StringTokenizer class are summarized as follows.

Method Description

hasMoreTokens O Returns true if there are remaining tokens.


nextToken() Returns the next token and advance.
countTokens () Returns the number of remaining tokens.

4.4. 9 Wrapper Classes

Because in Java values of primitive types are not objects, a wrapper class is provided
to "wrap" the values of primitive types into objects when needed. Each primitive type
has a corresponding wrapper class as shown in the following table:

Primitive Type Wrapper Class

boolean Boolean
byte Byte
char Character
double Double
float Float
int Integer
long Long
short Short
4.4 Class Declarations • 129

Wrapper classes allow values of primitive type to be used in place where objects
are expected, as in the elements of collection classes such as List or Hashtable
(we discuss collections in Section 8.2 [p. 308]). Wrapper classe also provide useful
methods and constants for manipulating the values of primitive type .
Seemingly, instances of wrapper classes could also be used for in-out parameters
for primitive types. Unfortunately, however, they may not. The rea on i that instances
of wrapper classes are immutable; that i , they provide no way to modify the tate of
their instances.
If we let Type be a wrapper class of primitive type named f)pe, for each wrapper
class Type at least two constructor are provided. One talces a value of f)pe, and the
other takes a string representation of a literal of f)pe. For example, the Integer cla s
has the constructors
Integer(int value)
Integer(String value )
Instances of wrapper classes can also be created by using the cla method val-
ue □f (). The val ueOf () method of wrapper class Type talces a string repre entation
of a literal of type and returns an instance of Type. Each wrapper cla al o provide
methods for retrieving its value as a primitive type.

Wrapper Class Method of Retrieving Values

Boolean booleanValue ()
Character charValue 0
Byte byte Value()
Double double Value ()
Float float Value()
Integer intValueO
Long longValue ()
Short short Value ()

For a tring repre entation of a literal of primitive type type, the value re pre ented
by the literal can be obtained a
Type . valueOf (literal). typeValue ( )

For example, the following expre ion give the re ult hown:

Expression Result

Integer.value0f("100") . intValueO 100


Double . value0f (" 1E3"). doubleValue O 1000.0
130 ■ Elements of Java

The class method parse Int () of the Integer class can also be used to parse
integers. It parses a string representation of an integer literal and returns an int
value. For example, the expression Integer. parse Int (" 100") produces the result
100.
A common use of the parsing method is to parse the input values to a program,
which are often provided as strings.

EXAMPLE 4 . 8 The Maximum of Two Integers

PURPOSE
To illustrate the use of the parse Int O method to parse command-line arguments.

DESCRIPTION
The following program takes two integers as command-line arguments. It converts the
string arguments into integers using the parse Int () method and finds the maximum
of the two integers.

SOLUTION
1be DMIUJIIIIID of two in ~ents: Muimua. java
public class Maximum {
public static void main(String[] args) {
if (args.length >= 2) {
int i1 = Integer . parseint (args[O]);
int i2 = Integer . parseint(args[1]);
System . out .println("The maximum of"+ i1 +" and 11 + i 2 +
II is; II + ((i1 >= i2)? i1 ; i2) );
} else {
System.out .println( 11 Usage: java Maximum integer! integer2 11 ) ;

}
}
}

Running the program produces the following results:

venus% java Maximum 12 11


The maximum of 12 and 11 is : 12

The Float and Double Classes


Classes Float and Double provide some useful constants and methods for flo ating-
point arithmetic. The constants that follow are declared in both classes.
4.4 Class Declarations ■ 131

In Double In Float Description

double P0SITIVE_INFINITY float P0SITIVE_INFINITY +oo


double NEGATIVE_INFINITY float NEGATIVE_INFINITY -oo
double NaN float NaN NaN

The following instance methods are declared in both classes. These methods can be
used to test whether the value wrapped in the instance i NaN or infinity.

In Double In Float

boolean isNaN O boolean isNaN ()


boolean isinfinite () boolean isinfiniteO

And the following class methods are also declared in both classes. These methods can
be used to test whether a parameter is NaN or infinity, such as

In Double In Float

boolean isNaN(double v ) boolean isNaN (float v)


boolean isinfinite (double v) boolean isinfinite(float v)

Mathematical Constants and Functions


Table 4.4 shows the commonly u ed mathematical con tant and functions defined
in class Math. All the method are cla method . The following are some simple
examples of the use of these function :

Point p1, p2;


double dx = (p2 . x - p1 . x);
double dy = (p2.y - p1 . y);
// the distance between p1 and p2
double distance= Math . sqrt(dx • dx + dy • dy);
// the angle between the line connecting p1 and p2 and the X axis
double angle = Math . atan2 (dy, dx) ;
132 • Elements of Java

TAILE 4.4
Mathematical Constants and Functions

Constant/Method Description

double E e = 2.7182818284590452345
double PI rr = 3.14159265358979323846
double sin(double a) sin(a), a an angle in radians
double cos(double a) cos(a), a an angle in radians
double tan(double a) tan(a), a an angle in radians
double asin(double a) arcsin(a), result in [-rr /2, rr /2]
double acos(double a) arccos(a), result in (0, rr]
double atan(double a) arctan(a), result in [-rr/2,rr/2]
double atan2(double a, double b) arctan(b/a), result in [-rr, rr]
double exp(double a)
double pow(double a, double b)
double log(double a) ln(a), the natural logarithm of a
double sqrt(double a) ../a, the square root of a
double rint(double a) truncated integer value of a
double random() a pseudorandom number in (0.0, 1.0)
double ceil(double a) fa l, the ceiling of a
double floor(double a) LaJ, the floor of a
int round(float a) La+ 0.5J , the rounding of a
int round(double a)
int abs (int a) la I, the absolute value of a
long abs(long a)
float abs(float a)
double abs(double a)
int max(int a, int b) max(a, b), the maximum of a and b
long max(long a, long b)
float max(float a, float b)
double max(double a, double b)
int min(int a, int b) min(a, b) , the minimum of a and b
long min(long a, long b)
float min(float a, float b)
double min(double a, double b)
4.4 Class Dedarations • 133

EXAMPLE 4.9 The Minimum of an Nray of Integers

PURPOSE
To illustrate the use of mathematical functions in Math class.
DESCRIPTION
The following program finds the minimum value of an array.
SOLUTION
1bf' mlnlmlJDl of-an &rnJ' ofln~rs: Minim.java
public class Minimum {
public static void main(String[]args) {
int a[] = { 75, 34, 80, 11, 95 , 34, 53, 81, 33, 13 };
int min= a[O];
for (inti= 1; i < a. length ; i++) {
mi n= Math.min (min , a[i]);
}
System . out . println ("The minimum value is: 11
+ min);
}
}

Running the program produces the following results:


venus¾ java Minimum
The minimum value is : 11

EXAM PL E 4 . 1 0 The Max- Min Value of a Two-Dimensional Array

PURPOSE
To illustrate the use of mathematical functions in Math class and handling of two-
dimensional arrays.
DESCRIPTION
Assume the following declaration of a matrix of double :

double mat [] [] ;

The following program segment find the max- min value of a two-dimen ional array,
that is, the maximum of the minimum of each column, or
max min mat[i][j]
O~ j-:;m O~i ~ 11

where II and m are the number of row and column , re pectively.


134 • Elements of Java

SOLUTION
1be mu-min value of a two-dimensional arra)': Mu:Min. ava
public class MaxMin {
public static void main(String[]args) {
double mat[](] = { { 2 . 3, 5.1, 9.9 },
{ 8 . 3, 4.5, 7.7 },
{ 5 . 2, 6.1, 2 . 8} };

int n = mat.length;
int m = mat(O] . length;
double maxmin = 0 . 0;
for (int j = O; j < m; j++) {
double min= mat(j] [OJ;
for (inti= 1; i < n ; i++) {
min= Math.min(min, mat[i] [j]);
}
if (j == 0) {
maxmin = min;
} else {
ma.xmin = Math . max(maxmin, min);
}
}
System.out . println("The max-min value is"+ maxmin);
}
}

Running the program produces the following results:

venus% j ava MaxMin


The max-min value is 4 .5

PACKAGES

Classes are the basic building blocks of Java programs, and a Java program consists
of one or more classes. Classes should be relatively small and comprise highly
cohesive functionalities. Because a large program may consist of thousands of classes,
providing a mechanism for logically organizing large programs is necessary. Java
provides two such mechanisms:

1. Files, which may contain a main public class and possibly a few nonpublic helper
classes.
2. Packages, which comprise many related classes, interfaces, or other packages.
Files are the compilation units of Java; that is, each file can be compiled separately.
Packages support hierarchical organization and arc used lo organize large programs
into logical and manageable units.
4.5 Packages • 135

4.5.1 Using Packages

In Java each class belongs to a package. Package declaration is file based; that is,
a11 classes in the same source file belong to the same package. Each source file may
contain an optional package declaration in the following form:
package PackageName ;

Let us consider the source file Point. j ava, for example.


package geometry;
public class Point {
public double x, y;
II . . .
}

The package declaration at the top of the source file declares that the Point clas
belongs to the package named geometry. When the package declaration is ab ent
from a file, all the classes contained in the file belong to an unnamed package.
Packages serve as a useful mechanism for grouping closely related classe and
interfaces. The classes that belong to a package should be clo ely related because a
class may access not only the public fields and methods of other classes belonging to
the same package, but also all except the private field and methods of these clas e .
A class in a named package can be referred to in two different way :

1. Using the fully qualified name,

PackageName. ClassName

we can refer to the Point class in package geometry as

geometry . Point

2. Importing the class and using the simple cla nan1e. We can import a cla in
the designated package using

import PackageName. ClassName ;

or, we can import all the classes in the designated package u ing

import PackageName. • ;

The Point class in package geometry can imply be referred to a Point when
either of the following import clau e occurs at the top of the source file:

import geometry . Point;


import geometry.•;

Some of most fundmuental and mo ·t commonly used cla e are defined in the
j ava. lang package. The j ava. lang package i implicitly imported by all Java
programs. So it is unnece ary to explicitly import the j ava. lang package.
136 • Elements of Java

Style Convention Package Names


Package names are all in lowercase letters, such as j ava. awt . event
Packages intended to be widely available should use the reverse of the Internet
domain as the prefix of the package name so that it will be unique globally-
for example, edu . depaul. cs

4.5.2 Partitioning the Name Space

Packages serve as a useful mechanism for partitioning name space and preventing
name collisions. When they belong to different packages, classes with the same name
can be used by the same class without causing name collision. The Java Class Library,
for example, contains another Point class, whose fields x and y are declared as
type int instead of type double. This other version of the Point class is defined in
package java. awt. However, both classes can be used by the same class by using
their fully qualified names.
geometry.Point= new geometry.Point(10.0, 20 . 0);
java.awt.Point = new java.awt . Point(10, 20);

Alternatively, we can use the import clause to import one of the classes so that
it can be directly referenced using only its class name.
import geometry;

Point = new Point (10 . 0, 20 . 0); // this refers to geometry.Point


java.awt . Point = new java . awt . Point(10, 20);

In practice, all classes are placed in packages for all applications. The basic rule
for placing classes in packages is stated in the folJowing guideline:

Design Guideline Package Coheswn


• Only closely related classes should belong to the same package.
• Classes that change together should belong to the same package .
• Classes that are not reused together should not belong to the same package .

4.5.3 Packages and the Directory Structure

When pack.ages are used, source and class files must be placed in directories whose
structures match ~he structures of the packages. Several directory paths are important
to the Java compiler, j avac, as described in Table 4.5.
4.5 Packages • 137

TABLE 4.5
Directory Paths

Path Description

The source directory root This is where the compiler looks for the ource files.
The default is the current working directory.
The destination directory root This is where the compiler places the cla files (that
is, the files that the compiler generates). The default is
the current working directory. It can be pecified on the
command line by using the -d option.
The CLASSPATH A list of directories and/or jar files. The compiler will
look in these directories for any precompiled cla file
it needs. The CLASSPATH can be et as an environment
variable or specified on the command line by using the
-classpath option. The CLASSPATH is al o u ed by
the Java Virtual Machine to load cla es.*

* Consult the JDK/JRE installation guide for instructions on etting the CLASSPATH environment variable
on specific platforms.

For example, let srcdir be the source directory root and classdir be the desti-
nation directory root. Let us assume that we have a clas Foo in package pack-
age 1. package 2. In this case, the source file Foo. j ava mu t be located at

srcdir/package I/package2/Foo.java

After compilation, the class file will be located at

classdi r/package I/package2/Foo.class

To compile the program, we mu t change the working directory to the source


directory root (that is, srcdir) and i sue the following command for compilation:

venus¾ javac package1 / package2/Foo.java

To run the prooram


0 ,
we chanoe
0
the workino0 directory to the desti11atio11 directory root
(that is, classdir) and do the following:

venus¾ java packagel.package2.Foo

If we want a different program to use the precompiled class package1 • pack-


age2. Foo. class, we must include the c/assdir in the CLASSPATH for that program.
13 8 ■ Elements of Java

The following is the same program Maximum as in Example 4.8, except it is


placed in a package named xj. num:
package xj . num;
public class Maximum {
public static void main(String[] args) {
if (args.length >= 2) {
int il = Integer.parseint(args[O]);
int i2 = Integer .parselnt(args[l]);
System . out.println( 11 The maximum of 11 + il + 11 and 11 + i2 +
11
is: 11 + ((il >= i2) ? il : i2));
} else {
System.out . println ( 11 Usage: java Maximum integerl integer2 11 ) ;

}
}
}

Assuming srcdir is the source directory, the source file Maximum. j ava must be
placed in the subdirectory classdir /xj /num. To compile the program, change the
current directory to srcdir and do
venusi. java xj/num/Maximum.java

In this case, the srcdir directory is also the destination directory. The Java compiler
generates the byte-code file Maximum. class in the same directory as the source code
file, that is, classdir/xj/num. To run the program, remain at the srcdir directory and
do
venusi. java xj .num.Maximum 11 12
The maximum of 11 and 12 is: 12

You may choose to use a destination directory, say destdir, that is different from
the source directory. To compile the program, change the current directory to the
source directory srcdir and do
venusi. j ava -d destdir xj /num/Maximum . j ava

The Java compiler generates the byte-code file Maximum. class in destdir /xj /num.
To run the program, change the current directory to the destination directory destdir
and do
venusi. java xj.num . Maximum 11 12
The maximum of 11 and 12 is : 12

4.5.4 Organization of the Java Class Library

An integral part of Java is the Java Class library, which in Java 2 comprises more
than 1,900 classes. The Java Class Library consists of the core packages, or rho e
whose names begin with the prefix j ava, and the extension packages, or those who e
names begin with the prefix j avax. These classes are organized as a hierarchy of
packages according to their functionalities. Table 4.6 gives a brief summary of some
of the packages in Java 2.
4.6 Exceptions • 139

TABLE 4.6
Selected Packages in J2SE

Package Description

j ava. applet Support for applets


j ava. awt AWT (A bstract Window Toolkit) for graphical user interfaces
j ava . beans Support for pluggable components
j ava. io Support for input and output
j ava. lang Basic language and run-time support, including manipulation
of simple data types, common mathematical function . threads.
security, and system resource management
j ava . math Support for large numbers
j ava. net Support for network communication
j ava . rmi Support for remote method invocation
j ava. security Support for data encryption and digital signature
j ava . sql Support for Java database connectivity (JDBC)
j ava . text Support for text processing and formatting
j ava. util Support for common utilities, including collections. time
management, and data compression
j ava . vecmath Support for tuples and matrices
j avax. activation Support for the JavaBean Activation Framework
j avax . mail Support for electronic mail
j avax. media Support for a variety of format of multimedia data. including
streaming and tored audio and video
j avax . naming Support for Java naming and directory service (JNDI)
org. omg. CORBA Support for common object reque t broker architecture
(CORBA)

4.6 EXCEPTIONS

Exceptions are 1me.\pected condition in program. . The Java exception-handling


mechanism facilitate. recovery from unexpected ondition or fai lure . There are at
lea. t three reasons for having a pecial mechani m to handle exceptions instead of
u ing the regular control tatement' . Fi1 t, the location at which an exception usually
occurs is not where it can be reasonably dealt with. Therefore, handling exceptions
140 • Elements of Java

disrupts the normal flow of execution and provides a legitimate excu e for using goto
statements. Second, mixing the logic of error handling with the logic of regular tasks
makes a program unnecessarily large and complex and thus more difficult to read and
maintain. Finally, ad hoc methods for error handling, such as longjmp (a common
way of handling exceptions in C), are often platform specific and nonportable.
Moreover, when systems require high reliability and fail-safe processing, an
effective exception-handling mechanism is essential in the effort to deliver such assur-
ance. An exception-handling mechanism should be an integral part of a programming
language if that language is to support effectively the development of large-scale,
complex, and reliable software systems.

4.6.1 Sources of Exceptions

When an exception occurs in a Java program, the normal flow of execution is inter-
rupted, and we say that an exception has been thrown. Exceptions originate from two
sources:

1. The run-time environment (that is, the NM). Performing an illegal operation,
such as dereferencing a null pointer or accessing an array with an out-of-bound
index, causes a run-rime exception to be thrown.
2. Java programs, including the Java Class Library. When a unexpected condition is
encountered in a program, an exception can be explicitly thrown with the thro ..
statement.

A variety of exceptions are defined in the Java Class Library. Programmers may
also introduce their own exceptions.
It is important to point out that most of the run-time exceptions represent logical
errors in programs that should be fixed. Exception handling is only a mechanism
to allow recovery at run-time in the event such an error is encountered. It is not a
replacement for debugging.

4.6.2 Hierarchy of Exceptions

Exceptions are modeled as objects of exception classes. Different types of exception


are characterized by different exception classes, which are organized into a hierarchy
as shown in Figure 4.2.9
The Thro ..able class is the superclass of all errors and exceptions. Only in-
stances of the Thro.. able class, or one of its subclasses, are thrown by the Java Virtual
Machine or can be thrown by Java programs. The different categories of throwables

9 . This diagram onl y shows some of the most common error and exception dusscs. For a complete list o f
Lhc error and exception classes, sec Lhc AP! doc umentation o f the j ava. l ang package.
4.6 Exceptions • 141

Figure 4.2 Assertion Error


Error
Class hierarchy of
errors and excep- OutOfMemoryError
tions.
Arithmetic Exception

ClassCastException

lndexOutOfBoundException

Null PointerException

lllegalArgumentException

NumberFormatException

Interrupted Exception

IQ Exception

are repre ented by various ubcla e . The major categorie are error , exception•.
and run-time exceptions, which are ·ummarized in the follo\ ing table:

Category Description

Error A ubcla of Throwabl e. Error· arc eriou. and fatal


problem' in progrnm . Errors arc Lhro, n by the JVM and
are t picall not handled by regular program. .

Exception ' Ubcla. of Throwabl e. Exceptions can be thrm n by any


progrnm. All u. cr-dcfined exceptions should be a . ubclas.
of Exception.
Runtime.Exception A ·ubcltn of Exception. Run-time exceptions arc cau cd
by ilkgal operations and thrown b the JVM .
142 • Elements of Java

Errors and run-time exceptions are called unchecked exceptions. All others are
called checked exceptions. Checked exceptions must be caught or declared in the
throws clause (see Sections 4.6.3 and 4.6.4). The following are the most common
run-time exceptions:

Run-Time Exceptions Common Causes

Ari thmeticException Dividing an integer by zero


ClassCastException Casting an object to a wrong class (see
Section 5.2.2 [p. 168])
Index0ut0fBound.Exception Accessing an array with an out-of-bound index,
that is, an index that is negative, greater than or
equal to the length of the array
IllegalArgumentException Passing an illegal or inappropriate argument to
a method
NullPointerException Deferencing a reference variable that is null
NumberFormatException Attempting to convert a string to one of the
numeric types, but the string does not have the
appropriate format

The following are some of the most common checked exceptions:

Exceptions Common Causes

CloneNotSupported.Exception Attempting to clone an object whose class does


not implement the Cloneable interface (see
Section 6.3 .4 [p. 231])
Interrupted.Exception Interrupting a thread that is not running (see
Section 11.1.2 [p. 553])
IOException Encountering problems while performi ng
input/output operations (see Section 8.4
[p. 366])
4.6 Exceptions • 143

The following are some of the mo t common errors:

Errors Common Causes

AssertionError An as ertion ha failed ( ee Section 6.2.3 [p. 224])


OutDfMemoryError JVM cannot allocate an object becau e it i out of memory.
and no more memory could be made available by the garbage
collector

Programmers may introduce additional user-defined exception cla e . All u er-


defined exception classes should extend the Exception class, directly or indirectly.
User-defined exception classes should not extend the RuntimeException cla

4.6.3 Throwing Exceptions

Most of the exceptions are thrown by the Java Virtual Machine. However, excep-
tions can also be thrown anywhere in a program to signify that an unexpected or
abnormal condition has occurred and it is impo ible to continue the normal flow of
control. Exceptions can be thrown using throw statements. The yntax of the throw
statement is
throw Exception ;

where Exception is an instance of Throwable or one of it ubclas e .


A method must declare the checked exception that it may throw. u ing the
throws clau e, a follows:
[MethodModijiers] ReturnType MethodName ( [Parameterlist] )
[throws Exception 1, £teeptio11 1 .. . ]
{
Statemellts
}

Statement that may throw a checked exception mu t be pla ed in either of the


following two contexts:
1. In a try-catch tatement with a matching catch block for the exception. uch a
try {
II ...
throw new MyE.~ception();
II . . .
} catch (MyE.~ception e) {
II handle MyExceptlon
}
144 ■ Elements of Java

2. In a method that declares the exception in its throws clause, such as

void aMethod() throws MyException {


II . . .
throw new MyException() ;
II . . .
}

4.6.4 Catching and Handling Exceptions

The main purpose of the Java exception-handling mechanism is to allow an excep-


tion to be caught and handled by a code segment located elsewhere, perhaps a good
distance from the origin of the source. When an exception is thrown, the exception-
handling mechanism will attempt to locate and transfer control to a matching excep-
tion handler. The exception handler detennines whether the exception can be handled
and if so, how. Statements that may throw exceptions can be enclosed in a try-catch
statement.

try {
(statements that may throw exceptions)
} catch (Exception! el) {
(exception handler 1 )
} catch (Exception2 e2) {
(exception handler 2)
} finally {
(finish up (optional))
}

A try-catch statement begins with a try block, which consists of the normal
flow of control. Some of the statements in the try block may throw exceptions. A
try-catch statement can have one or more catch blocks (that is, exception handlers)
and an optional finally block. Each catch block takes a single parameter, who e
type must be Throwable or one of its subclasses. Whe n an exception is thrown, the
catch blocks of the immediate enclosing try-catch statement are searched seque11tially
for a match. A catch block matches the exception thrown if the class of the exception
thrown matches the parameter type of the catch block or one of its subclasses. If a
match is found, the flow of control is transfered to the matching catch block. If a
finally block is present, it will be executed just before the flow of control leave
the try-catch statement, whether or not another exception is thrown.
Suppose that there are two catch blocks for exception classes Exc eption !
and Exc eption2, respectively, and Exception! extends Except i on2. In other
words, Exception! represents a more specific type of exceptions than Excep tion2,
and Except i on2 represents a more general type of exceptions than Ex cep tion !.
Since the catch blocks are searched sequentially, it is imporatnt to pl ace the catch
block for Except i on! before the catch block for Exception2, or the catch block
4.6 Exceptions ■ 145

for Exception! will never be reached. The catch blocks for very general types of
exceptions, such as
catch (Throwable e) or catch (Exception e)

should be avoided, since they catch almost all type of exceptions. If such indiscrim-
inatory catch blocks are needed, they should appear as the Ia t catch block.
There are several options in handling exception : (1 ) Recover from the exception
and resume execution with the statement immediately following the try-catch tate-
ment; (2) throw another exception, and pass the responsibility to another exception
handler; or (3) tenninate the program gracefully when unable to recover from the ex-
ception. In any case, the finally block, if present, will be executed before leaving
the try-catch statement.
Printing out the execution stack trace at the point where the exception occurred
in the catch block, as in the following code segment, i useful for debugging.
catch (AnException e) {
e.printStackTrace();
}

It is usually a bad practice to catch an exception, then ignore it, that i . to do


nothing in the exception handler, as in this code segment:
catch (AnException e) {}

Using Exception Handling


Let us consider the following PurchaseOrder cla s, which contain a method cal -
culateltemTotal () that attempts to calculate the total amount of an order based on
the unit price of the item and the quantity ordered. A precondition of the method i that
the quantity must be greater than or equal to zero. If the quantity i negative, it i an
exception; an error must have occurred somewhere. The calcu.lateltemTotal 0
method cannot proceed after a negative quantity is detected. The only reasonable thing
to do is to notify the caller that an exception has occurred. We compare two olution
to this problem: one uses the exception-handling mechani m. and one doe not.
The first solution does not use the exception-handling mechani m.
public class PurchaseOrder {
public static final double ERROR_CODE1 = .. .
public double calculateltemTotal (double unitPrice, int quanti t y) {
if (quantity< 0) {
II exception
return ERROR_CODEl ;
} else {
// normal condition
return unitPrice • quantity;
}
}
II . . .
}
---

146 • Elements of Java

The caller might catch the exception as follows:


PurchaseOrder anOrder;
II . ..
double total= anOrder.calculateitemTotal( . );
if (total== Purchase0rder.ERROR_CODE1) {
II handle the exception

} else {
II the normal condition
total . . .
II . ..
}

This solution is rather inelegant. Various error conditions might complicate


what started as a simple calculation. Moreover, the error condition is returned to
the caller through a programmer-defined error code named ERROR_CODE1, which
must be carefully chosen. Otherwise, it could be confused with a legitimate value.
In addition, the caller must check the return value for the error code before it can be
used. Otherwise, it could cause more serious problems by treating the error code as a
regular value and using it in other computations.
Using the exception-handling mechanism, we have a simpler and more elegant
solution.
public class PurchaseOrder {
public double calculateltemTotal(double unitPrice, int quantity) {
if (quantity< 0) {
II exception
throw new IllegalArgumentException("negative quantity");
}

II normal condition
return unitPrice * quantity;
}
II . . .
}

Now the caller can catch the exception.


,-- PurchaseOrder anOrder;
try {
II . ..
double total= anOrder.calculateltemTotal( );
total .
II .. .
} catch (IllegalArgumentException e) {
// handle exception
}

Now, the code for handling normal conditions is separated from the code that
handles exceptions. The logic flow under the normal condition can be written more
clearly and succinctly.
4.6 Exceptions ■ 147

Ex AM p LE 4 . 1 1 The Maximum of Two Integers with Exception Handling

PURPOSE
To illustrate the handling of exceptions in the Maximum program in Example 4.8.

DESCRIPTION
The Maximum program in Example 4.8 doe not handle the exception when the input
values are invalid. For example, invoking the program with noninteger argument will
cause a NumberFormatException to be thrown.

venus¾ java Maximum eleven twelve


Exception in thread "main" java.lang.NumberFormatException : eleven
at java.lang . Integer .parseint (Integer.java:426)
at java.lang .Integer .parseint (Integer.j ava:476)
at Maximum .main (Maximum.java:13)

A more robust version of the program i to catch and handle the exception. In thi ca e.
there is no reasonable way to continue when an invalid input value i encountered. So
we cannot recover from the exception, but we can at least give a meaningful me age
in the exception handler.

SOLUTION
.
The maxfmwn of two integer arguments: Muimum2. j ava
public class Maximum2 {
public static void main (String O args) {
if (args.length >= 2) {
try {
int il = Integer.parseint (args[O] );
int i2 = Integer . parseint (args [l]);
System. out. println ( "The ma.i:imum of " + i 1 + " and " +
i2 +" is : "+ (( il >= i2) ? il : i2));
} catch (NumberFormatException e) {
System.out .println ("Invalid input value : "+
e . getMessage O) ;
System.out .println ( "The input values must be integers .");
}
} else {
System . out . println( "Usage: j ava Ma.-<:imum integer! integer2");
}
}
}

Running the program with proper argument ' produces the following re ult

venus¾ java Ma.-<:imum2 12 11


The maximum of 12 and 11 is : 12
148 ■ Elements of Java

Running the program with invalid arguments produces the following results:

venus¾ java Maximum2 eleven twelve


Invalid input value: eleven
The input values must be integers.

Finally, a word of caution on using the exception-handling mechanism. The


exception-handling mechanism is intended for handling rare and abnormal conditions
in programs. Using the exception-handling mechanism to handle normal or typical
conditions will have severe negative impact on the performance of programs.

4.7 A SIMPLE ANIMATION APPLET

Java programming is quite different from conventional programming in many aspects.


Not only it is object-oriented, but it also is framework-based.

Framework-Based Programming
Aframework provides the basic structure and utilities for applications, allowing the
application development effort to be reduced significantly. A framework is extensible
and flexible and hence can accommodate a broad range of application requirements
and functionalities. However, using a framework also means that the conventions and
styles adopted by the framework must be followed and that applications do not have
full control of the system. The top-level control of the system usually resides in the
framework, which is often called the inversion ofcontrol. Applications must cooperate
with the framework to perform their tasks.

Interaction Styles
The way in which typical Java programs interact with users can be categorized in the
following manner:

Active: Programs in this category run actively without input or intervention from
the user. The most common type of active programs consists of animation
programs, such as the applet that we present in Example 4. I 2.
Reactive: Programs in this category perform tasks in reaction to user input,
such as keystrokes, mouse clicks, and menu selections. The Drawing Pad
program discussed in Chapter 9 is an example of such a program.
Hybrid: Hybrid programs combine features from the first two categories. These
programs function by themselves and also react to user input. The Bouncing
Ball with Controls program discussed in Chapter 8 [p. 305] is an example
of a hybrid program.
--
4.7 A Simple Animation Applet • 149

EXAM P L E 4 . 1 2 A Digital Clock Applet-The Initial Version

PURPOSE
To illustrate the basics of the Java applet framework and simple animation.

DESCRIPTION
This applet displays the current time in hours, minutes, and seconds in the followinoe,
fonnat (Figure 4.3):

HH:MM:SS

SOLUTION
An applet does not require a main O method, but it must be a subcla s of j ava
. applet. Applet . The Applet class is actually a skeletal implementation of an
applet. An applet goes through a life cycle as illustrated in Figure 4.4. The following
methods are invoked at various points of the life cycle:

Method Purpose Invoked

ini t 0 Initializes the applet When the applet is initially loaded


start() Activates the applet When entering the Web page that contain
the applet
stop() Deactivates the applet When leaving the Web page that contains the
applet
destroy () Destroys the applet When the Web page that contains the applet
i di carded

Figure 4.3
Applet Viewer: DigitalClock.class llf;J £1
The digital clock Applet
applet.

14:33:09
Applet started.
150 • Elements of Java

Figure 4.4
start
The life cycle of
applets.
Leaving Visiting destroy

1
page page

stop Discarding
page

An applet may override some of these methods. In the digital clock example, we
override three of these four methods, ini t (), start (), and stop (), and define two
other methods: paint () and run ().
All applets are graphical applications, so the graphical appearance of the applet
must be defined. One way of doing so is to use the paint () method to paint the
appearance of the applet directly. Because this is an active applet, it requires a thread
to drive the animation. One way to create a thread is to implement the Runnable
interface and define the run () method, which is the main body of the thread. The
run () method of a thread is analogous to the main () method of an application class.
We begin with the following class declaration, which shows the overall structure
of the program. We present the details of the methods separately.

~tal dock let: DigitalCl,ock. j ava


---~---------
import java.awt.•;
import java.util . Calendar;
public class DigitalClock
extends java. applet.Applet implements Runnable {
protected Thread clockThread = null;
protected Font font= new Font("Monospaced", Font.BOLD, 48);
protected Color color= Color.GREEN;
(start() and stop() methods on page 151 )
(run() method on page 151 )
(paint() method on page I 52)
}

The notation

(Code segment on page 11nn )

indicates a placeholder of a code segment that is defined elsewhere. The page number
indicates where the code segment is defined for easy reference.
The font and color fields specify the font and color to be used in drawing the
numbers. We use a 48-point boldface monospace font and the color green. The fi eld
4. 7 A Simple Animation Applet • 151

clockThread is the thread that keeps the clock running. We discuss threads in detail
in Chapter 11 [p. 547]. However, the use of threads in all animation applets is nearly
identical and can be captured as an idiom (see Section 5.6.2).
The start () and stop () methods activate and deactivate the applet by creating
and killing the thread.

.Methods of class DigitalClock: start O and stop O ' .


public void start() {
if (clockThread == null) {
clockThread = new Thread (this);
clockThread.start ( );
}
}

public void stop() {


clockThread = null ;
}

It is important to deactivate the applet by killing the animation thread. Otherwi e.


the applet would keep running and consuming CPU and memory resources even after
you leave the Web page that contains the applet.
The run () method contains an infinite loop that periodically invoke the re-
paint () method. The rate of update, also called the refresh rate, i controlled by
the argument of the sleep () method. It pecifie the sleep duration in rnilli econd .
As the digital clock shows only hours, minutes, and econd . it need to be updated
only once every second. Therefore, the animation thread can sleep l econd ( l.000
milliseconds) after each update. Also note that the sleep () method may throw an
InterruptedException; therefore, the sleep () method mu t be invoked in ide
a try-catch statement.

Method of cl~ Dig~talCl.?ck: run()


publ ic void run() {
while (Thread.currentThread () == cl ockThread) {
repaint() ;
try {
Thread . currentThread () . sleep(lOOO);
} catch (InterruptedE.xception e){}
}
}

Note that the run() method, calls the repai nt () method, not pai nt O, and
the paint () method i not explicitly invoked. The mi ing link i provided by the

11
152 ■ Elements of Java

framework. The paint() method will be invoked indirectly when repaint O is


invoked. We discuss the details of the interaction between these methods in Section 5.5
[p. 193]. The following conventions are important:
■ Call the repaint() method, not paint O, to change the applet's appearance.
■ Override the paint () method, not repaint (), to describe how the applet
should be drawn.
We use the Calendar class in our paint () method to obtain the current hour,
minute, and second. For obvious reasons, Calendar is a singleton class. An instance
of Calendar must be obtained with the get Instance() method, not the new
operator.

Method of class DigitalClock: aint ()


public void paint(Graphics g) {
Calendar calendar= Calendar.getinstance();
int hour= calendar.get(Calendar.H0UR_0F_DAY);
int minute= calendar.get(Calendar.MINUTE);
int second= calendar.get(Calendar.SEC0ND);
g . setFont(font) ;
g.setColor(color);
g.drawString(hour +
I I . II
+minute/ 10 +minute% 10 +
" . ti +second/ 10 +second% 10,
10, 60);
}

The drawstring () method takes three arguments:

drawstring (srr, x, y )

where str is the string to be drawn and (x, y) specifies the x and y coordinates of the
left end of the string on the baseline, as illustrated in the following diagram.:

Baseline

\ Asample string.
(x, y)
7
The HTML source code for the Digi talClockDemo page follows:

HTML IOUfte: Di 1talClockDemo. html


<!--DigitalClockDemo.html-->
<html>
<head>
<title>Digital Clock Applet</title>
</head>
4.7 A Simple Animation Applet • 153

<body bgcolor=white>
<hl>The Digital Clock Applet</hl><p>
<applet code=DigitalClock . class
width=250 height=80>
</applet>
<p><hr>
<a href=DigitalClock.java>The source</a>
</body>
</html>

The j ava. awt. Color Class


Instances of the Color class represent color . The Color clas is capable of represent-
ing roughly 1.6 million 24-bit colors. The following common colors are predefined
as constants:

Constant Description

BLACK The color black


BLUE The color blue
CYAN The color cyan
DARK_GRAY The color dark gray
GRAY The color gray
GREEN The color green
LIGHT_GRAY The color light gray
MAGENTA The color magenta
ORANGE The color orange
PINK The color pink
RED The color red
WHITE The color white
YELLOW The color yellow

We can create an arbitrary color with

new Color(r, g, b)

where ,. , g, and b are the value of the red, green, and blue components, re pectively.
They fall in the range Oto 255.
154 ■ Elements of Java

The java. awt. Font Class


We can create a font with

new Font (name, style , size )

where name, style, and size are the font family name, font style, and font size, respec-
tively. If a font that exactly matches the description cannot be found, an available font
that closely matches the description will be returned. The attributes of fonts are as
follows.

Name: A string indicating the name of the font. The font name can be either
the physical font name or the logical font name. The physical font names
refer to the actual names of the fonts installed on a particular machine.
The physical font names are platform specific and often machine specific.
The logical font names refer to families of fonts with certain common
characteristics. The logical font names are mapped to physical fonts with
matching characteristics available on a given machine. The following logical
font names are available 10 :

Serif Sans- serif Monospaced Dialog Dialoginput

Programs using logical font names should be able to run on any platfom1
on any machine without change. Programs using physical font names may
need to be customized to use the physical fonts available on a particular
machine.

Style: An integer indicating the style of the font. It can be one of the following
constants, defined in the Font class:

Font.PLAIN Font.BOLD Font . ITALIC

Combination styles can be obtained by joining two or more of the style


constants using bitwise-or. For example, the bold and italic style can be
specified as:

Font . BOLD I Font .ITALIC

Size: A positive integer indicating the size of the font in printer's points. A point
is approximately / 2 inch on printed media and roughly one pixel on monitor
screens.

I~- For backward compalibility, the following logical fon t names are also available, but deprecated,
.
T1mesRoman, Helvetica, and Courier .
Chapter Summary ■ 155

CHAPTER. SUMMARY

■ Java supports two kinds of types: primitive types and reference types. A primitive
type variable holds a value of the type. A reference type variable holds a reference
to an object or array. Wrapper classes convert values of primitive types to objects.
■ Java supports the primitive types boolean, byte, short, int, long, char,
float, and double. Each type has default initial values for initializing variables.
■ Conversion of a type of a narrower range to a type of a broader range is caJJed
widening. Conversion of a type of a broader range to a type of a narrower range
is called narrowing.
■ Java expression and statement syntax is very similar to that of C++.
■ Objects and arrays are stored on a garbage-collected heap. They must always be
created explicitly by using the new operator, array initializer, or string literals.
■ Arrays are objects and are bound-checked.
■ Equality of references and equality of object states are two distinct concepts.
For the reference variables r1 and r2, r1 == r2 refers to the equality of the
references, and r1. equals (r2) refers to the equality of the state of the two
objects.
■ The goto statement was eliminated in Java. Multilevel breaking and the excep-
tion mechanism are used to handle situations in which goto may have been used
in other languages.
■ Classes are the basic building blocks of Java program . A Java program con i ts
of one or more class declarations. A class declaration defines a class. Each class
defines a reference type. A class declaration comprises declaration of fields,
methods, and inner classes.
• Class fields can be initialized via explicit initializers, default initial value , ini-
tialization blocks, and constructors.
• Parameters are passed by value. Values of primitive types must be wrapped inside
a class in order to serve as in-out parameters.
• Strings are objects, not char arrays. The String class i immutable, and the
StringBuff er class is mutable.
• The toStringO method of each clas is implemented to convert the instances
of the class to string repre entation .
• Interfaces declare feature but do not provide implementation. Abstract classes
are classe with partial implementation.
• Packages are used to organize large program into logical and manageable units.
A package may contain clas es. interface , or other packages.
• Exceptions are unexpected condition in programs. The exception-handling
mechanism facilitates recovery from unexpected condition or failures. A tate-
mcnt that may throw an exception mu t be placed in idea try-catch statement that
catches the exception or in a method that declares the exception in its throws
clause.
156 ■ Elements of Java

lEXERCISES

4.1 Write a Java application to calculate the 4.4 Define two Java classes:
factorial of an integer n, using iteration (not Point, with the x and y coordinates, both of
recursion). The factorial function n ! is defined which are double values, as the fields.
as Circle, with the center, an instance of Point,
0!= 1 and radius, a double value, of the circle as
n!=n*(n-1)! the fields.
Write two methods of the Circle class to do
The input value is given as a command-line
the following:
argument to the Java application.
(a) Given a point p and a circle centered at c
4.2 Write a Java application to calculate the average
of a list of integers. The input data is stored with radius r, determine whether p is inside
the circle.
in a text file, in which each line contains a
single integer. The name of the input data file (b) Given the center and radius of two circles,
is given as a command-line argument to the determine whether the two circles touch or
Java application. The output should include the overlap.
number of integers and their average. Write a main() method of the Circle class to
4.3 Write a Java application that uses the data from test each of these methods.
a text file named students. txt to create 4.5 Implement the toString () method for the
a file called studentemail . txt. Toe input Card class in Section 4.4.2. Each suit of cards
file consists of a number of lines, with each should be represented by a single letter:
line containing the data of a student in colon-
delimited format: S for • H for O D for ◊ C for eTa

Last Name: First Name: Social Security Number When the rank of a card is no higher than I 0,
it should be represented by its numeric value.
For example:
When the rank of a card is higher than 10, it
Owen:Michael :326502626 should be represented as

The output file contains student e-mail IDs J for Jack Q for Queen
generated from the information in the input K for J(jng A for Ace
file. Each input record will be converted to an
e-mail ID in the following format: For example, the • King should be represented
as SK.
the first character of the first name +
4.6 Implement a shuffle() method for the Deck
the first character of the last name +
class in Section 4.4.2 to shufiie the deck of
the last 4 digits of the social security number+
"O" + cards. Use Math. random() to emulate the
"se . depaul . edu" randomness of shuffling. Then implement a
deal () method to deal the cards in the shuffled
For example, Michael Owen's new e-mail ID deck to four hands, and use the toString 0
would be method in Exercise 4.5 to print out the hands.
mo26260se.depaul.edu 4.7 Using packages.
(a) Move each of the Java application in
Note that no uppercase letters are allowed in the Exercises 4.1 -4.3 to a package named
new e-mail IDs and that the output file should myprog. single. Compile and run each
contain one e-mail ID per line. of the applications.
Project • 15 7

(b) Move bolh Point and Circle classes The scores of the project and exams are
in Exercise 4.4 lo a package named recorded as integers between 0 and l 00, as
myprog. multi. Compile and run the ap- in the record
plication.
Johnson:Phil:100:90:95 :84:91
4.8 Run the Java application in Exercise 4.2 with
invalid input: The total score of each student i calculated by
(a) Provide an incorrect file name; that is, the u ing the formula
file named does nol exist. Total = (Project I) x 10% + (Project 2) x 10% +
(b) Provide a correct file name, but the input
(Project 3) x 10% + (Midterm exam) x 30% +
file contains invalid contents; for example,
a line contains a noninteger or multiple (Final exam) x 40%
integers. The total core is then converted to a letter grade
Write an improved version of the program using by using the chart
exception handling. This program should give
Total 2:: 90 A
a meaningful message when the input file name
is incorrect, and ignore any lines in the input 90 > Total 2:: 80 B
file that are other than a single integer. 80 > Total 2:: 70 C
4.9 The Calendar class also provides methods to 70 > Total 2:: 60 D
get the current year, month, date, and day of Total < 60 F
the week. Enhance the digital clock applet to
do the following: Write a Java application that wilJ read the
student record file from tandard input and
(a) Display the current year, month, date, and
do the following:
day of week.
(b) Display the time using the 12-hour formal (a) Calculate the total score and the letter grade
instead of the 24-hour format. for each tudent. Print the re ults to tandard
output in the format
For example, the display might be
last name first name total grade
3:05:28 PM
5 May 2002 Tue (b) Calculate the highe t. !owe l. and average
core of the rudent project . exam . and
total core . and print the re ult. to tandard
output.
(c) Count the number of tudcnt in each letter-
grade range, and print the count to standard
4.1 A ssume that you have a tudent record file. output.
It is a text file in which each line contains a A ume that the input file contain. no more
s ingle record, and each record consist of the than I00 record .
following colon-delimited fields :

last name :first name: project 1:project 2 :


project 3: midterm exam :final exam

........... ,
I ,
....

Classes and Inheritance

CHAPTER OVERVIEW
In this chapter, we d iscuss overloading, inheritance, overriding, hiding, subtypes, poly-
morphism, and casting. In addition, we examine issues involved in designing and im-
plementing classes and offer some design guidelines. We also present several more
sophisticated animation applets to illustrate techniques and idioms commonly used in
animation.

5.1 OVERLOADING METHODS AND CONSTRUCTORS


- The methods and con tructor of a cla can be overloaded.

Definition 5.1 Overloadi11g


Overloading refers to the ability to allow different methods or constructors of a
class to hare the smne name. The name i aid to be overloaded with multiple
implementations.

159
160 • Classes and Inheritance

The legality of overloading depends on the signatures of the methods or con-


structors being overloaded. The signature of a method or con tructor consi ts of the
name of the method and a list of the types of its parameters. Note that the return type,
parameter names, and final designations of parameters are not part of the signa-
ture. Parameter order, however, is significant. The following are some examples of
methods and their signatures:

Method Signature

String toString() toString()


void move(int dx, int dy) move(int, int)
void move(final int dx, final int dy) move(int, int )
void paint (Graphics g) paint (Graphics )

The condition under which overloading is allowed is stated as the following rule:

The Rule of Overloading


Two methods or constructors in the same class can be overloaded, i.e., sharing the
same name, if they have either different numbers of parameters or the same number
of parameters but of different types. In other words, no two methods or construc tors
in the same class may have identical signatures.

The following class declaration illustrates overloaded constructors and over-


loaded methods:

public class Point {


protected double x, y;
public Point() {
X = 0.0; y = 0 . 0;
}

public Point(double x, double y) {


this .x = x; this . y = y;
}

/.. calculate the distance between this point and the other point ·t
public double distance(Point other) {
double dx = this.x - other.x ;
double dy = t his.y - other . y;
return Math.sqrt (dx * dx + dy * dy);
}
5.1 Overloading Methods and Constructors • 161

/** calculate the distance between this point and (x,y) ·1


public double distance(double x, double y) {
double dx = this.x - x ;
double dy = this . y - y;
return Math.sqrt(dx * dx + dy * dy) ;
}

r· calculate the distance between this point and (x,y) ·1


public double distance (int x, int y) {
double dx = this.x - (double) x;
double dy = this.y - (double) y;
return Math.sqrt(dx * dx + dy * dy);
}

r· calculate the distance between this point and the origin ·1


publi c double distance( ) {
return Math.sqrt(x * x + y * y) ;
}

II other methods
}

When an overloaded method i called, the number and the type of the arguments
~e used to determine the signature of the method that will be invoked. Overloading
1s resolved at compile time, as in the following code segment:

Point pl= new Point( ) ; // invoke Point( )


Point p2 = new (20 . 0, 30 . 0); // invoke Point{double,double)
p2.distance(p1); // invoke distance(Point)
p2.distance(50.0, 60.0); // invoke distance(double,double)
p2 . distance(50, 60) ; // invoke distance(int,int)
p2. distance() ; // invoke distance( )

Operator Overloading
Operator overloading is a generalization of overloading of method . lt allow. oper-
ators, such a +, *, and ==. etc., to operate on different type of parameter , i.e.,
operands. In Java, operator are overloaded only on built-in operation on primitive
types. For example, the arithmetic operators (+.-, *, / ,%) are overloaded on all nu-
meric type . The only exception is that the operator + and += are al o o erloaded on
class String for concatenation. No other cla e · are allO\ ed to overload operators.
Operator overloading i, an important charncteri 'tic of C++. When u ed properly,
it allows computations to be expre ed naturally and ·uccinctly while maintaining the
conventional meaning of the operator. Operator · hould be defined in accordance with
their usual and conventional meanings. Howewr, there is nothing to prevent operators
from being overloaded in m.i'leading \ a ·. Furthermore, as we learned from C++,
overuse of operator overloading can hamper the readability of programs. Thu the
potential pitfall ' of operator overloading over ·hadow any possible benefits. For thi
reason, Java restri ts operator overloading to its primitive data types and strings.
162 ■ Classes and Inheritance

To Overload or Not to Overload


Overloading is, for the most part, a convenience, not a necessity. It allows methods
to be named naturally and logically. However, the same functionality can often be
accomplished without the use of overloading. Care must be exercised not to overuse
or misuse overloading, which hampers the readability of programs.

Design Guideline Use Overloading Judiciously


Overloading should be used only in two situations:

1. When there is a general, nondiscriminative description of the functionality that


fits all the overloaded methods.
2. When all the overloaded methods offer the same functionality, with some of them
providing default arguments.

In the first case, all the overloaded methods have the same number of parameters
but with different types. An example is the overloaded append () methods in the class
StringBuffer.

public class StringBuffer {


public StringBuffer append(String str) { .. . }
public StringBuffer append(boolean b) { .. . }
public StringBuffer append(char c) { . . . }
public StringBuffer append(int i) { . . . }
public StringBuffer append(long 1) { . .. }
public StringBuffer append(float f) { . . . }
public StringBuffer append(double d) { . . . }
II ...
}

All the overloaded methods fit the following general description:

A string representation of the argument is appended to the end of the current contents
of the string buffer.

Another example in this category is the overloaded max () method in the class Math.

public class Math {


public static i nt max(int a, int b) { .. . }
public static long max(long a, long b) { . .. }
public static float max(float a, float b) { . }
public static double max(double a, double b) { . }
II .. .
}

Again, all the overloaded methods fit the following general description:

The resull is the maximum of the two arguments.


5.2 Extending Classes ■
163

In the second case, the overloaded methods have different numbers of panunete
Usually the one with the most parameters is the base method, which implements t~s.
actual functionality. All other overloaded methods simply delegate the task to the bas:
method. An example is the overloaded substring method in the class String.
public class String {
public String substring (int i, int j) {
II base method: return substring [i .. j - 1]
}
public String substring (int i) {
II provide default argument
return substring (i , length ());
}
II . . .
}

Both methods provide the same functionality: They return a substring. The method
with two parameters is the base method. The method with a single parameter simply
delegates the task to the base method by supplying a default second argument.
Overloading should be avoided in all other situations.

5.1 EXTENDING CLASSES

Inheritance defines a relationship among clas es. When cla C2 inherits from, or
extends , class Cl, class C2 is called a subclass or an extended class of class Cl, and
class Cl is called a superclass of C2. Inheritance is a mechanism for reusing the
implementation and extending the functionalities of superclasses. All the public and •
protected members of the superclass are accessible in the extended classes.•
The extension relationship among classe is the strict form of inheritance, in
the sense that the implementation of the superclass is actually inherited or reu ed in
the ubclasses. Two other relationships can be viewed as weak form of inheritance:
interface extension and interface implementation ( ee Section 5.3 [p. 176]). Java
supports only single inheritance for class exten ion; that i , each cla may not have
more than one superclass. The exten ion relation among cla se form a hierarchy
with the class Obj ect a it root. Every cla other than Object ha. a unique
superclass. If no supercla s i explicitly declared. Object i a urned to be the
superclass. The Object class i the only cla with no upercla . Multiple inheritance
is supported for interface exten ion and implementation. We di cu the ex ten ion aod
implementation of interface in Section 5.3 [p. 176].
Again, the syntax of cla declaration i a hown in the follow ing fragment. The
extends clause specifie the uperclass, and the implements clau e specifies the
interfaces implemented by the cla .
[ClassModifiers] class ClassName
[ extends SuperC/ass]
[implements /11terface 1 , /11te,face~ . .. I {
ClassMe111berDec/amrio11s
}
164 ■ Classes and Inheritance

s.2.1 Constructors of Extended Classes


The initialization of an extended class consists of two phases: (1) the initialization of
the fields inherited from the superclass and (2) the initialization of the fields declared
in the extended class. One of the constructors of the superclass must be invoked to
initialize the fields inherited from the superclass. The constructors of the extended
class are responsible for initializing the fields declared in the extended class. The
following code fragment illustrates the use. of several typical constructors of extended
classes. The ColoredPoint clas extends the Point class to include a new color
field.

import java.awt.Color;
public class ColoredPoint extends Point {
public Color color;
public ColoredPoint(final double x, final double y,
final Color color) {
super(x, y);
this.color= color;
}

public ColoredPoint(final double x, final double y) {


this (x, y, Color. black) ; // default value of color
}

public ColoredPoint() {
color= Color.black;
}
}

The fields inherited from the superclass, x and y, should be initialized by invoking
one of the constructors of the Point class. The new field in the extended class, color,
should be initialized in the constructors of the ColoredPoint class. The constructors
of the superclass are invoked with the keyword super.

• In the first constructor of the ColoredPoint class, super (x, y) invokes the
constructor of the Point class with a matching signature: Point (double,
double) . The invocation of super (. . . ) must be the first statement in the
constructor of the extended class.
• The second constructor of the ColoredPoint class supplies a default value
for the color field. It invokes another constructor of the same class with the
keyword this. The constructor invoked is the one with the matching signature:
ColoredPoint (double, double, Color) . The invocation of this (. . .
) must be the first statement in the constructor.
• In the third constructor of the ColoredPoint class, no explicit invocation using
~uper or this is made. ln this case, the no-arg constructor of the superclass i
invoked implicitly.

. If no constructor is defined in the extended class, the no-arg constructor i pro-


vided by default. The default no-arg constructor simply invokes the no-arg con tructor
5.2 Extending Classes • 165

of the supercla s. For example, the following no-arg constructor will be provided im-
plicitly for the Extended cla s when no constructor is provided explicitly:
public class ExtendedClass extends SuperClass {
public ExtendedClass() {
super();
}
II methods and fields
}

lf the Superclass does not provide a no-arg constructor, a compilation error will
result. If another constructor of the ExtendedClass is present, the no-arg constructor
is not provided implicitly.
The extended class may also u e explicit initializers or the default initial values
to initialize the fields. The following order of initialization applies to the fields in both
the superclass and the extended class:
1. The fields of the superclass are initialized, using explicit initializer or the default
initial values.
2. One of the constructor of the uperclass is executed.
3. The fields of the extended class are initialized, using field initializer or the default
initial values.
4. One of the con tructor of the extended class i executed.
For example, let us consider the classe Super and Extended.
public class SuperClass {
int x = . . . ; II executed first
public Superclass () {
x = . . . ; II executed second
}
II . ..
}
public class ExtendedClass extends Superclass {
int y = . . . ; II executed third
public ExtendedClass () {
super();
y = . . . ; II executed fourth
}
II . ..
}

When new Extended () i invoked to create an in tan e of the e tended class,


initialization proceed a indicated by the comment .

5.2.2 Subtypes and Polymorphism

One of the most important characteri tic of object-oriented programming languages


i the dynamic binding of method . Dynamic bi11di11g of methods refer to the binding
of a method in ocation to a pecifi implementation of the method at nm rime rather
than at compile rime.
166 ■ Classes and Inheritance

Variables and Types, Objects and Classes


Before getting into the details of dynamic binding, we need to take a closer look at
some commonly used terms: variable, object, class, and type. A variable is a storage
location having an associated type. The type of a variable is determined at compile
time, that is, statically, based on the declaration of the variable. The type of a variable
is also known as its declared type. An object is an instance of a class. The class of an
object is determined when the object is created, at run time. A variable of refere nce
type holds a reference to an object. As we will demonstrate shortly, the variable
may hold references to objects of different classes, subject to the substitutability of
subtypes. The class of the object referred to by a reference variable cannot always be
determined at compile time. Sometimes it can be determined only at run time.

Subtypes
The inheritance relationship can be viewed in various ways, as illustrated in Fig-
ure 5. l(a). A subclass extends the capability of its superclass; the subclass inherits
features from its superclass and adds more features . Moreover, a subclass is a special-
ization of its superclass; every instance of a subclass is an instance of the superclass,
not vice versa. For example, every line is a shape but not every shape is a line. Some
features are available in the subclass but not in the superclass. There is yet another
view of classes and inheritance: Each class defines a type. AH the instances of the
class constitute the set of the legitimate values of that type. A s every instance of a
subclass is also an instance of its superclass, the type defined by the subclass is a
proper subset of the type defined by its superclass. The set of all the instances of a
subclass is included in the set of all the instances of its superclass, as shown in Fig-
ure 5.1 (b). The subset relation between the value set of types is known as the subtype
relation.

Definition 5.2 Subtype


Type T1 is a subtype of type T2 if every legitimate value of T 1 is also a legitimate value
of T2. ln thi s case, T2 is the supertype of T 1•

Figure 5.1

Different views
of inheritance:
(a) Extending the
Line
Shape

Rectangle
e Shapes
--~
superclass; and
(b) inclusion of (a)
instance sets. (b)
--
5.2 Extending Classes • 16 7

That T1 is a subtype of T2 is not the same as T 1 and T2 being compatible. The


compatible type relation is symmetric, whereas the subtype relation is not. If T 1 is a
subtype of T2 and T2 is also a subtype of T1, then T1 and T 2 are compatible.
The inheritance relation among classes is a subtype relation. Moreover, each
interface also defines a type, and the interface extension and implementation relations
are also subtype relations (see Section 5.3 [p. 176]). The subtype relation applies to
class types and interface types, as well as primitive types. For example, int is a
subtype of long, since the set of all int values is a sub et of the set of all long
values.

Substitutability of Subtypes
A value of a subtype can appear wherever a value of its supertype i expected. In other
words, a value of a subtype can always substitute for a value of its supertype.

To rephrase this rule in the context of classe and object : An in tance of a ubclas can
appear wherever an instance of its superclass is expected. An instance of a ubclas
can always substitute for an instance of its superclass.

Conversion of Reference Types


The conversion of reference types is governed by the subtype relation. The concept
of widening and narrowing can be extended to reference type .

Definition 5.3 Widening and Narrowing (Reference Type)


The conversion of a subtype to one of it upertype i called widening. The conver ion
of a supertype to one of it ubtype i called 11anv wi11g.

The widening of reference types i alwar allowed and i carried out implicitly
whenever necessary. In other word , a reference to an object of cla . C can be
implicitly converted to a reference to an object of one of the _upercla_ e of C. The
narrowing of reference type require explicit cast. Thu , the narrm ing of reference
types is also known as dow11casri11g. Narro\ ing i · alwa allowed at compile time.
However, it i not always safe, and it may result in run-time exception .
There i also an important difference bet\ een the con er ion of primitive types
and reference types. The con er·ion of primitive type re. ult in a change of the
repre entation of the value · being converted. F r example, con erting an int to
double results in representing the int value in double format. The conver ion of
an objec t reference does 1101 affect the repre entation of the object. Its identity and
tah.: remain the same.
168 • Classes and Inheritance

Polymorphic Assignment
In static programming languages, such as C, the rule of assignments is that the left-
hand side and the right-hand side of an assignment must be of compatible types. In
object-oriented languages a more powerful form of assignment, known as polymor-
phic assignment, is allowed.

Rule of Assignment
The type of the expression at the right-hand side of an assignment must be a subtype
of the type of the variable at the left-hand side of the assignment.

This rule means that the variable on the left-hand side may hold objects of
different classes. In other words, if class E extends class B, any instance of E can
act as an instance of B. In the following code fragment the Student class has two
subclasses, Undergraduate and Graduate:

class Student { . . . }
class Undergraduate extends Student { }
class Graduate extends Student { . . . }

Now we create two instances.

Student studentl, student2;


studentl = new Undergraduate() ; // polymorphic assignment, okay
student2 = new Graduate() ; // polymorphic assignment, okay

In the assignments shown, the types of the expressions on the right-hand side f
are Undergraduate and Graduate, respectively. They are subtypes of the type of
the variable on the left-hand side, Student. Therefore, no explicit cast is necessary.
However, attempting to do the following will result in a compilation error.
Graduate student3;
student3 = student2; // compilation error

An error will be generated because the right-hand side type is Student and the
left-hand side type is Graduate. Even though student2 actually holds a reference to
an instance of Graduate, the declared type of student2 is Student , which is not a
subtype of the left-hand side type Graduate. Type checking is carried out at compile
time and is based on the declared types of variables. An explicit cast is necessary here:
student3 • (Graduate) student2; // explicit cast, okay

Casting a reference variable to a subtype of its declared type is called down cast-
ing. J~va _allows expli~i~ casting of any reference type to any other reference type at
compile lime. The vahd1ty of an explicit cast is always checked at t' If h t
· · I'd run 11ne. t e ca
1s mva 1 , a ClassCastExcepti on will be thrown.
5.2 Extending Classes • 169

In the preceding example, a run-time check will be performed to determine


whether student2 actually holds a reference to an object that is an instance of
Graduate or its subclasses. If not, a ClassCastException will be thrown. In this
case, it is okay. Now, let us consider the following :

student3 = (Graduate) student!; II compilation okay, run-time exception

This statement will not result in a compilation error. However, as student 1 actually
holds an instance of Undergraduate, which is not a subtype of Graduate, a
ClassCastException will be thrown at run time.
Since downcasting may result in exceptions being thrown, it should be done with
proper care. There are two proper ways of downcasting:

1. The pessimistic approach. Use the instanceof operator to check the cla of
an object before downcasting. The expression

exp instanceof Type

returns true if exp is an instance of a class or an interface type named T)pe. It


returns false otherwise. The following program egment uses instanceof to
prevent a potential run-time exception:

if (student! instanceof Graduate ) {


Graduate gradStudent = (Graduate) student!;
} else {
II student1 is not a graduate student
}

2. The optimistic approach. Catch the ClassCastException exception. a fol-


lows:

try {
II . . .
Graduate gradStudent = (Graduate) student!;
II . ..
} catch (ClassCastException e) {
II student1 is not a graduate student
}

Failing to downcast in eitherof these way may lead to run-time failure of the program.

Why Is Downcasting Needed?


Let u a ume that the Graduate cla define a method get ResearchTopic O that
is not defined in the Student class.

Student student!= new Graduate ();


II . ..
student 1 . getResearchTopic () ; II compilation error
II . ..

... __....,..___________________________
170 ■ Classes and Inheritance

Invocation of the getResearchTopic () method through student! results in


a compilation error because the declared type of student! is Student-not
Graduate-even though student! holds an instance of Graduate.
The validity of method invocation is checked statically at compile time and is
based on the declared types of variables, not the actual classes of the objects. There-
fore, student! must be downcast to Graduate before the getResearchTopic ()
method can be invoked.

Student student= new Graduate();


II . ..
if (student instanceof Graduate) {
Graduate gradStudent = (Graduate) student;
gradStudent . getResearchTopic(); // okay
II . ..
}

Then the question is, Why not declare student to be Graduate in the first
place? The possible reasons are as follows :

■ The variable student is a parameter. The actual object referenced by student is


created in some other part of the program and may be an instance of any subclass
of Student.
■ The variable student references to an element retrieved from a collection object,
such as a Map or a Set. The declared type of elements in collections is usually
Object. Downcasting the elements retrieved from a collection to their actual
classes is often necessary (see Section 8.2 [p. 308]).
• The variable student references a object returned by the clone () method. The
return type of the clone() method is declared as Object . Hence a cloned object
needs to be downcast to its actual class.

Array Types
Java arrays are objects. The subtype relationships among array types are defined as
follows. First, all array types are subtypes of Object (e.g., int[] and double[] are
subtypes of Object). Second, if class or interface Y is a subtype of class or interface
type X, then Y[] is also a subtype of X[] .
The following diagram illustrates the subtype relation among array types:

Object

int[] double X

y Y[]
---
5.2 Extending Classes ■ 171

The following sequence is allowed:


Student sal[J;
Graduate sa2[J = new Graduate[4OJ;
II . . .
sal = sa2; II polymorphic assignment
Student student4 = sal[OJ;
Graduate students= sa2 [OJ;

However,
Graduate student6 = sal [OJ ; II compilation error

causes a compilation error, so an explicit downcast is necessary:


Graduate student6 = (Graduate ) sal [OJ ; II okay

5.2.3 Overriding Methods

Methods defined in a superclass can be overridden by methods defined in a subclass.


Here is the definition:

Definition 5.4 Overriding


Overriding refers to the introduction of an instance method in a ubclass that ha the
same name, signature, and return type of a method in the supercla . Implementation
of the method in the subclass replaces the implementation of the method in the
superclass.

Overriding is different from overloading. Overriding is concerned with methods


of different classes that have an inheritance relation hip. In overriding, method hare
the same name, signature, and return type. In contrast, overloaded methods are part
of the same class, but have different ignature .
We can illu trate the distinction between overloading and overriding in the fol-
lowing manner. In
class A {
publi c void ml O { . . . }
publi c void ml (int i) { . . . }
}

class Acontains two overloaded method . An in tance of Acan acce s both methods,
depending on the arguments of the method 'invocation, o

Aa =newA() ;
a. ml O; II Invoke m1 ()
a . ml (1) ; II Invoke m1 (int)
172 • Classes and Inheritance

Now, let us assume that we have the two classes

class B {
public void m20 { . . . }
}
class C extends B {
public void m2 0 {. . . }
}

Implementation of method m2 () in class B is overridden by another implementation


of method m2 () in class C. For a given object, one but not both of the implementations
of method m2 () is available, depending on the class of the object.

B b = new BO;
Cc= new C();
b . m2 () ; II invoke the m2( ) in class B
c . m2 () ; II invoke the m2() in class C

Overriding a method with another method of different signature or return type is


not allowed. For example, the following code segment will cause a compilation error:
class B {
public void m3(int i) { . . . }
}
class C extends B {
public void m3(char c) { . . . }
}

Method m3 () in class B is not overloaded with method m3 () in class C because


they are not in the same class. In C++ this is legal, and it is interpreted as method m3 ()
in class Chiding method m3 () in class B (see Section 5.4 [p. 184] on hiding). Such
permissiveness in C++ offers little help but adds to the complexity of its semantics.
Most likely, the signature of m3 () in class C is a mistake. It could actually be intended
to override method m3 () in class B with the same signature. A C++ compiler would
let it go unnoticed, but the Java compiler would generate an error message.
Let us consider the following example with the declarations for Student, Grad-
uate, and Undergraduate:
public class Student {
public Student(String name) {
this.name= name;
}

public String toString() {


return "Student : "+ name;
}
protected String name;
}

publi c class Undergraduate extends Student {


public Undergraduate(String name) {
super(name);
}
5.2 Extending Classes • 173

public String toStr i ng() {


return "Undergraduate student: " + name ;
}
}

public class Graduate ext ends Student {


public Graduate (String name ) {
super (name);
}
public String toString() {
return "Graduate student: " + name ;
}
}

Note that the instance method t oString () of St udent is overridden in both


its subclasses. Because a variable of Student may hold a reference to an instance of
Student, Graduate, or Undergraduate, implementation of method toSt r i ng () .
which will be invoked in the following method invocation, cannot be detennined at
compile time.
Student student ;
II student is assigned some value
s t udent . toStri ng ( );

Which implementation of method toStri ng O will be invoked depend on the


actual class of the object referenced by the variable at run time, not the declared
type of the variable. This is known as a polymorphic method invocation. in which
implementation of a method is bound to an invocation dynamically at run time. For a
polymorphic method invocation
var. m( . . . );

dynamic binding proceeds as follows:


Step 1. currentC/ass = the claof the object referenced by var.
Step 2. if method m( ) is implemented in rnrrentC/ass
then the implementation of m() in curremClass i invoked.
else currentC/ass = the superclas of currenrClass. and repeat Step 2.
Now, we illustrate the use of polymorphic method invo ation in the cla Course,
which provides an enroll () method to enroll a tudent in the cour e and a Ii t method
to list all the students currently enrolled in the cour e.

public class Course {


public voi d enrol l (Student s ) {
if (s != null && count< CAPACITY)
students[count++] = s ;
}
public void l i st () {
for (int i = O; i < count ; i ++)
System . out.println (students [i] .t oString () );
}
174 • Classes and Inheritance

protected static final int CAPACITY= 40;


protected Student students[] = new Student[CAPACITY];
protected int count= O;
}

Figure 5.2 shows the relationship between the classes. Note that the assignment
in enroll,
students[count++] = s;

is a polymorphic assignment and that the method invocation in list,

students[i] .toString()

is a polymorphic method invocation. Now, we enroll some students-both graduate


and undergraduate-in a course.
Course c = new Course();
c.enroll(new Undergraduate("John"));
c.enroll(new Graduate("Mark"));
c .enroll(new Undergraduate("Jane"));
c .list();

The output is
Undergraduate student : John
Graduate student: Mark
Undergraduate student: Jane

Note that the expression students [i] . toString () was executed three times.
Twice it was bound to the implementation in the Undergraduate class, and once
it was bound to the implementation in the Graduate class.

Final Methods
A method that is declared as final cannot be overridden in a subclass. Final methods
are useful in preventing a subclass from accidentally overriding methods that should

. not be overridden. Usually, such methods collaborate with some other methods or
objects and are expected to honor certain conventions and contracts. Accidental
overriding of these methods could violate the contracts and cause program failures.
Final methods also allow the Java compiler and JVM to optimize byte-code.

Figure 5.2
Student Course
Students and Student [] students
courses. int count
Undergraduate Graduate static final int CAPACITY
void enroll(Student s)
void list()
5.2 Extending Classes • 175

Invoking Overridden Methods


Sometimes it is desirable to invoke the implementation of a method that has been
overridden. Suppose that we have defined an equals () method for the Point class.
public class Point {
public boolean equals(Object other) {
if (other!= null &&
other instanceof Point) {
Point p = (Point) other;
return (x == p .x) && (y == p . y) ;
} else {
return false;
}
}

II other declarations
}

Now, we try to define the method equals() for the class ColoredPoint,
which extends the class Point. The equals() of ColoredPoint overrides the
equals() of Point. However, we do not want to repeat implementation of the
method equals() of Point. We want to use the method equals () of Point
to check the equality of the inherited fields and check only the equality of the
fields declared in ColoredPoint in the equals() method of ColoredPoint. The
equals () method of Point can be invoked via the super reference.
public class ColoredPoint extends Point {
public boolean equals(Object other) {
if (other != null &&
other instanceof ColoredPoint ) {
ColoredPoint p = (ColoredPoint ) other ;
return (super . equals (p) && color .equals (p.color));
} else {
return false;
}
}

II other declarations
}

The keyword super represents an object reference having the same value a. this,
but it behaves as if it were a reference to the superclass.

Restriction
In the following scenario, we have a Polygon clas :
public class Polygon {
public void move (int dx, int dy) { . , }
public void scale (double fact or ) { . . }
public void addVertex (Poi nt p) { }
}
176 ■ Classes and Inheritance

We want to define a Rectangle class, and it is natural to think that Rectangle


should extend the Polygon class. However, the addVertex O method clearly does
not apply to rectangles because rectangles have a fixed ~umber o~ vertices. Thus
it is reasonable to make the addVertex O method unavailable to instances of the
Rectangle class. Such extensions are called restri~tions. Alt.h o.ugh. a restriction is
desirable in some circumstances, it is also problemattc. A restnctlon 1s not a subtype
of its upercla s.

Handling Restrictions in Java


Java does not directly support restriction . However, restrictions can be handled in
Java by overriding the restricted method with an empty body, as in
public class Rectangle extends Polygon {
public void addVertex(Point p) {}
II . ..
}

or by overriding the restricted method and throwing an exception, as in


public class Rectangle extends Polygon {
public void addVertex(Point p)
throws MethodNotSupported {
throw new MethodNotSupported("addVertex");
}
II . . .
}

In the latter case, the throws clause must be included in the method declaration of
the superclass. (See the discussion of the throws clause in Section 4.6 [p. 139].)

5.3 EXTENDING AND IMPLEMENTING INTERFACES

lnterfaces declare features but provide no implementation. Classes that implement an


interface should provide implementations for all the features (i.e., methods) declared
in the interface. Interfaces are intended to capture the common characteristics and
behavior of the classes that implement the interfaces. Two relationships involving in-
terfaces may be considered to be weak forms of inheritance. In the implem entation
relationship among classes and interfaces a class may implement zero or more in-
terfaces. A class does not inherit any implementation from an interface; it provide
implementation for the features declared in the interface. In the extension relationship
among interfaces an interface, called a subinterface, may extend zero or more inter-
faces, called superinterfaces. The subinterface does not inherit any implementation
from the superinterfaces because interfaces contain no implementation. The ubin-
terface contains all the features declared in the superinterfaces. An interface can only
extend other interfaces, not classes .
. Java allows on!y sing/~ inherit~nce for class extension but multiple inheriwnce
for mterface ex ten 10n and mterface implementation. A cla s may implement multiple
5.3 Extending and Implementing Interfaces ■ 177

interf~ces, and an int~rfac~ may e~tend multiple interfaces. Although the Object
class 1s the root of the mhentance hierarchy among classes, there is no single root for
the interface extension and implementation relationships.
A class that implements an interface provides implementation for the abstract
methods declared in the interface by overriding those methods, as illustrated by the
following program fragment:
public interface Myinterface {
void aMethod(int i); II an abstract method
}
public class MyClass implements Myinterface {
public void aMethod (int i) {
II implementation
}
II . ..
}

If a class implements multiple interfaces, it should override aU the abstract


methods declared in all the interfaces.

5.3.1 Subtypes Revisited

Each interface also defines a type. The interface extension and implementation are
also subtype relations. A reference type in Java can be either a class type, an array
type, or an interface type. Now, we can define the complete subtype relations among
reference types.

• If class C 1 extends class C2 , then C 1 is a subtype of C2.


• If interface / 1 extends interface / 2 , then / 1 is a subtype of fi .
• If class C implements interface / , then C is a subtype of / .
• For every interface /, / is a subtype of Object.
• For every type T, reference or primitive type, T[ ] (array of type T ) i a subtype
of Object.
• If type T 1 is a subtype of type T2, then T1[ ] i a subtype of type TJ ].
These relations also imply that, for every class C that is not Object, C i a subtype
of Object.
Implementing multiple interface allow a clas to as ume different roles in
different contexts. Suppose that we have the following two interface , one for students
and one for employee :

public interface Student {


float getGPA O;
II . . . other methods
}
public interface Employee {
float getSalary();
II . . . other methods
}
178 ■ Classes and Inheritance

The FulltimeStudent and FulltimeEmployee classes in the following pro-


gram segment implement the Student and Employee interfaces, respectively:
public class FulltimeStudent implements Student {
public float getGPA() {
II calculate GPA
}
protected float gpa;
II . . . other methods and fields
}

public class FulltimeEmployee implements Employee {


public float getSalary() {
II calculate salary
}
protected float salary;
II . . . other methods and fields
}

A class can also implement both interfaces:


public class StudentEmployee implements Student, Employee {
public float getGPA() {
II calculate GPA
}
public float getSalary() {
II calculate salary
}
protected float gpa;
protected float salary;
II . . . other methods and fields
}

The StudentEmployee class is a subtype of both Student and Employee, as


illustrated in Figure 5.3. Hence instances of .S tudentEmployee can be treated either
as students or as employees. In one context, a student employee can be viewed as a
student:
Student[) students = new Student [. . . ] ;
students(O] • new FulltimeStudent();
students (1) • new StudentEmployee () ; II a student employee as a student
II . . .
for (inti• O; i < students . length; i++) {
. . . students [i] . getGPA () . . .
}

Figure 5.3

Implementation of
Interfaces.
5.3 Extending and Implementing Interfaces ■ 179

In the other context, a student employee can be viewed as an employee:


Employee [] employees = new Employee [ . . . ] ;
employees[O] = new FulltimeEmployee();
employees [1] = new StudentEmployee O; // a student employee as an employee
II . . .
for (inti= O; i < employees . length ; i++) {
. . . employees [i] . getSalary () . . .
}

Therefore, a student employee can play two different roles in two different
contexts. This dichotomy is the key advantage for allowing a class to have multiple
supertypes via interface implementation.

5.3.2 Single Versus Multiple Inheritance

Whether to support single or multiple inheritance is one of the most hotly debated
issues in object-oriented programming language design. C++ supports true multiple
inheritance, whereas Java supports only a limited form of multiple inheritance through
interface extension and implementation. In the preceding example, the StudentEm-
ployee class implements two interfaces but inherits no implementation from the
interfaces, as interfaces have no implementation. As a consequence, the getGPA 0
method is implemented separately in the FulltimeStudent and the StudentEm-
ployee classes. Similarly, the getSalary ( ) method is implemented separately in
the FulltimeEmployee and the StudentEmployee classes. With the type of true
multiple inheritance supported by C++, besides the subtype relationship that allows
the instances of a subclass to play different roles, a subclass can also inherit (i.e.,
reuse) implementation from multiple superclasses. For example, with true multiple
inheritance, we could do the following:
public class Student {
public float getGPA () {
II calculate GPA
}
protected float gpa ;
II . . . other methods and fields
}

public class Employee {


publi c float getSalary () {
II calculate salary
}
protected float salary;
II . . . other methods and fields
}

public class Full timeStudent extends Student {


II implementation of getGPA() is Inherited
II . . . other methods and fields
}
180 ■ Classes and Inheritance

public class FulltimeEmployee extends Employee {


II implementation of getSalary( ) is inherited
// ... other methods and fields
}

II the following is illegal in Java!


II multiple inheritance of classes
public class StudentEmployee extends Student, Employee {
II implementation of both getGPA() and getSalary() is inherited
II . . . other methods and fields
}

Although multiple inheritance among classes supports implementation reuse in


addition to the subtype relation, it is much more complicated than the Java inheritance
model. It is more difficult to implement, less efficient, and difficult to use when the
inheritance relation becomes complicated. Let us extend the preceding example a bit.
Conceivably, both the Student and Employee classes should be subclasses of a more
general class Person. This type of inheritance relation is known as diamond-shaped
multiple inheritance, as illustrated in Figure 5.4, because of its shape. Diamond-
shaped multiple inheritance is not a problem, and in fact it is rather common.

public class Person {


public String getName() {
II . ..
}
protected String name;
}

public class Student extends Person {


public float getGPA() {
II calculate GPA
}
protected float gpa;
II . . . other methods and fields
}

public class Employee extends Person {


public float getSalary() {
II calculate salary
}
protected float salary ;
II . . . other methods and fields
}

II the following is illegal in Java!


// multiple inheritance of classes
public class StudentEmployee extends Student, Employee {
// Implementation of both getGPA() and getSalary() is inherited
II . . . other methods and fields
}

Now, the question is, How many names does a student employee have? Both the
Student and Employee classes inherit a copy of the name field from the Person
5.3 Extending and Implementing Interfaces • 181

Figure 5.4

Diamond-shaped
multiple inheri-
tance relationship. Student Employee

StudentEmployee

Figure 5.5
I Stu$ent I Employee
'i-
Implementation I i

reuse through
1----- -- --'- ------ I I - - - - - - _ .._ - - - - - - - -
I I
I ,------'---~

delegation. Studentlmpl I
I Employeelmpl

FulltimeStudent StudentEmployee FulltimeEmployee

class, and the StudentEmployee class in turn inherits all the fields from both of its
superclasses. Therefore, each instance of the StudentEmployee class contains two
copies of the name field. When we try to access the name field of a student employee,
which copy is accessed? Is it possible to access both copies? Common ense tells us
that there should be only one copy of the name field instead of two for each student
employee. These issues must be addressed when we use true multiple inheritance in
C++, whose semantics are further complicated by name re olution features and virtual
base classes. The complicated semantics of C++ make the implementation and u e of
multiple inheritance difficult.
The restricted form of multiple inheritance in Java is much simpler. Furthermore.
the reuse of implementation can be accomplished by using an alternative mechanism,
as shown in Figure 5.5. Assume that we have two interfaces:

interface Student {
public float getGPA ();
}
interface Employee {
public float getSalary();
}

We can h~ve two classes to implement the e interfaces:

public class Studentimpl implements Student {


public float getGPA() {
// calculate GPA
}
protected float gpa ;
}
182 • Classes and Inheritance

public class Employeelmpl implements Employee {


public float getSalary() {
// calculate salary
}
protected float salary;
}

The implementation in Student Impl and Employee Impl can be directly reused
in the full-time student and employee classes by utilizing class extension:

public class FulltimeStudent extends Studentlmpl {


II method getGPA() and field gpa are inherited
II . . . other methods and fields
}
public class FulltimeEmployee extends Employeelmpl {
II method getSalary() and field salary are inherited
II . . . other methods and fields
}

In addition, a student employee class can be implemented as follows to reuse the


implementation in Studentlmpl and Employeeimpl:

public class StudentEmployee implements Student, Employee {


public StudentEmployee() {
studentlmpl = new Studentlmpl();
employeelmpl = new Employeelmpl();
II . . .
}
public float getGPA() {
return student Impl . getGPA () ; II delegation
}
public float getSalary() {
return employeelmpl. getSalary () ; II delegation
}

protected Studentlmpl studentlmpl;


protected Employeelmpl employeelmpl;
II . . . other methods and fields
}

The implementation technique used in the get GPA() and get Salary() meth-
ods is known as delegation because each method simply delegates the task to another
object, studentimpl and employeeimpl, respectively. The implementation in the
Studentimpl and Employeelmpl classes is reused through delegation.
The simple and restricted form of multiple inheritance adopted in Java coupled
with the delegation technique can accomplish everything that can be accomplished
by using true multiple inheritance.
5.3 Extending and Implementing Interfaces ■ 183

5.3.3 Name Collisions among Interfaces

In Java, a class may extend one class and implement multiple interfaces, and an
interface may extend multiple interfaces. Names inherited from one interface may
collide with names inherited from another interface or class. However, name collisions
are fairly easy to resolve in Java.
Two methods that have the same name offer the following possibilities:
■ If they have different signatures, they are considered to be overloaded.
■ If they have the same signature and the same return type, they are considered to
be the same method. In other words, the two methods collapse into one.
■ If they have the same signature but different return types, a compilation error will
result.
■ If they have the same signature and the same return type but throw different
exceptions, they are considered to be the same method, and the resulting throws
list is the union of the two original throws lists.
For example, let us consider two interfaces and a class that implements both.

public interface X {
public void methodl(int i) ;
public void method2 (int i);
public void method3 (int i);
public void method4(int i) throws Exceptionl;
}

public interfaces Y {
public void methodl(double d);
public void method2 (int i);
public int method3 (int i);
public void method4 (int i) throws Exception2 ;
}

public public class MyClass implements X, Y {


public void methodl(int i) { . . . } // overrides method1 in X
public void methodl (double d) { . . . } // overrides method1 In Y
public void method2( i nt i) { . . . } // overrides method2 in X and Y
public void method4(int i) // overrides method4 in X and Y
throws Exception!, Exception2 { }
}

The two methods named method1 () in MyClass are overloaded. The two
methods named method3 () in X and Y will cau e a compilation error.
Two constants having the ame name i alway allowed. They are con idered to
be two separate constants, a in

public interface X {
static final int a =
}
public interfaces Y {
static final double a a • ••

}
184 ■ Classes and Inheritance

public class MyClass implements X, Y {


void aMethod() {
. . . x. a // the int constant a in X
. . . y .a . . . // the double constant a in Y
}
}

5.3.4 Marker Interfaces

Marker interfaces are empty interfaces, that is, interfaces that declare no methods or
constants. They establish a subtype relationship between themselves and the classes
that implement them. They are intended to mark classes as having certain properties.
The most commonly used marker interface is the Cloneable interface. The Clone-
able interface is used to distinguish classes that can be cloned from those that cannot
be cloned. Only those classes that implement the Clone able interface can be cloned.

HIDING FIELDS AND CLASS METHODS

When a subclass declares a field or class method that is already declared in its
superclass, the field or class method in the superclass is not overridden; it is hidden.

Definition 5.5 Hiding


Hiding refers to the introduction of a field (instance or class) or a class method in a
subclass that has the same name as a field or a class method in the superclass.

Overriding and hiding are different concepts.


• Instance methods can only be overridden. A method can be overridden only by
a method of the same signature and return type.
• Class methods and fields can only be hid4en. A class method or field (instance
or class) may be hidden by a class method or a field of any signature or type.
The following example illustrates these differences:
public class A {
int x;
void y() { . . }
static void z() { . }
}
public class B extends A {
float x; // hiding
void y() { } // overriding
static int z() { } // hiding
}
5.4 Hiding Fields and Class Methods • 185

There is a crucial distinction between overriding and hiding. When an overridden


method is invoked, the implementation that will be executed is chosen at run t 1me.
·
However, when a hidden method or field is invoked or accessed, the copy that will
be used is determined at compile time. In other words, the class methods and fields
(instance or class) are statically bound, based on the declared type of the variables.
Let us consider the following variation of the Point and ColoredPoint example.

public class Point {


public String className = "Point";
static public String getDescription() {
return "Point";
}
// other declarations
}

public class ColoredPoint extends Point {


public String className = "ColoredPoint";
static public String getDescription () {
return "ColoredPoint";
}
// other declarations
}

The instance field className of the ColoredPoint class hides the field of the same
name of the Point class. The static method getDescription () of the Colored-
Point class hides the method of the same name of the Point class.

ColoredPoint pl= new ColoredPoint(l0 .0, 10.0, Color.blue);


Point p2 = pl;
System.out.println(pl.getDescription ());
System.out.println(p2.getDescription ());
System.out.println(pl.className) ;
System.out . println(p2.className);

The output is

ColoredPoint
Point
ColoredPoint
Point

Although, both pl and p2 refer to the ame object, the binding of ~e s~atic
methods and fields is based on the declared types of the variables at compile ume.
The declared type of pl and p2 are ColoredPoint and Point, respectively. Hence
the result. 'd hid' Hid
If the preceding example look confu ing, that is the reason to a:~ 1 mg. -
ing field and tatic method offer little help but hampers the reada~i~ty of pro~rams.
The mies of hiding are intended to resolve coincidental nan1e colhs1ons, that 1s, un-
related features that happen to have the an1e name in subclasses and superclasses.
186 • Classes and Inheritance

Design Guideline Avoid Hiding


Avoid biding fields and class methods. Use different field names and class method
names for unrelated features.

Moreover, it is better to invoke class methods through class names instead of


object references. So instead of

System.out . println(p1 . getDescription());


System.out.println(p2 .getDescription());

we should write

System.out.println(ColoredPoint.getDescription());
System.out.println(Point.getDescription());

Even though Colored.Point. getDescriptionO still hides Point. getDe-


scription () , at least it is less confusing.

APPLICATIONS-ANIMATION APPLETS

In this section, we present some animation applets to illustrate the graphics capability
of Java and important techniques used in animation.

5.5.1 Parameters of Applets

The initial version of the Digital Clock applet is simple but not very flexible. Nothing
can be changed without modifying and recompiling the source code. The flexibility to
change the appearance or behavior of applets is desirable in many situations. Applets
provide this type of flexibility via the parameters supplied in the applet tag.

EXAMPLE 5.1 The Digital Clock Applet- An Enhancement

PURPOSE
To illustrate the use of parameters in applets and enhancing a class by extension.

DESCRIPTION
In this example, we develop a digital clock that behaves the same as the original
version (see Example 4.12) but allows the foreground color to be set as a parameter.
5.5 Applications-Animation Applets ■ 187

SOLUTION
Because• •
we intend
.
to preserve

the behavior of the on·c,o 1·nal di'g•tal
1 cIock , we extend
the or:igmal.vers10_n t? obtam the enhanced version. The only method that needs to be
ovemdden 1s the 1n1 t () method, in which the display color is set.

,......,~dock~:DigitalCloct2.java
import java . awt . Color ;
public class Digita1Clock2 extends DigitalClock {
public void init () {
String param • getParameter("color");
i f ("red" . equal s (par am)) {
color= Color . red;
} else if ("blue 11 .equal s (param) ) {
color= Color . blue ;
} else i f ( 11 yellow" . equal s (par am)) {
color= Color .yell ow ;
} else i f ( 11 orange " . equals (par am)) {
color= Col or . orange ;
} else {
color= Color . green;
}
}
}

The getParameter () method is a method of the Appl et class. It taJces a string


argument-the name of the parameter to be retrieved-and returns a string that is the
value of the specified parameter. The parameters are expected as part of the <applet>
tag in the Web page. If the parameter is not set in the <applet> tag, a null reference
is returned by the getParameter () method.
This enhanced digital clock applet allows the foreground color to be set to any
of four common colors: red, blue, yellow, and orange. If the color is not specified,
the color is set to green by default. The colors can easily be extended to allow more
choices.
The following code fragment is a sample applet tag for the enhanced digital clock:

<applet code=Digita1Clock2 . class width=250 height=80>


<param name•color value•blue>
</ applet>

In general, the applet tag with parameters taJce the following form:

<applet code•class_jile11ame
width•pixe/s height•pixe/s>
<param name•param_name1 value•param_val11ei>

<param name•para111_11 ame,, value•param_v<Jl11e,, >


</ applet>
188 • Classes and Inheritance

5.5.2 An Idiom for Animation Applets


The basic mechanism of animation is to display a sequence of frames. Each frame
shows objects that have moved slightly from their positions in the preceding frame.
When the sequence of frames is shown faster than 10 frames per second, human beings
will perceive continuous motion of the objects. Motion pictures and television work
in the same way but at higher frequencies, typically between 24 and 30 frames per
second.

Ex AM p LE 5. 2 The Scrolling Banner Applet-The Initial Version

PURPOSE
To illustrate animation and text drawing.

DESCRIPTION
In this example, we develop a simple animation applet. It displays a text banner that
moves horizontally from right to left. When the banner moves completely off the left
end of the viewing area, it reappears at the right end (see Figure 5.6).

SOLUTION
The fields of the ScrollingBanner class are summarized in the following table.

Field Description

bannerThread The animation thread


text The text to be displayed
font The font used to display the text
x, y The current position of the text
delay The interval between two consecutive frames in rnilJiseconds
offset The distance moved between two consecutive frames in pixel
d The size of the viewing area

Figure 5.6
■44·1 @ 11t4Vi§►i9Nill&i;ki,iU§ft46ti1W-□1tx1
The scrolling Applet
banner applet.
Java is cool
Applet started.
5.5 Applications-Animation Applets • 189

banner et: Scroll~er. java


import java.awt . *;
public class ScrollingBanner
extends java . applet . Applet i mplements Runnable {
protected Thread bannerThread ;
protected String text;
protected Font font=
new java . awt.Font("Sans-serif", Font . BOLD , 24);
protected int x, y ;
protected int delay= 100;
protected int offset= 1;
protected Dimension d;
(init() method on page 189)
(paint() method on page 190)
(start(), stop(), and run() method on page 190)
}

The ini t () method handles the initialization of the applet. It retrieves two
parameters: delay and text. The get Size() method returns the size of the viewing
area in the Web page. The result is of type Dimension, which is declared as

public class java . awt .Dimension {


public int width, height ;
}

The initial position of the text is set at the rightmost position in the viewing area.
The geometry of the drawing is illustrated in Figure 5. 7.

alScroll er class: Wt()


public void init ( ) {
// get parameters "delay" and "text"
String att = getParameter ( "delay" );
if (att != null) {
delay• Integer.parselnt(att);
}
att = getParameter( "text" );
if (att != null) {
text= att;
} else {
text= "Scrolling banner .";
}

Figure 5.7 (0, 0) . .


Length v,ewmgarea
Drawing of the
scrolling banner. . . J.ava.is co.a J.avais c.ool ...ava is . c.ool.
(x,y) (d . wi dth , y)
\ ( - length, y) Rightmost position
Leftmost position (d .wi dt h- 1, d .height - 1)
190 ■ Classes and Inheritance

II set initial position of the text


d = get Size O ;
x"' d.width ;
y = font .getSize();
}

The paint () method paints the current frame. The key to the animation is that
the position of the text has to be adjusted from the previous frame. If the text fall s out
of bounds, it should be repositioned.

public void paint(Graphics g) {


II get the font metrics to determine the length of the text
g . setFont(font);
FontMetrics fm = g.getFontMetrics ( );
int length= fm.stringWi dth(text);
II adjust the position of text from the previous frame
x -• offset;
II if the text is completely off to the left end
II move the position back to the right end
if (x < -length)
x • d . vidth;
II set the pen color and draw the background
g.setColor(Color.black);
g.fillRect(O , O,d.width,d .height) ;
II set the pen color, then draw the text
g.setColor(Color .green);
g . drawString(text, x, y);
}

The control portion of the applet, which consists of the bannerThread variable
and the start O , stop() , and run() methods, is nearly identical to that of the
digital clock applet.

public void start() {


bannerThread • new Thread (this);
bannerThread.start ( );
}
public void stop () {
bannerThread • null;
}
public void run() {
whi le (Thread . currentThread() •= bannerThread) {
try {
} Thread . currentThread( ) .sleep (delay);
5.5 Applications-Animation Applets ■ 191

catch (Interrupted.Exception e){}


repaint();
}
}

Why Use ini t ()?


Usually, initialization of an object state is done in the constructors of the class. How-
ever, you may have noticed that for applets, the initialization is not done in the
constructors but in the method ini t (). Would it be the same if the initialization
were moved to a constructor of the applets? The answer is no. The reason is that
an applet is not a full-blown program, and it must be invoked by another program
referred to as the applet context. The applet context is either a Java-enabled Web
browser or the appletviewer. The applet context is responsible for interpreting the
<applet> tag in the HTML file and initializing and executing the applet. The key
factor here is timing. The applet context first creates an instance of the applet, us-
ing its no-arg constructor. At this point, the applet instance has no knowledge of any
information specified in the <applet> tag. Specifically, a call to getSize () from
the no-arg constructor of an applet would return a O x O dimension, and getParam-
eter () would return null. The information specified in the <applet> tag will be
available to the applet only after it has been created. That is why any initialization
that depends on the size and parameters specified in the <applet> tag must be done
in the method ini t (). The method ini t () is invoked by the applet context after
the information specified in the <applet> tag is processed and made available to the
applet.

Idiom: Animation Applets


You may have noticed that the basic structures of the animation applets presented
so far are nearly identical. That fact is not a coincidence. The common structure
represents a general implementation scheme of animation applets. We capture this
scheme as an idiom. An idiom is a way to repre ent a template implementation of a
recurring problem that may be customized and adapted in different contexts.

Idiom Animation Applet

Catego,y: Behavioral implementation idiom.


Intent: For an applet to update continuou ly its appearance without user input
or intervention.
Also known as: Active applet.
Applicability: Use the animation applet idiom to animate dynamic processes .
192 ■ Classes and Inheritance

The generic structure of the top-level class of an animation applet can be de-
scribed as follows:
public class AnimationApplet
extends java . applet . Applet implements Runnable {
Thread mainThread = null;
int delay;
public void start(){
if (mainThread == null) {
mainThread = new Thread(this) ;
mainThread . start O ;
}
}
public void stop() {
mainThread = null ;
}
public void run() {
while (Thread. currentThread() == mainThread) {
repaint();
try {
Thread.currentThread().sleep(delay);
}
catch (InterruptedException e) {}
}
}
public void paint(java.awt . Graphics g) {
(paint the current frame)
}

(other methods and fields)


}

When using this idiom to create an animation applet, you should copy the code
shown in Courier font verbatim, but you can replace the names shown in italic with
any other names. Field main Thread is the main control thread. Field delay determines
the refresh rate.
..
The java.avt.FontMetrics Class
The FontMetrics class provides the metrics for each font design. The most com-
monly used metrics are those illustrated in the following diagram, along with the
methods for retrieving these metrics:

L d' .. J..... . .... . . . .... . ..


D
:::ir.A g..· . . . . ·
· · · ··· : ·· ···· ..
: ! ~eight
esce.ntf ..... i . . .. ..... ;... .
Baseline
.(; >'
Width
5.5 Applications-Animation Applets ■ 193

Method Description

getAscent () Return the ascent of the font.


getDescent () Return the descent of the font.
getHeight () Return the height of the font.
get Leading() Return the leading of the font.
stringWidth (str) Return the width of string str when it is drawn using the font.

It may seem natural for the Font class to provide a method, ay, getFontMet-
rics () , to return an instance of the FontMetrics class corresponding to a given
font. However, the getFontMetri cs O method is not provided by the Font class,
but by the Graphics class instead. The reason is that the metrics of a fon t depend not
only on the font family and the size and style of the font, but also on the re olution of
the graphics context in which the characters will be rendered. Therefore, the metrics
of a font should be obtained as follows, assuming that g is an instance of Gr aphi cs:

Font font • new Font(name , style , size) ;


g . setFont(font) ;
FontHetrics fm • g.getFontHetrics () ;

5.5.3 Double-Buffered Animation

The ScrollingBanner has a minor glitch. If you pay clo e attention, you will notice
that it flickers. To understand the cause of the flickering, we need to look more clo ely
at how frames are painted. The animation process is controlled by the whil e loop in
the run method. During each iteration, the thread sleeps for a hart period of time
before calling the repaint() method. The repaint() method call another method:
update O. The default implementation of the updat e O method

■ clears the background by filling it with the background color (u ually, the default
background color is gray or white),
■ sets the pen color to the foreground color, and
■ calls the pai nt () method.

In the pai nt ( ) method, the background i repainted- this time in black- and then
the text is drawn. Because the painting i done directly on the screen, it cannot be
completed instantaneou ly. The background painting may take long enough for the
human eye to notice; hence the flickering effect.
194 ■ Classes and Inheritance

EXAMPLE 5.3 The Scrolling Banner Applet-Using Double-Buffering

PURPOSE
To illustrate double-buffered animation and enhancing a class by extension.

DESCRIPTION
This version of the scrolling banner applet behaves basically the same as the initial
version but eliminates the flickering.

SOLUTION
The solution to avoid flickering is quite simple. Instead of painting each frame directly
on the screen, we first paint it in a temporary buffer in memory. When the frame has
been completed, we copy it from the temporary buffer to the screen in a single step.
The painting in progress is not visible because it is done in a temporary buffer. Copying
an image from memory to the screen is usually supported by graphics hardware so that
it can be done fast, resulting in smoother motion from frame to frame. This technique
is commonly called double-buffering, or off-screen drawing.
Now, we try to improve the scrolling banner applet by using double-buffering.
The main functionality of the moving text remains the same, so we simply need to
extend the original applet.

Daable-balraed banner a let: Scroll1~Bmmer2 .j ava


import java.awt . •;
public class ScrollingBanner2 extends ScrollingBanner {
protected Image image; II The off-screen image
protected Graphics off screen; II The off-screen graphics
public void update(Graphics g) {
II create the offscreen image if it is the first time
if (image•• null) {
image• createimage(d . width, d.height);
offscreen • image.getGraphics();
}
II draw the current frame into the off-screen image
// using the paint method of the superclass
super .paint(offscreen);
II copy the off-screen Image to the screen
g . drawimage(image, 0, 0, this);
}

public void paint(Graphics g) {


update(g);
}
}
5.5 Applications-Animation Applets • 195

We override the default implementation of the update() method. Our new


update() method uses double-buffering, which requires two additional fields:

Field Description

image An off-screen image that is the same size as the viewing area
off screen A graphics context associated with the off-screen image

An image object is simply a matrix of pixels. We cannot directly draw into an


image object. A graphics object must be created for drawing into the image by calling
the getGraphics () method. With the graphics object, drawing into an off-screen
image is no different from drawing directly on the screen. ■

EXAMPLE 5.4 The Bouncing Ball Applet

PURPOSE
To illustrate the Animation Applet Idiom, graphics drawing, and double-buffering.

DESCRIPTION
Our new animation applet shows a ball moving inside a rectangular box (see Fig-
ure 5.8). The ball reverses direction when it touches any of the four sides of the box.

SOLUTION
The fields of the BouncingBall class are as follows:

Field Description

color Color of the ball


radius Radius of the ball in pixels
x, y Current position of the ball
dx, dy Distance moved between two con ecutive frames in the x and y
directions in pixel
image Off-screen image
off screen Off-screen graphic
d Size of the viewing area
196 • Classes and Inheritance

Figure 5.8

The bouncing ball


applet.

1· ' ' . - ·...


·.'. ·. -~t ~!::~~--~::~~ ~L:~ :~ . .~-- ~- - ~ ~-;,••,. ,-.·~;\J
•'"1; ~ ~~ ~ ,r,• :f''':.~'!-..... ,..,,~.. ~- ,$')"',.-X:·0 -:- ~... >~-

'--...
~- ~P , ~
~l.:) ~\·
-~ "'v ~\Y
0/ "\.
u
~ _j J i111port java.awt.•; , &-C '\
~ ...) public class BouncingBall ~
3 <' ~ extends java.applet.Applet implements Runnable {
~ !°)· protected Color color = Color. green;
protected int radius= 20;
protected int x, y; . ~ ~
protected int dx = -2, dy = --4; ➔ ~ ,~
~rotected Image image; ~ ~
protected Graphics offscreen;1:.......... ---x.,_~ ..s.5f'~
0
protected Dimension d;
..-_ public void initO {
String att • getParameter("delay");
if (att !• null) {
delay• Integer .parseint(att);
}
d • getSize () ;
x • d.vidth * 2 / 3 ;
y • d.height - radius;
}

public void update(Graphics g) {


II create the off-screen image buffer
// if it Is invoked the first time
if (image•• null) {
image• createimage(d.vidth, d . height);
offscreen • image.getGraphics() ;
}
5.5 Applications-Animation Applets ■ 197

II draw the background


offscreen.setColor(Color .white);
off~creen.fillRect(0,0,d.width,d.height)·
II adJust the position of the ball '
II reverse the direction if it touches
II any of the four sides
if (x < radius I I x > d.width - radius) {
dx = -dJc;
}
if (y < radius I I y > d .height - radius) {
dy = -dy;
}
x += dx;
y += dy;
II draw the ball
offscreen.setColor (color);
offscreen.fill0val(x - radius, y - radius,
radius* 2, radius* 2);
II copy the off-screen image to the screen
g.drawimage(image, 0, 0, this);
}

public void paint(Graphics g) {


update(g);
}

// The animation applet idiom


protected Thread bouncingThread ;
protected int delay= 100;
public void start() {
bouncingThread = new Thread (this);
bouncingThread . start( );
}

public void stop() {


bouncingThread = null;
}

public void run() {


while (Thread.currentThread() •• bouncingThread) {
try {
Thread.currentThread().sleep(delay);
} catch (InterruptedException e){}
repaint();
}

}
}

The j ava. awt. Graphics Class
The Graphics class is an abstraction of different displaying devices, such as screens,
printers, and off-screen images. It encapsulates the details and characteristics of these
devices and allows us to treat them unifonnly.
198 • Classes and Inheritance

The Graphics class is fairly large and provides the basic capabilities for two-
dimensional graphics. More sophisticated two-dimensional graphics capabilities are
provided in a subclass Graphics2D.
Two groups of methods are commonly used in this class: methods for setting and
retrieving attributes and methods for drawing. The methods for setting and retrieving
attributes are summarized in the following table. In the parameters of the methods,
color is an instance of the class Color and font is an intance of the class Font.

Method Description

void set Color (color) Set the current color.


void setFont (font) Set the current font.
void setPaintMode() Switch to the paint or overwrite
mode.
void setXORMode (color) Switch to the XOR mode.
Color getColor () Get the current color.
Font getFont () Get the current font.
FontMetrics getFontMetrics() Get the font metrics of the
current font.
FontMetrics getFontMetrics (font) Get the font metrics of the
specified font. ·

Drawing methods are divided into two categories: (1) methods that have a draw
prefix, which draw the outlines of certain shapes with the current color; and (2) meth-
ods that have a fill prefix, which fill the interior areas of certain shapes with the
current color. The methods for drawing various shapes are summarized as follows.

Text String
Baseline
dravString(String s, int x, int y)
\?Xy
(x, y)

The ordered pair (x, y) specifies the left end of the string on the baseline.

Line Segment
void dravLine(int xl, int yl,
int x2, int y2)

The ordered pairs (x1, Y1) and (x2, Y2) specify the two ends of the line segment.
5.5 Applications-Animation Applets ■ 199

Rectangle
(x, y)
void drawRect(int x, int y, int w, int h)
void fillRect(int x, int y, int w, int h)
DF w

The ordered pair (x, y) specifies the upper-left comer of the rectangle, and wand h
specify the width and height, respectively, of the rectangle.

Oval (x, y)
void draw□val(int x, int y, int w, int h)
void fill □val(int x, int y, int w, int h)
=□
.
• ····· ··· •·rh
. . . -~ .....
w

The ordered pair (x, y) specifies the upper-left comer of the surrounding rectangle;
and w and h specify the width and height, respectively, of the surrounding rectangle.

Round-Cornered Rectangle
(x, y)
void drawRoundRect(int x, int y, int w,
int h, int rw, int rh)
void fillRoundRect(int x, int y, int w,
{
rh
int h, int rw, int rh)
w

The ordered pair (x, y) specifies the upper-left corner of the surrounding rectangle;
w and h specify the width and the height, respectively, of the surrounding rectangle;
and rw and rh specify the width and height, respectively, of the quarter ovals at the
comers.

Three-Dimensional Highlighted Rectangle


(x, y)
void draw3DRect(int x, int y, int w, int h,

□}
boolean raised)
void fill3DRect(int x, int y, int w, int h,
boolean raised)
w

The ordered pair (x, y) specifies the upper-left comer of the rectangle; and w and h
specify the width and height, respectively, of the rectangle. The boolean parameter
raised has the following effects:

■ true: Draw a raised three-dimensional rectangle; and


■ false: Draw a sunken three-dimensional rectangle.
200 • Classes and Inheritance

Arc
void drawArc(int x, int y, int w, int h,
int rs, intra)
void fillArc(int x, int y, int w, int h,
int rs, intra)

i ..... ::: ::, .... ·•\•,·:: ...... i· ......


w

The ordered pair (x, y) specifies the upper-left corner of the surrounding rectangle;
w and h specify the width and height, respectively, of the surrounding rectangle; and
rs and ra specify the starting angle and the angle covered by the arc, respectively, in
degrees.

5.5.4 Reading Files in Applets

An applet is not allowed to read or write files on the client machine-the machine
on which the applet is executed. However, an applet is allowed to read files from the
server host-the host from which the applet is downloaded.

The URL
The URL class represents the Universal Resource Locator (URL). A URL talces the
following form. 1

http : //java.sun.com:80/document/index.html#section2

lt consists of the following components, not all of which need be present.

URL Component Example

Protocol http: / /
Host address j ava. sun. com
Port :80
Path /document/index.html
Section reference #section2

_I. The dou_ble slash (If) in a ~eb site address does not indicate a comment as it does in program code. When
1thappe~ s m program code, ll must be enclosed by double quotes, as in: "http:// j ava. sun . com/ index
. tm1 .
5.5 Applications-Animation Applets ■ 201

URLs can be constructed by using one of the following constructors of the URL
class.

URL Constructor Description

URL (spec ) Construct a URL represented by a string spec.


URL (url, path) Construct a URL by giving a base URL url and a relative
path path to the base URL.

For example,
URL urll = new URL ( "http : //j ava. sun . com/index.html" ) ;
II Construct URL: http:l{java.sun.com/index.html
URL url2 = new URL (urll, "document/intro . html");
II Construct URL: http:/{java.sun.com/document/intro.html

The following two methods of the Applet class return the URL of the document
page and the Java class file , respectively:

Method Description

getDocumentBase () Return the URL of the Web page in which the applet is
emdedded.
getCodeBase () Return the URL of the applet class file.

An applet can read a file by first constructing a URL, u ing the relative path of
the file to be read. A text file named filename can be read a follow :
URL url = new URL (getDocumentBase () , filename) ;
BufferedReader in=
new BufferedReader (new I nputStreamReader (
url .openStream ()));
String line;
try {
while ((line= in.readLine () ) != null ) {
II process line
}
} catch (IOException e) {}
We di cu s the BufferedReader and InputStreamReader classes in Sec-
tion 8.4 [p. 366].
202 ■ Classes and Inheritance

Images and Audio Clips


The Applet class also provides methods to easily access image and audio files by
giving their URLs.

Method Description

getimage (url) Retrieve the image stored at url.


play(url) Retrieve and play the audio clip stored at url.

Symptoms Possible Causes and/or Fixes

Applets run fine with The most likely cause is that all the byte-
the applet viewer code files (. class files) were not transfered
but do not run in a to the Web server.
browser.

Unlike C and C++ compilers, which generate


a single object file for each source file, the
Java compiler may generate multiple byte-
code files for each source file. The Java
compiler generates a . class file for each
class, even when several classes are put in
the same source file.

■ Java allows methods and constructors to be overloaded. Operator overloading


is limited to operations on primitive types and string concatenation. Methods
or constructors of the same class but with different signatures can be over-
loaded.
■ Inheritance is a mechanism for reusing the implementation as well as extending
the functionality of a superclass. The class extension relation is the strict form of
-- . . - -----
Chapter Summary ■ 203

inheritance, in which the implementation of the superclass is actually inherited


or reused in the subclasses. The interface extension and implementation relations
can be considered as weaker forms of inheritance, in which no implementation
is inherited from the interfaces. Only single inheritance is allowed for class ex-
tension. Multiple inheritance is allowed for interface extension and implementa-
tion.
■ Subtypes are relations among types. Every legitimate value of a subtype is also
a legitimate value of its supertypes. A value of a subtype can appear wherever
a value of its supertype is expected. The subtype relation is defined by class ex-
tension, interface extension, and implementation. Downcasting refers to casting
a reference type explicitly to one of its subtypes.
■ Polymorphic assignment refers to the kind of assignments allowed in object-
oriented programming languages, in which the right-hand-side expression of
an assignment can be an object of many different types, so long as the type
is a subtype of the type of the left-hand-side expression of the assignment.
Polymorphic method invocation refers to method invocations that can be bound
to different implementations at run time. Such binding occurs when a method is
overidden in subclasses.
■ Overriding is different from overloading. Overriding has to do with methods in
different classes having the same signature and return type. Overloading deals
with methods in the same class having different signatures. Overriding is also
different from hiding. Instance methods can only be overridden. Static methods
and fields can only be hidden. A static method or field may be hidden by a static
method or a field of a different signature or type.
■ Interfaces declare class features but provide no implementation.
■ Java does not support the kind of true multiple inheritance supported by C++.
Java supports only a restricted fonn of multiple inheritance through interface
extension and implementation. The Java inheritance model allows a type to have
multiple supertypes. The reuse of implementation from multiple upercla es can
be emulated in Java through delegation.
■ Classes should be designed, organized, and implemented in a way that is easy
to understand, is safe to use, and provides complete functionality. Public fields
should be avoided. Public classes should conform to the canonical form de cribed
in Section 6.3.
■ Basic graphics capabilities are provided by the Graphics class, along with the
supporting classes Color, Font, FontMetrics, and Dimension.
■ Applets cannot read or write files from the client machine; they can only read
files from the host server.
■ Double-buffering refers to a technique used in animation to reduce flickering.
This technique is al o called off- creen drawing. Double-buffering improves the
quality of animation significantly.
-
204 ■ Classes and Inheritance

5.1 A polymorphic array of shapes. (iii) Print the string representations of all the
shapes stored in the array to standard
(a) Implement a simple class hierarchy of
output.
shapes, which consists of an interface.
Assume that the input file contains no more
than 100 shapes.

Shape Attributes 5.2 Modify the BouncingBall applet so that it


contains documentation comments that can be
processed by the j avadoc utility. Then use
LineSegment The coordinates of
javadoc to generate HTML pages from your
two end points
modified version of the BouncingBall applet.
Rectangle The coordinates of the You should document every class, method,
upper-left comer, the return type, field, and parameter. Use the tags
width, and the height author,version, since,param, returns,
Circle The coordinates of the and see.
center and the radius 5.3 Write a java applet that uses the getCode-
Base () and getDocumentBase () methods to
display the applet's code base and document
Shape and three classes, LineSegment, base, respectively. Hint: Use the toString ()
Rectangle, and Circle, that implement method of the URL class.
the Shape interface. Each shape has the 5.4 Enhance the digital clock applet to allow the
following attributes: font to be customized via another parameter in
All attributes are integers, and the unit the applet tag.
is pixel. The toString () method should
5.5 Write an animation applet that shows an analog
be defined for all shapes to give string
clock.
representations of the shapes.
(b) Write a Java app to do the following: 5.6 Enhance the ScrollingBanner applet with
the following functionalities:
(i) Read a text file that contains descrip-
tions of different shapes from standard (a) Allow the font and color to be set by
input. Each line of the input file is a parameters.
description of a shape in one of the (b) Allow the banner to move in different
following formats: directions: left to right, upward, downward,
and diagonally.

Rectangle x and y (width and height)


Circle (x, y) (radius)

All numbers are integer literals. Fields 5.1 Write an applet that retrieves a text file contain-
are delimited by white spaces.
ing a list of points, and produce a scatterplot
(ii) Create instances of the shapes de- in a two-dimensional coordinate system. Each
scribed in the input file and store them line of the input file contains a single point,
in an array of Shapes. and each point is represented by two integers
Projects • 205

that specify the x and y coordinates and are 5.3 Write an applet that retrieves sales data as
separated by white spaces. parameters in the same format as in Project 5.2,
5.2 Write an applet that retrieves sales data as and display the data using a bar chart.
parameters, and display the data on a pie chart. 5.4 Extend Exercise 5.1 by adding a draw()
The format of the parameters is the following: method to each of the shape classes that draws
the respective shape. Write an applet that reads
<par am name=categories value="cat 1 • • • cat0 "> a file that contains descriptions of different
<param name=cat 1 value=amount 1> shapes, and draw the shapes using the draw ()
method.
<param name=cat 0 value=amount 0 >

The amount for each category is represented


as an integer. For example, the following are
sample sales data for a software superstore:

<param name=categories
value="education utility
entertainment reference"
<param name=education value=10000>
<param name=utility value=12000>
<param name=entertainment value=30000>
<param name=reference value=9000>
From Building Blocks to Projects

CHAPTER OVERVIEW
In this chapter, we examine the issues involved in designing classes: organizing files and
classes, and using the canonical form of public classes. We present a number of important
design guidelines. We also briefly discuss the documentation of classes and methods and
the use of the j avadoc utility. We introduce the concepts of contracts, preconditions,
postconditions, and invariants, and using the assertion facility to check preconditions,
postconditions, and invariants at run time. Finally, we briefly discuss two useful tools:
JUnit for unit testing of classes and Ant for project building.

6.1 DESIGN AND IMPLEMENTATION OF CLASSES

6.1 .1 Public and Helper Classes


There are two kind of cla ses. The first kind compri e cla e for general u e, known
as public classes. A public cla mu t be declared public and re ide in a file wh~ e
name coincides with the cla name. For example, the public cla Point must re. ide
in a file named Point. j ava. The econd kind compri e cla . e that are u ed olely
for implementing other cla. e . The e cla se are called aiLtilimJ, or helper, ~la es.
They hould not be declared publi . If nn au. iliary cla upports a ingle public class,
207

.... ,.
208 • From Building Blocks to Projects

Figure 6.1 public interface List {


public int size();
The List Interface. public boolean isEmpty();
public Object element(int i);
public Object head();
public Object last();
public void insert(Object item, inti);
public void insertHead(Object item);
public void insertLast(Object item);
public Object remove(int i);
public Object removeHead();
public Object removeLast();
}

it should then reside in a separate file. It should reside in the same file as the class it
supports. If an auxiliary class supports a multiple public class in the same package, it
may reside in a separate file.
Throughout this chapter, we use an interface List as shown in Figure 6.1 and a
class LinkedList, which implements the List interface using a doubly linked list,
as examples. 1 Now, let us first consider the organization of the LinkedList . j ava
class. The LinkedList. j ava class is a public class and resides in a file named
LinkedList . j ava. Since the implementation uses a doubly linked list, the
LinkedList class requires an auxiliary class Node, which represents the nodes in
the doubly linked lisL The Node class is used only in implementation and should not
be exposed to clients. The Node class should not be public and should reside in the
same file as the LinkedList class (i.e., the LinkedList. j ava file). There are two
options for the Node class:

1. The Node class can be a separate class. In file LinkedList. j ava,

public class LinkedList implements List {


protected Node head, tail;
protected int count;
II . ..
II implementation of linked list
}
class Node {
Object element;
Node next, prev;
}

2. The Node class can be a nested class of the LinkedList class. In file Linked
List . java,

public class LinkedList implements List {


protected Node head , tail;

I. The List interface


. and the LinkedList
. class discussed in thi s chapter are not th c same as an d s h ou Id
not be confused with the List interface and the LinkedList in the Java Collcctio F k h' I ·
discussed later in Chapter 8. n ramcwor , w IC 1 15
6.1 Design and lmplementadon of Classes • 209

protected int ' count;


static protected class Node {
Object element;
Node next, prev;
}
II . ..
II implementation of linked list
}

The difference between the two is that in option 1 the Node class is not accessible
to any subclass of LinkedList not in the same package, whereas in option 2 the Node
class is accessible to subclasses of LinkedList.

6.1.2 Class Members

In Java, the order of the class members is insignificant. In other words, reordering
the members of a class does not alter the semantics of the class. This characteristic
allows the members of classes to be ordered to maximize the readability.
The fields and methods of a class should be ordered according to their accessi-
bility and roles. The methods of a class should be organized into groups appearing in
the following order:

■ Public constructors
■ Public accessors, or selectors, which are methods that do not modify the state of
objects
■ Public mutators, or modifiers, which are methods that modify the state of objects
■ Nonpublic constructors and auxiliary methods

A recommended organization of a public class is as follows:

public class AC/ass {


(public constants)
(public constructors)
(public accessors)
(public mutators)
(nonpublic field )
(nonpublic auxiliary methods or ne ted classes)
}

Figure 6.2 shows the organization of the LinkedList clas .

6.1.3 Design Guidelines

In thi section we examine the is ue involved in de igning cla es. We discuss a


number of basic guidelines for clas and package de ign.
21 0 • From Building Blocks to Projects

Figure 6.2 package mylist;


public cla.s s LinkedList implement List {
Organization of the II constructor
LinkedList class. public LinkedList() { · }
II accessors
public int size() { · · · }
public boolean isF.mpty~) {. · · · }
public Object element(int i) { · }
public Object head() { · · · }
public Object last() { · · · }
II canonical form methods
public boolean equals(Object other) { . . . }
public int hashCode O { · · · }
public Object clone() { }
public String toString() { . }

II mutators
public void insert(Object item, inti) { • • • }
public void insertHead(Object item) { }
public void insertLast(Object item) { . . . }
public Object remove(int i) { . . . }
public Object removeHead() { . .. }
public Object removeLast O { . .. }

II fields
protected Node head, tail;
protected int count;
// auxiliary nested class
static protected class Node {
Object element;
Node next, prev;
}
}

Avoid Public Fields

Design Guideline Avoid Public Fields


There should be no nonfinal public fields, except when a class is final and the field is
unconstrained.

For a field named attr, an accessor, also known as a getter, named getAttr()
should be provided to access the value of the field. Optionally, a mutator of the field,
also known as a setter, named setAttr() may be provided to modify the value of the
field. When a field is of type boolean, its accessor should be named isAttr( ).
6.1 Design and Implementation of Classes • 211

Figure 6.3

Rectangular and
polar coordinates.
y

According to this guideline, in the Point class, instead of making x and y public,
they should be nonpublic, and accessors and mutators should be provided for them.

public class Point {


public Point() {}
public Point(final double x, final double y) {
this . x = x; this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(final double x) {
this.x = x;
}

public void setY(final double y) {


this.y = y;
}
protected double x, y;
}

One may think that public fields such as x and yin Point are straightforward and
hannless and that accessors and mutators for such simple field are overkill. However,
that assumption is not correct. Let us consider the clas PolarPoint. It extends the
Point class and represents a point in both rectangular coordinate (x, y) and polar
coordinates (r, a), where r is the radius (the distance from the point to the origin) and
a is the angle between the x ax.is and the line that connects the point and the origin,
as depicted in Figure 6.3.

public class PolarPoint extends Poi nt {


public PolarPoint() {}
public PolarPoint(final doubler, final double a) {
this . r = r; this.a z a;
polarToRectangular();
}
21 2 ■ From Building Blocks to Projects

public double getRadius() {


return r ;
}
public double getAngle() {
return a;
}
public void setRadius(final doubler) {
this . r = r;
polarToRectangular();
}
public void setAngle(final double a) {
this.a= a;
polarToRectangular();
}
public void setX(final double x) {
this . x • x;
rectangularToPolar();
}
public void setY(final double y) {
this . y = y;
rectangularToPolar();
}
protected doubler, a;
protected void polarToRectangular() {
x = r • Math . cos(a);
y = r * Math.sin(a);
}
protected void rectangu.larToPolar() {
r = Math.sqrt(x * x + y * y) ;
a = Math . atan2(y , x);
}
}

Now, the fields in PolarPoint are constrained by the following invariant:

x = r * cos(a) /\ y = r * sin(a)
If the fields r and a were public, a client would be able to do the following and leave
the object in an inconsistent state:

PolarPoint p • new PolarPoint();


p. r • 100. 0 ; // p could be inconsistent

Using mutators setR () and setA () , we can maintain the invariant by adjusting
fields x and y accordingly when field r or a is modified. Making fields r and a
nonpublic and requiring clients to use setR () and set A() to modify these fields
ensures that the instances of PolarPoint remain in consistent states.
PolarPoi nt p • new PolarPoint();
p . setR(l00 . 0); // p remains consistent
6.1 Design and Implementation of Classes • 21 3

If the fields x and y were public in Point, the Point class itself would not be
harmed because x and y are unconstrained. However, that would allow the clients of
PolarPoint to modify directly the fields x and y, resulting in an inconsistent state.

PolarPoint p = new PolarPoint ();


p . x = 100. o; II p could be inconsistent

Thus seemingly harmless public fields can hinder the ability of subclasses to maintain
consistency. However, when the x and y fields are nonpublic, their modifiers, setX ()
and setY (), can be overridden in PolarPoint to ensure consistency.
If a class has no public fields, the clients of the class can only access the
functionality or services provided by the class via its public methods. Therefore, a
corollary of the avoid public field design guideline is the following design guide-
line:

Design Guideline Completeness of the Public Interface


The set of public methods defined in the class should provide full and convenient
access to the functionality of the class.

Separating Interface from lmple~entation


Now, assume that we want to design and implement a list, which is an ordered
collection of elements that can be accessed through an index. The size of the li t
can grow as needed. Such a list can be implemented in various ways, such as a Unked
list or dynamically resizable array. In this case, we should separate the list interface
from the list implementation.

Design Guideline Separate Interface from Implementation


When the functionality supported by a class can be implemented in different ways, it
is advisable to separate the interface from the implementation.

The advantages of separating interface from implementation are that the imple-
mentation details are completely hidden from the client , o change in implementa-
tion will not affect clients. The List interface hown in Figure 6.1 define the interface
of the list we want to implement.
Implementation of the li t can be provided in everal different clas e , all of
which implement the List interface. For example, both of the following classes,
214 • From Building Blocks to Projects

Linked.List and DynamicArray, implement the List interface, but their imple-
mentation can be entirely different:
public class LinkedList implements List {
(body of LinkedList)
}

public class DynamicArray implements List{


(body of DynamicArray)
}

Separating an interface from the implementation of a class in Java is similar to


separating a class into a header file and an implementation file in C++. However,
there are a few subtle differences between Java and C++ in this regard. In C++, the
header and the implementation files are two parts of a class. In Java, however, the
interface and the class are two separate entities that have an implementation relation.
In C++, the header file contains the interface and some of the implementation of the
class. All the fields-even protected or private-must be declared in the header file.
In-line methods must also be defined in the header file. In Java, however, an interface
contains no implementation. The implementation is entirely encapsulated in the class
that implements the interface.

6.1.4 Documenting the Source Code

A common problem in software development is that as software evolves, the docu-


mentation and the actual code of the software could easily become out of sync. As
more and more changes are being made to the software code, the documentation and
the code could drift farther and farther apart. Eventually, the documentation could
become unusable, making further changes to the software code very difficult. Java
supports an excellent and unique approach for combining source code with documen-
tation and other reference materials together, in the form of documentation comments
(doc comments for short) in the source code. The goal is to make it easier to keep the
documentation and the code in sync by keeping them close to one another.
With the help of a Java utility tool called j avadoc, a full set of reference docu-
mentation can be built in two steps. First, a set of specially formatted comments can
be added to document each class, method, and field in a program. Then the j avadoc
tool can be used to generate documentation of either individual classes or entire pack-
ages. The j avadoc utility, which comes with the JDK, parses the declarations and
doc comments embedded in Java source files and generates a corresponding set of
HTML pages for each class and each package. The generated HTML documetation
contains declaration of the classes, methods, and fields, along with doc comments
provided in the source code. In addition, the tool generates indices of all the features
and an inheritance tree for each package depicting the inheritance hierarchy of all the
classes in the package.
Each doc comment describes a feature, which can be a class, a field, a method, a
constructor, or a nested class. The doc comment must immediately precede the feature
it describes. A doc comment consists of a description of the feature, which will be
6.1 Design and Implementation of Classes • 21 5

copied verbatim to the documentation page, followed by a list of tags, which will be
formatted by j avadoc in a consistent style. The commonly used tags are summarized
in the following table:

Tag Description

aauthor The author(s) of the feature. Multiple author tags are allowed.
aversion The current version of the feature.
Gsince When or in which version the feature first appeared.
aparam The meaning and the acceptable values of a parameter of a method.
A method may have multiple param tags.
areturn The meaning and the possible values of the return value of a
method.
asee A link to the documentation of other related classes or method .
athrows An unhandled exception that might be thrown by a method

For each public class or interface, a doc comment documenting the class or
interface should be provided. The following is an example of doc comment describing
the LinkedList class:
; ..
• Class LinkedList implements the interface List , which is an
• ordered collection of elements that can be accessed through
• an index . The size of a LinkedList can grow as needed .

• Oauthor Xiaoping Jia


• Oversion 1.0 06/25/02
• Osince JDKl.1
•I
public class LinkedList implements List {
II . . .
}

A doc comment should be provided for each public method or constructor.


The Oparam tag should be used to describe each parameter of the method, and the
Oreturn tag should be used to describe the return value, if any. The following is
an example of doc comment describing the element() method of the LinkedList
class:
/o
• Retrieves the i-th element from the l i nked list .
•• Oparam i The position of the element to be retri eved.
• Oreturn Returns the i - th element in the list .
21 6 • From Building Blocks to Projects

* Osee #insert (Object item, inti)


•I
public Object element ( int i) {
II . . .
}

CONTRACTS AND INVARIANTS

6.2.1 Contracts of Methods

Each interface or class defines a set of services, i.e., the public methods, to be
provided by an implementor of the interface or by the class itself. The method
declarations only specify the type of the methods, not the behavior. A contract of
a method is a specification of the behavior of the method, i.e., what service is to be
provided by the method. Contracts of methods are often specified informally or even
left unspecified. Informal or missing contracts are major contributors of problems
in software development. The potential problems of informally specified contracts
include these:
■ Incompleteness and silence on some aspects of the behavior.
■ Ambiguity and multiple interpretations.
■ Contradictions with other contracts.
Informal contracts often lead to misunderstandings and misuses of the services. An
alternative is to specify the contracts of methods formally, i.e., mathematically. The
advantages of formally specified contracts include the following:
■ Precision and unambiguousness.
■ Run-time checking to catch violations of the contract by the implementation as
well as misuses of the services by the clients.
■ Facilitation of reasoning about the behavior of both the implementation and the
clients.
There are specification languages that support formally specifying the complete
behavior of software systems, including the algebraic specification language Larch
and model-based specification languages Z and VDM. However, the effective use
of these formal specification languages requires extensive skills in mathematics. Its
high cost is only justifiable for highly critical applications. Nevertheless, limited use
of formal ly specified contracts can be easy and very cost-effective . We advocate
the use of formal contracts to define the behavior of all methods in all classes. The
formal contracts do not necessarily need to be complete. Their main goal is to ensure
that certain constraints of the services are met and that certain conditions of the
implementations are maintained.
The contract of a method can be specified using preconditions and postcondition
of the method. A precondition of a method is a boolean expression that mu t hold
when the method i invoked. In other words, a method may not be invoked when it
6.2 Contracts and Invariants • 21 7

precondition is false. A postcondition of a method is a boolean expression that holds


when the method invocation returns. We document the precondition and postcondition
of a method in the doc comments with the special tags @pre and @post.2

I**
* Opre precondition
* Opost postcondition
•I
public void aMethod( ) {
II . ..
}

The preconditions and postconditions must be boolean expressions. The @pre


and @post may occur multiple times. The precondition or postcondition of a method
defined by mutltiple @pre or @post tags is the conjunction of all the boolean expres-
sions in the @pre or @post tags, respectively.
The preconditions and postconditions can be specified using the Java expression
syntax. However, the Java expression syntax is too limited for specifying the precon-
ditions and postconditions in some situations. Therefore, we introduce some minor
extension to the Java expression syntax so that the preconditions and postconditions
can be more expressive. The following two special names can be used in preconditions
and postconditions:

Oresul t A variable holding the return value of a method.


Ono change A boolean expression stipulating that the state of the object is
not changed by the method.

The following two boolean operators are often quite useful and can be used in
preconditions and postconditions.

==> logical implication, i.e., a => b, if and only if a is false or both a and
bare true.
<=> logical equivalence, i.e., a <=> b, if and only if both a and b are true
or both a and bare false .

Let us have a look at the formal contracts of ome of the method of the List
interface:

I**
• Returns the number of elements in t he li st .

• Opre true
• Opost Onochange
•I
public int size ();

2. The Opre and Opost tags and other tags for documenting con1ruc1 an~ invari~ts nre nol supported by
the slundard javadoc tool. However, there are third-party tools thnt provide vnnous levels of support for
these tags.
21 8 • From Building Blocks to Projects

/u
* Returns true if and only if the list is empty.
*
• Opre true
• Opost Oresult <=> size() > 0
* Opost Onochange
•I
public boolean isEmpty();

A precondition of true means that the precondition is always satisfied. There-


fore, the size() and isEmpty O methods can be invoked any time. A postcondition
onochange means that the method does not modify the state of the object; i.e., it is
an accessor. Both size() and isEmpty O methods are accessors.

/o
* Returns the i-th element in the list.
*
* Opre i >= 0 kt i < size()
* Opost Onochange
•I
public Object element(int i);

The element () method has a precondition. It means that the method can only
be invoked when the precondition is true. Otherwise, an exception will be thrown.
The postcondition only specifies that the state of the list does not change when this
method is invoked. However, it does not completely specify the behavior of the
method. The simple specification language we use here is inadequate to completely
specify some of the behaviors of the methods. A number of full-blown specification
languages, including Larch, VDM, and Z, are capable of completely specifying the
behaviors of programs and facilitating reasoning about their behaviors. However,
our goal of using formally specified contracts is much more modest. We only try
to formally constrain the behavior as much as possible for the purpose of precisely
documenting the behavior and instrumenting the source code with assertions derived
from the contracts. Such assertions can be extremely effective in assisting the testing
and debugging of programs.
The following are the contracts for methods head () and last (). They use the
element () method to constrain their results.

I**
* Returns the first element in the list .

* Opre !isEmpty()
* Opost Oresult •• element(O)
• Opost Onochange
•I
public Object head();
6.2 Contracts and Invariants ■ 219

I**
• Returns the last element in the list .
*
• Opre !isEmpty()
* Opost Oresult == element(size () - 1)
• Opost Onochange
•I
public Object last();

When specifying the postcondition of a mutator, i.e., a method that changes the
state of the object, it is necessary to distinguish the state of the object before and after
the method invocation. By default, the values of the expressions in the postcondition
are evaluated with respect to the after state of the object, i.e., the state of the object
immediately after the method returns. To refer to the values of expressions evaluated in
the before state, i.e., the state of the object immediately before the method is invoked,
we use the following prestate notation.

Expression Opre The value of Expression evaluated in the slate just before the
method is invoked, i.e., the prestate.

When specifying contracts involving a collection of objects, such as a list, it is


often necessary to use quantified expressions, which define conditions on a collection
of objects, rather than on an individual object. There are two type of quantified
expressions. A universally quantified expression stipulates that a predicate holds on
every object in a given colJection of objects. An existentially quantified expression
stipulates that a predicate holds on at least one object in a given collection of objects.
We introduce the following extensions of quantified expression that can be used in
preconditions and postconditions:

Oforall x: Range@ Expression Univer ally quantified expre ion


Oexists x : Range O Expression Existentially quantified expre ion

where x is a new variable name, Range i a range expre ion. which pecifies a
collection of objects, and fa:pression i a boolean expre ion. The range expres ion
can be one of the following:

[ m .. II] Both m and II are integer expre ion . It define an integer range
from m to 11 , inclusive.
Expression The Etpressio11 mu t evaluate to a Collection. Enumeration: or
Iterator object ( ee Section ,_). It define a r~nge th~t consists
of all the object in the collection. enumera1ton, or Herator,
respectively.
ClassName It define a range con i ting of all the in tance. of the clas .
220 ■ From Building Blocks to Projects

The contract of the insert () method is as follows:

/u
* Inserts a new element into the list at the i-th position.
*
* Opre item!= null kk i >= 0 kk i <= size()
* Opost size()== size()Opre + 1
* Opost Oforall k: [O .. size() - 1] 0
* (k < i ==> element(k)Opre == element(k)) kk
* (k == i ==> itemOpre == element(k)) kk
* (k > i ==> element(k - l)Opre == element(k)
•I
public void insert(Object item, inti);

The precondition of the insert() method specifies the possible range of the
insertion positions, from O to size O. The postconditions of the insert () method
are the following:

1. The size of the list will increase by 1 after the insertion.


2. The universally quantified expression specifies that
a. all the elements before the insertion position remain unchanged.
b. the element at the insertion position is the new item, after the insertion.
c. all the elements after the insertion position shift one position toward the end.

The insertHead () and insertLast () methods are special cases of the in-
sert () method, where the insertion position is at the begining and the end of the list,
respectively.

/u
* Inserts a new element at the head of the list.
*
* Opre item != null
* Opost size()== size()Opre + 1
* Opost itemOpre == element(O)
• Opost Oforall k: [1 .. size() - 1] 0 element(k - l)Opre -- element(k)
•I
public void insertHead(Object item);
/u
* Inserts a new element at the end of the list .

* Opre item I• null
* Opost size()•• size()Opre + 1
* Opost itemOpre •• element(size() - 1)
* Opost Oforall k: [O . . size() - 2] O element(k)Opre == element(k)
•I
public void insertLast(Object item);
6.2 Contracts and Invariants ■ 221

The preconditions of the remove () method are these:


1. The list must not be empty.
2. The position of the element to be removed must be in the valid range of the list,
i.e., from Oto size() - 1.

The postconditions of the remove () method are these:

1. The return value is the element at the ith before the method is invoked, i.e., the
element being removed.
2. The size of the list will decrease by l after the removal.
3. All the elements before the removal position remain unchanged, and all the
elements after the removal position shift one position toward the front.

I**
* Remove the element at the i-th position .
*
* Opre size()> 0
* Opre i >= 0 && i < size ()
* Opost Oresult = element(i)Opre
* Opost size()== size ()Opre - 1
* Opost Oforall k : [O . . size () - 1] 0
* (k < i ==> element (k) Opre == element (k)) &&
* (k >= i => element (k + l ) Opre = element (k))
•I
public Object remove(int i);
The removeHead () and removeLast () methods are special cases of the re-
move () method, where the removal position is at the begining and the end of the list,
respectively.

I**
* Remove the element at the head.
*
* Opre size()> 0
* Opost Oresult = element(O)Opre
* Opost Oforall k : [l . . size () 1] O element (k + l)Opre -- element(k)
•I
public Object removeHead( );

I**
* Remove the element at the end .
*
* Opre size()> 0
* @post Oresult = element(size () - l )Opre
* Opost Oforall k : (0 . . size 0 1] o element (k)Opre -- element (k)
•I
public Object removeLast ();
Specifying the contact of the methods in the List interface requires that all
classes that implement the List interface must honor the e contracts. In other words,
222 ■ From Building Blocks to Projects

the preconditions and postconditions of the methods in ~e interface s~ould become


the preconditions and postconditions of the correspondmg methods m the classes
implementing the interface.

6.2.2 lnvaraints of Classes


The state of an object is transient if it is being manipulated, i.e., if one or more of the
methods of its class are being executed. The state of an object is stable if it has been
initialized and is not being manipulated, i.e., if one of the constructors of its class has
been invoked on the object and none of the methods of its class are being executed. An
invaraint of a class is a formally specified condition that always holds on any object
of the class whenever it is in a stable state. Given the invariants of a class, an object
of the class is in a well-fonned state if the invariants hold on that state.
Invariants of a class are usually concerned with the soundness of the imple-
mentation of the class. Consider the LinkedList class shown in Figure 6.2, which
implements the List interface. It uses a doubly linked list to represent a list. The
key characteristics of the doubly linked list representation can be captured by the
following conditions on the implementation:
1. If the list is empty, both head and tail should be null.
2. If the list is not empty, the head field points to the first node in the list, and the
tail field points to the last node in the list.
3. The count field should be equal to the number of nodes that are reachable by
following the next link from the head of the list, i.e., the node pointed to by the
head field.
4. For each node that is reachable from the head of the list, the following conditions
hold:
a. The prev field points to the preceding node in the list, and the next field
points to the succeeding node in the list. In other words, given a node in the
list, the next field of its preceding node should point to the node itself, and
the prev field of its succeeding node should also point to the node itself.
b. The prev field of the first node in the list is null, and the next field of the
last node in the list is null.
The doubly linked list representation is accessed or manipulated by the methods
of the class to perform various operations. The doubly linked list representation is
well formed if all the preceding conditions hold whenever a linked list object is in a
stable state. These conditions are the invariants of the doubly linked list representation
used to implement the LinkedList class. We can write an auxiliary method of the

; ..
LinkedList class to check whether the invariants hold.

• Invaraiants of the linked list implementation.


•I
protected boolean _wellformed() {
int n-= O;
6 .2 Contracts and Invariants • 223

for (Node p = head; p != null; p = p.next) {


n++;
if (p .prev != null) {
if (p.prev . next != p) return false ;
} else {
if (head != p) return false;
}
if (p . next != null) {
if (p.next .prev != p) return false ;
} else {
if (tail != p) return false;
}
}
return n count;
}

We introduce a new documentation tag for documenting the invariants of a class.

/o
* Oinvariant Expression
•I
public class AClass {
II . . .
}

The expression in the @invariant tag must be a boolean expression. The @in-
variant tag may occur multiple times. The invariant defined by multiple occurrence
of the @invariant tag is the conjunction of all the boolean expressions in these @in-
variant tags. The implication and equivalence operators, as well as the quantified
expressions, can also be used in invariants. Since invariants alway deal with the tate
of objects at a given moment, the prestate notation is not meaningful in invariant .
The invariant for the LinkedList class can be expre ed a follow :

I /o
* Oinvariant
*I
_wellformed ()

public class LinkedList implements List {


II . . .
// implementation of linked list

protected boolean _wellformed() {


I I .. .
}

protected Node head. tail;


protected int count;
static protected class Node {
Object element ;
Node next, prev ;
}
}
224 ■ From Building Blocks to Projects

The implementer of a clas i respon ible for ensuring that all public methods
pre erve the invariants concerning the well-fom1edness of the implementation. The
requirements are stated in the following de ign guideline:

Design Guideline Preserving Invariants


Given a clas and its invariant , the obligations for the implementation to preserve the
invariants are the following:
Establishing invariants by public constructors: For each public constructor of the
class, the invariant must be the postcondition of the constructor or implied
by its postcondition.
Preserving invariants by public methods: For each public method of the class, the
invariant can be assumed to be a precondition of the method, and the invariant
must be the postcondition of the method or implied by its postcondition.

If these requirements are satisfied, then the invocation of any public method in
any order should always result in a well-fonned state.

6.2.3 Assertions

An assertion is a boolean condition at a given location of a program that should be


true whenever the flow of execution reaches that location. Java supports assertion
stateme111s,3 which check assertions at run time. The syntax of the assertion state ment
is the following:
assert Assertion ;

where Assertion is a boolean expression. An assertion statement evaluates the asser-


tion first. If the assertion is true , the statement has no other effects. If the a sertion
is false , an AssertionError exception is thrown.
Assertions can be used to perform run-time checking of preconditions and po t -
conditions of methods and invariants of classes.

• As ertions on the preconditions should be placed at the entry point of each


method, i.e., the beginning of the method body.
• Assertions on the postconditions should be placed at every exit point of each
method, i.e., the end of the method body and before each return statement.
• As ertions on the invariants of the cla s should be placed at the entry point and
every exit point of each public method.

. The following is a~ i_mpl~mentation of the head () method of the doubly linked


It t cla s. The precond1t1on 1s translated into an a. sertion at the beginning of the

3. A scrtions arc introduced in JOK 1.4. A JDK 1.4 or later is necessary to use assertions.
6.2 Contracts and Invariants • 225

methods, and the postcondition i Iran lated into an assertion ju t before the return
statement.
I**
* Returns the first element in the list.
*
* Opre !isEmpty( )
* @post @result== element (O)
*I
public Object head () {
assert !isEmpty();
Object result= (head !=null ? head . item nul l ) ;
assert result = element(O) ;
2

return result ;
}

The following is an implementation of the insert () method. In addition to


the assertions derived from the precondition and the po tcondition of the method.
the invariant of the doubly linked list class is also translated into assertion at the
beginning of the methods and just before the return statement.
/u
* Inserts a new element i nto the list at the i-th position .
*
* Opre item != null && i >= 0 && i <= s ize ( )
* @post size ()== size () Opre + 1
*I
publi c void insert (Object item, i nti ) {
assert item !z null && i >= 0 && i <= size ();
assert _wellformed();
int size_pre = size ();
if (i <= 0) {
insertHead (item);
} else if (i >= count) {
insertLast(item);
} else {
// i > 0 && i < count ;
Node n = head;
for (int j = O; n != null && j < i - 1; j ++) {
n = n . next ;
}
Node node= new Node();
node . item= item ;
node . next = n .next;
node . prev = n;
node.next.prev = node ;
n.next = node;
count++;
}
int size_post = size ( ) ;
assert size_post •• size_pre + 1 ;
assert _wellformed() ;
}
226 ■ From Building Blocks to Projects

To compile a program with assertions requires JDK 1.4 or higher and the
-source switch must be used, as follows:

venus¼ javac -source 1.4 LinkedList . java

You may notice that not all the postconditions are being asserted. Postconditions
that deal with the prestate of objects are usually more complicated to assert. It is often
necessary to clone the object before it is modified. We will show another version
of this method that asserts the postcondition involving the object in prestate (see
p. 233).
Using assertions derived from the preconditions for all methods is known as
defensive programming. Its aim is to prevent a component from being misused. An
assertion failure of a precondition indicates that a client attempted to use the service
improperly. In the case of an error, assertions on preconditions can help to e stablish
whether the error is caused by the implementation of the service or the improper use
of the service.
Using assertions derived from the postconditions and the invariants of classes can
be extremely effective in assisting the unit testing and debugging of the implemen-
tation of classes. Incorrect test results can show the symptoms of errors. It is often
a tedious, time-consuming, and nontrivial process to locate the source of the errors.
An assertion failure of a postcondition or invariant usually identifies the source of an
error.

Design Guideline Use Assertions Aggressively


Each method should include assertions on the preconditions and postconditio ns of
the method and invariants of the class.

6.2.4 Design by Contract

An important principle of the object-oriented approach is that the extension and


implementation relation model the is-a relation in the real world. This mean that
if class S is a subclass of class C, then an instance of S is an instance of C, and
an instance of S can be substituted for an instance of C . The type compatibility
rules o~ ~~ject-oriented languages allow such substitutability. H owever, the type
compaub1!Jty rules do not ensure that such substitutions will be well behaved at run
time, i.e., whether the instance of Swill behave in a way that is consistent with how an
instance of C would. To ensure that such substitutions are well behaved at run tim e,
6.3 The Canonical Form of Classes • 227

the implementation of class S must honor the contract of class C. This requirement
can be stated as the follow_ing important design guideline:

Design Guideline Design by Contract


Each method in a class must honor the contract of the respective method in its
superclass and/or the interfaces implemented by the class. Specifically:
■ The precondition of each method in a subclass must be no stronger than the
precondition of the respective method in its superclass and/or the interfaces
implemented by the class. In other words, the method in the subclass cannot
be more restrictive than the respective methods in its superclass.
■ The postcondition of each method in a subclass must be no weaker than the
postcondition of the respective method in its superclass and/or the interfaces
implemented by the class. In other words, the methods in the subclass cannot
do less than the respective methods in its superclass .

. ----
6.3 THE CANONICAL FORM OF CLASSES

The canonical form of public classes ensures that instance of those clas e wi!J
be well behaved when they are manipulated by the Java run-time environment and
many other classes, such as those classes in the collection framework (see Section 8.2
[p. 308]).

Design Guideline Canonical Form of Public Classes


Classes designed for general use should follow the ca11011ical fonn of public cla e •
which consists of the following element :
No-arg constructor: Providing a public no-arg con tructor.
Object equality: Overriding the equals() and hashCode O method .
String representation: Overriding the toStringO method.
Cloning: Implementing the Cloneable interface, and overriding the clone 0
method.
Serialization: Implementing the j ava. io . Serializable interface and over-
riding the read0bj ect () ,rnd wri te0bj ect O method , when the in-
stances of the clas · may need to be aved i.11 file or tran ferred over the
network.
228 • From Building Blocks to Projects

The relevant methods involved in the canonical form are discussed in the follow-
ing ub eclions.

6.3.1 No-Argument Constructor

The no-arg constructor of a class is useful because it allows instances of the clas
to be created dynamically by the Java virtual machine at run time. As a result, the
objects are not created by u ing the new operator directly or indirectly anywhere in
the program. The program that u e the object may not have any reference to the cla s
or any knowledge of the existence of the class. The class will be made available to the
program at run time, and the objects will be created by the Java virtual machine. Many
important Java applications and frameworks depend on the ability to load classe and
create instances al run time. Therefore, to make your class as widely reu able a
possible, it should provide a no-arg constructor.
The dynamic loading mechanism is one of the more useful feature of Java.
However, a full discussion of the mechanism is beyond the scope of this book.

6.3.2 Object Equality

The equals() method defines the equality of object states on a per-class basis. The
default implementation of the equals() method in the Object class tests the identity
of objects; that is, o1. equals (o2) if and only if both o1 and o2 refer to the same
object. Most classes should override this method and define the notion of equality
based on the contents, i.e., the states, of the objects, not their identities.
The contract of the equals () method is that all implementation must satisfy the
following conditions:

■ Reflexivity: For any object x , x. equals (x) is always true .


■ Symmetry: For any objects x and y , x. equals (y) is true if and only if
y. equals (x) is true.
■ Transitivity: For any objects x, y , and z , if both x. equals (y) and y. equals (z )
are true, then x . equals (z) should also be true.
• Consistency: For any objects x and y, x. equals (y) should consistently return
true or consistently return false , if the stales of x and y are unchanged.
■ Nonnullity: For any object x , x. equals (null) should always be false .

The following is a template of a typical implementation of the equals () m ethod


of a class C:

publ ic boolean equals(Object other) {


if (thi s== other)
return true ;
if (other instanceof C) {
C otherObj = (C) other;
(compare each field, and return false if not equal)
6.3 The Canonical Fonn of Classes • 229

return true;
}
return false;

I
}

The comparison of the fields can be done as folJows:

■ For a field p of primitive type:

if (p != otherObj.p)
return false ;

■ For a field r of reference type:

if (r ==null? otherObj.r != null !r.equals (otherObj .r ))


return false;

The values of some fields may be temporary, derived from other fields. or
nonessential. These fields are called insignificant fields. They can be excluded from
the comparison for equality.
The following is an implementation of the equals () method of the LinkedLi st
class. Two lists are considered equal if they are of the same length and the element
at the same position of the two lists are pairwise equal. ote that the list referenced
by parameter other does not need to be a linked list.

public boolean equals (Object other) {


if (this== other)
return true ;
if (other instanceof List ) {
List otherList = (List ) other;
if (this.getCount () == otherList.getCount ()) {
for (inti= O; i < this.getCount (); i++) {
Object thisElement = this.elementAt (i );
Object otherElement = otherList.elementAt (i );
if (thisElement == null) {
if (otherElement != null) {
return false;
}
} else {
if ( !thisElement .equals (otherElement )) {
return false ;
}
}
}
return true ;
}
}
return false ;
}
230 ■ From Building Blocks to Projects

6.3.3 Hash Code of Objects


The hashCode () method is used by collection classes that implement hash tables
such as HashMap and HashSet (see Section 8.2 [p. 308]). Overriding the equals ()
method requires overriding the hashCode () method also.
The contract of the hashCode () method is that if two objects are equal according
to the equals() method, they mu t return the ame hashcode; i.e., for any objects
x and y, x . equals (y) must imply x. hashCode () == y. hashCode O. It is not
required for two objects that are not equal according to the equals () method to
return different hash code . However, returning different hash codes for object that
are not equal usually improves the perfonnance of hash tables.
In general, the hash code can be computed as follows:
1. Computing a hash code for each significant field . The significant field are the
fields that are compared in the equals() method. For a field of primitive type,
the hash code can be computed by converting the value to an integer. For a field
of reference type, the hash code can be computed by calling the hashCode ()
method on the field, if it is nonnull.
2. Combining the hash codes of all significant fields. The key is to include all the
hash codes while computing the final hash code.
The following is a template of a typical implementation of the hashCode ()
method of a class C.
public boolean hashCode () {
int hash = o; // the accumulative hash code
i nt c; // the hash code for a field
(for each field compute and combine the hash code)
return hash ;
}

The following are a few ways to combine the hash codes of individual fields:
1. Bitwise-or

hash = hash<< n I c

where n is an arbitary integer constant, for example, 8.


2. Addition

hash= hash* p + c

where p is a prime number, for example, 37.

. The followi?g is an implementation of the hashcode () method for the Linked-


List class that 1 consistent with the equals () method.
public int hashCode() {
int sum .. O;
inti= O;
Node node= head;
6.3 The Canonical Fonn of Classes ■ 231

while (i < 4 && node != null) {


if (node.element != null) {
sum «= 8;
sum I= node.element.hashCode() & OxFF;
}
node= node .next;
}
return sum;
}

6.3.4 Cloning Objects

The clone O method returns a clone of the object it elf. It is analogous to the copy
constructor in C++. The contract of the clone() method i the following:
■ The cloned object must not be the same object as the original; i.e., o . clone()
!= 0.

■ The cloned object and the original object are instance of the same class.
■ The cloned object must be equal to the original object; i.e., o. clone()
. equals (o) .

The Object class provides a default implementation of the clone () method.


The clone() method in the Object class is protected. The default implementation
of the clone() method in the Object class behaves as follow :

■ For a class that implements the marker interface Cloneable, it create a shallow
copy of the original object.
■ For a class that does not implement the marker interface Cloneable. it throw
a CloneNotSupported.Exception.

A shallow copy means that the value of each field of the original object i copied to
the corresponding field of the cloned object, whether the field i of primitive rype or
reference type. Shallow copying is adequate for field of primitive type . For field of
reference types, only the references are copied, and the actual object referenced by
the fields are not cloned. A deep copy mean that the objects referenced by the field
of reference type are al o cloned. The difference between hallo, and deep copie
is illustrated in Figure 6.4 for the LinkedList example.
For a class to support cloning, it i nece ary to implement the Cloneable
interface, oven-ide the clone () method, and declare it public. The folio, ing is
another version of the Point clas that upport cloning. ince all the field of the
Point clas are of primitive type . the default hallow cop implementation in the
Object class is sufficient.

public class Point implements Cloneable {


public Object clone () throws CloneNotSupportedException {
return super.clone();
}
232 • From Building Blocks to Projects

II . ..
}
II client code
Point pl= new Point();
Point p2 = (Point) pl . clone();

The following program fragment is an implementation of the clone () method


of the LinkedList class that creates a deep copy of the list:
public class LinkedList implements List, Cloneable {
II . ..
public Object clone()
throws CloneNotSupportedException {
LinkedList list= (LinkedList) super.clone();
list .head= list .tail= null ;
list.count= O;
for (Node node= head; node != null ; node= node .next) {
if (node.item!= null) {
list.insertLast(node.item);
}
}
return list;
}
}

An important rule in implementing the clone () method is that the cloned object
should not be created using the new operator, unless it is a final class. This rule exists
because of the requirement that the cloned object and the original object are instances
of the same class. If the new operator were used to create the cloned objects, and if
there were a subclass that did override the clone() method, then a clone of an object

Figure 6.4
list1 listl
Copies in cloning: head: head:
(a) shallow copy;
(b) dttp copy. tail: tail:

listl. clone()
head: head: 1
tail: tail:
count: 3

3
(a) {b)
6.3 The Canonical Fonn of Classes • 233

of that subclass would be an instance of its superclass, since the inherited clone()
method was used. The right way to create the cloned objects of a nonfinal class C is

public Object clone()


throws CloneNotSupportedException {
C clonedObject = (C) super . clone();
(Clone reference type fields. if a deep copy is desired)
return clonedObject;
}

Using Clones in Assert.ions


Using the clone O method, we can assert postconditions involving objects in
the prestate. The following is another version of the insert () method of the
LinkedList class using the clone O method to retain a copy of an object in the
prestate.

I**
* Inserts a new element into the list at the i-th position.
*
* Opre item != null && i >= 0 && i <= size ()
* Opost size() == size()Opre + 1
* Opost Oforall k : (0 . . size() - 1) 0
* (k < i ••> element(k)Opre •• element(k)) ll
* (k •• i ••> itemOpre •• element(k)) ll
* (k > i ••> element(k - 1)0pre •• element(k)
•I
public void insert(Object item, inti) {
assert item != null && i >= 0 && i <= size () ;
assert _wellformed();
int size_pre = size ( );
LinkedList this_pre • null;
try {
this_pre • (LinkedList) clone();
} catch (CloneNotSupportedException e) {}
if (i <= 0) {
insertHead(item);
} else if (i >= count) {
insertLast(item);
} else {
// i > O && i < count;
Node n = head;
for (int j = O; n != null && j < i - 1; j ++) {
n = n .next;
}
Node node= new Node () ;
node . item = item ;
node.next= n .next ;
node.prev = n;
234 • From Building Blocks to Projects

node.next.prev = node;
n.next = node;
count++;
}
int size_post =size();
assert size_post == size_pre + 1;
boolean insertOK • true;
for (int k • O; insertOK && k < size(); k++) {
if (k < i) {
insertOK • (tbis_pre.element(k) •• element(k)) ;
} else if (k •• i) {
insertOK • (item•• element(k)) ;
} else {
insertOK • (tbis _pre .element(k - 1) •• element(k));
}
}
assert insertOK;
assert _wellformed();
}

6.3.5 String Representation of Objects

We have previously discussed the toString () method (see Section 4.4.8 [p. 122]).
It returns a string representation of an object. The toString () method is very useful
in many situations, especially for testing and debugging programs. The result of the
toString () method should include all the fields of the object.
The following code fragment implements the toStringO method of the
LinkedList class:
public String toString() {
StringBuffer s = new StringBuffer();
inti= O;
for (Node node= head; node != null; node= node.next, i++) {
s.append ("[" + i +"]="+node.element+ "\n");
}
return s.toString();
}

6.3.6 Serialization

Serialization is the process of transforming an object into a stream of byte . Dese-


ria/ization is the reverse process. Serialization allows objects to be easily saved to
files or sent lo remote hosts over a network. Objects of classes that implement the
j ava. io. Seralizable interface can be serialized and deserialized. Cla se whose
in lances need to be stored in files or sent to remote hosts should implement the
java. io . Seralizable interface. A default implementation for serialization and
de erialization is provided for all classes that implement the j ava . io . Seralizable
interface. Often, this is all that needs to be done lo support serialization. A cla s may
6.4 Unit Testing • 235

customize the way its instances are serialized by implementing the methods read-
Object O and wri teObj ect O. Serialization is discussed further in Section 8.4. l
[p. 367].

6A UNIT TESTING

The testing of software systems is usually divided into several phases:


Unit testing: To test each unit, or component, independently, before the unit are
integrated into the whole system.
Integration and system testing: To integrate all the components of a system and
to test the system as a whole.
Acceptance testing: To validate that the system functions and perfonn a
expected by the customers or the u ers.
~Unit testing is important because (1) for the whole system to function properly,
each of its components, or units, must function properly individually; and (2) ince
units are much smaller than the whole system, it is much easier to find errors in each
unit individualJy. Thorough unit testing will significantly reduce the effort and co t
required for integration and system testing . .En this book we focus on unit testing only,
since unit testing is usually the responsibility of the developer(s) of the unit, while
integration and system testing and acceptance testing are usually the responsibility of
a separate testing or quality assurance team.
In object-oriented software development, the unit for unit te ting i often a ingle
public class along with its helper classes, if any. Each cla hould include method
that facilitate unit testing. Some simple unit tests can be carried out in the main 0
method of the class. More extensive and complete tests are u ually written in separate
classes for testing purposes only.

6.4.1 Simple Unit Testing

The following is a simple unit test of the LinkedList class:


package test;
import mylist.•;
publ ic class LinkedListTestl {
public static void main(String args[) ) {
LinkedList 1 = new LinkedList ();
l.insertHead(new I nteger(l));
l . insertHead (new Integer(2)) ;
l . insertLast(new Integer (3));
l . insertLast(new Integer(4)) ;
l . insert(new Integer (S) , 3) ;
236 • From Building Blocks to Projects

l.insert(new Integer (6), 3);


l .insert(new Integer (7), 3);
System . out.println("First pass");
System.out.println(l);
l.removeHead();
l.removeLasr();
l .removeElementAt( 2);
System.out . println("Second pass") ;
System . out .println(l);
LinkedList 12 = (LinkedList) l.clone();
System.out.println("Cloned list ");
System . out .println (l2) ;
12. removeHead ();
System.out.println("Original list" );
System.out . println(l);
System . out .println( "Cloned list");
System .out .println (l2);
}
}

The test invokes most of the methods of the Linked.List class. However, the test
is rather ad hoc. It only prints out the results without any indication of the correctness
of the test results. It requires a visual inspection of the results by a human tester
to determine the correctness of the results. A complete test of nontrivial classes i
usually much more extensive and often needs to be repeated many times. Depending
on human visual inspection to determine the correctness of the results i extemely
time-consuming, unreliable, and often impractical.
The testing results can be automatically verified in the testing program. The
following is another vision of the program testing the implementation of LinkedLi st
clas . In this version, the expected results after each testing step are included in the
test program, as a two-dimentional integer array results . The actual results and the
expected results are compared, and any discrepencies are reported.

package test ;
import mylist.•;
public class LinkedListTest2 {
protected static i nt [)[) results = {
{ 2, 1, 3, 7, 6, 5, 4 }, II result after insertion
{ 1, 3, 6, 5 }, II result after removal
{ 1, 3, 6, 5 }, II the cloned list
{ 1, 3, 6, 5 }, II the original list after removing the
head from the cloned list
{3,6,5}, II the cloned list after removing t he
head from the cloned list
};
--
6.4 Unit Testing ■ 23 7

public static void main(String args[J )


throws CloneNotSupported.Exception {
boolean testPassed = true ;

LinkedList 1 = new LinkedList( ) ;


l . insertHead(new Integer(!)) ;
l.insertHead(new Integer(2)) ;
l . insertLast(new Integer (3));
l.insertLast(new Integer (4));
l.insert(new Integer(5), 3);
l . insert(new Integer(6), 3) ;
l.insert(new Integer(7), 3);

System.out . println("First pass") ;


System . out . println(l);
if (!TestUtil.match(l, TestUtil.tolntegerArray(resul ts [O) ))) {
System . out . println ("Result mismatch") ;
testPassed = false;
}

1. removeHead () ;
l.removeLast();
l . remove(2);

System.out . println ( "Second pass" ) ;


System . out . println (l);
if (!TestUtil.match(l, TestUtil.tol ntegerArray( results [1) ))) {
System .out . println("Result mismatch");
testPassed = false;
}

LinkedList 12 = (LinkedList) l.clone( );


System . out .println( "Cloned list" );
System . out .println(l2) ;
if (!TestUtil.match(l2, TestUtil . tolntegerArray (results [2)))) {
System.out . println ( "Result mismatch");
testPassed = false ;
}

12 . removeHead();
System.out.println ( "Original list" );
System.out . println (l);
if (!TestUtil . match(l, TestUtil . tointegerArray(results[3)) )) {
System.out.println("Result mismatch" );
testPassed = false ;
}
System.out.println("Cloned list" );
System . out.println(l2) ;
if (!TestUtil .match (l2, TestUtil.tointegerArray(results[4]))) {
System .out . println ( "Result mismatch");
testPassed = false;
}
238 ■ From Building Blocks to Projects

if (testPassed) {
System. out. println ( "Test passed. ") ;
} else {
System . out .println("Test failed .");
}

The TestUtil class provides two auxiliary methods that are used to compare
the lists against the expected results:

■ The tolntegerArray () method converts an array of integer values to an array


of integer objects,
■ The match() method tests whether the contents of a list match the contents of
an array of objects.
package test;
import mylist.•;
public class TestUtil {

public static boolean match(List list, Object[] array) {


boolean result= false;
if (list I= null kk
array != null) {
int n = list . size();
i:f (n = array.length) {
for (inti= O; i < n; i++) {
Object item= list.element(i);
if (item != null) {
if (!item . equals(array[i])) {
return false;
}
} else {
if (array[i] != null) {
return false ;
}
}
}
result "' true;
}
} else if (list •• null kk
array zz null) {
result• true;
}
return result;
}

publi c static Object(] tointegerArray(int(] intArray) {


if (intArray !• null) {
int n • intArray . length;
6.4 Unit Testing ■ 239

Object[] resultArray = new Object[n];


for (inti= O; i < n; i++) {
resultArray[i] = new Integer(intArray[i]);
}
return resultArray;
} else {
return null;
}
}

This test program compares the lists to their expected results after each testing
step. It also prints out a simple message at the end indicating whether all the test
results are correct.

6.4.2 JUnit-A Unit-Testing Tool

When dealing with a large and extensive set of test cases, it is usually more convenient
to use a unit-testing tool. JUnit 4 is a flexible, easy-to-use, and open-source unit-testing
tool for Java programs. The following is a template of typical JUnit test programs:

public class MyTest extends TestCase {


public MyTest (String name) {
super(name);
}
public void testCase_l () {
(test and compare results)
}

public void testCase_n () {


(test and compare results)
}
public static Test suite() {
return new Test Suite (My Test. class);
}
}

Each test program, that is, test suite, is written as a subclass of the TestCase
class. The constructor with the test suite name is required. Each test case i written as
a separate test method. The names of test methods must begin with the prefix test.
The suite () method in the test program is required. It aggregates all the test cases
in the class into a test suite.
The Linked.List Uni tTest class that follows i a simple test program using
JUnit to test the implementation of the Linked.List clas . It consists of three
test cases as the following three method of the test cla s: testinsert () ,

4. JUni t is available al www . juni t . org. Documentation and other infom1ation about JUnit can be found
at the site.
240 ■ From Building Blocks to Projects

testRemove () , and testClone O. The assert True O method in the test cases
as erts that the boolean expression argument must be true, otherwise an error is
recorded.
package unittest ;

import junit . framework . •;


import myli st.•;
import test. TestUtil ;
publi c class LinkedListUnitTest extends TestCase {
public LinkedListUnitTest(String name) {
super(name) ;
}

publi c void testlnsert () {


LinkedList 1 = new Li nkedList ();
l . insertHead (new Integer ( l )) ;
l . insertHead(new Integer(2)) ;
l . insertLast (new Integer (3) ) ;
l . insertLast(nev Integer (4 ));
l .insert(nev Integer(5), 3) ;
l. i nsert(new Integer(6), 3);
l . insert(nev Integer (7) , 3) ;

assertTrue(TestUti l. match(l,
TestUti l.tolntegerArray(new int[] {2, 1, 3 , 7, 6, 5 , 4 } )));
}

publi c void testRemove () {


Li nkedList 1 • nev LinkedList();
f or (int i= 1; i <= 7 ; i++) {
l .insertLast (nev Integer (i )) ;
}
l.removeHead() ;
1. r emove Last() ;
l . r emove (2);

assertTrue (TestUtil .match (l ,


TestUtil. tolntegerAr ray (new i nt[) {2, 3, 5 , 6})));
}

public void t est Cl one() throws CloneNotSupportedException {


Linkedlist 11 • new Linkedlist();
for (int i • 1; i <• 7 ; i ++) {
11 . i nsertLast (new Integer(i));
}

int [) ial • {1, 2 , 3, 4 , 5, 6, 7} ;


i nt[) ia2 • {2 , 3 , 4 , 6 , 6, 7};

LinkedList 12 • (LinkedList) 11 .cl one();


assertTrue( "Clone is not identity", 12 ! =- 11);
assertTrue ("Clone equal s to original" , 11. equals (12)) .
assertTrue ( "Match 1" , TestUtil . match (11, TestUt il . t oI~tegerA (i 1))) .
asser tTrue (" Mateh. 2" , TestUtil . match (12 , TestUt 1· 1 . t o1ntegerArrray a '
r ay ( i. a 1)));
6.4 Unit Testing • 241

12.removeHead();
assertTrue ( "Match 3"' TestUtil. match(l1, TestUtil. tolntegerArray(ial) )) ;
assertTrue("Match 4", TestUtil .match(l2 , Testutil . tolntegerArray(ia2))) ;
assertTrue( "Not equal", !11.equals(l2)) ;
}

public static Test suite() {


return new TestSuite(LinkedListUnitTest . class);
}
}

To compile and run the test program, JUnit must be installed and the libr fil
j unit.jar must be included in the CLASSPATH. The following command co:ile:
the JUnit test class:5
venus¾ j avac -classpath . :]Unit home/ juni t . jar -source 1. 4
unittest/LinkedListUnitTest.java

where ]Unit home is the installation directory of JUnit on your computer. JUnit
provides test runners to execute the test cases. The text-based JUnit test runner can
be invoked as follows:5
venus¾ java -classpath . :JUnithome/junit.jar -ea junit . textui.TestRunner
unittest.Lin.kedListUnitTest

The GUI-based JUnit test runner can be invoked as follows:5


venus¾ java -classpath . :]Unit home/junit . jar -ea junit. swingui. TestRunner
unittest . LinkedListUnitTest

The result of running the GUI-based JU nit test runner is shown in Figure 6.5.

6.4.3 Testing Coverage Criteria

A thorough and effective unit test of a class must


1. systematically test all aspects of the implementation based on certain establi hed
criteria; and
2. automatically check the correctne of the te t re ults.
There are two approaches to deriving te t cases and mea uring the completeness
of a set of test cases: the black-box and the white-box approache . The black-box
testing approach derives test case based on the pecification of a component alone,
without any consideration of the implementation of the component. The white-box
testing approach derives test case ba ed on the tructure of the code implementing
the component. The black-box test can be derived independently from the implemen-
tation of the component, and even before the implementation tarts. A a key practice
in Extreme Progranuning, it i strongly recommended that the black-box tests of a
component be written before the implementation of the component tarts.

5. The command should be typed on one line.


;._

242 • From Building Blocks to Projects

Figure 6.5 a

JUnlt
GUI-based JUnit
test runner. Test dUS name:
t.:lu:.:.n:..it_t,_s_t._L_ln_k_e_d_L_ls_tU_nl_tT
~;e;-s_t_ -=========---'I~ Q I. .___. .
@ RelHd duses every run

Runs: Errors: Fi.llures:


u
3/3 0 0

unittest. LinkedlistUnitT est Run


testlnsert
testRemo'll'E!
testClone

...


rinished: 0.207 seconds Exit

One of the most important methods of black-box testing is equivalence parti-


tioning of input space. In this appraoch, the input space of a program i partitioned
into a number of equivalence partitions. The input space should include both valid
and invalid input data. Elements in the same equivalence partition are the input data
that hould be processed by the program in an equivalent way. Therefore, we can rea-
sonably deduce the behavior of the program on all the input data in an equivalence
partition by testing a single representative from the equivalence partition. Using thi
approach, a set of test cases is considered adeq uate if it tests at least one repre entative
from each equivalence partition.
The white-box testing approach defines a number of coverage criteria of unit
testing. Given a set of test case of a unit, the completeness of the test cases can be
measured by the following different levels of coverage they satisfy :

Statement coverage: Every statement in the program mu st be exec uted at least


once.
Branch coverage: Every branch of the program must be executed at least once.
6.5 Project Build • 243

Condition coverage: Every boolean condition in the control statements of the


program must be evaluated to true and false at least once, respectively.
Combination condition coverage: For every compound boolean condition in the
control statements of the program, every combination of the truth value of
the individual boolean conditions must be exercised.
The statement coverage represents the minimum unit-testing criterion, and the
combination condition coverage represents the most thorough unit-testing criterion.
A thorough unit testing should include test cases systematically derived using
both white-box and black-box approaches.

6.5 PROJECT BUILD

The term proj ect build refers to both the process and the result of producing an
executable deliverable of the project. In an iterative development proce , delivering a
new project build is a key element in every iteration, starting from the very beginning.
Each build process often involves many repetitive tasks, such as compiling, te ting.
generating documentation, and packaging source or binary file , and so forth . A large-
scale software system usually consists of numerou subsy tern and component .
Each of the subsystems and components couJd be built separately. Thu , the number
of repetitive tasks involved in a build could multiply. It is al o quite common that
many of the tasks are interdependent. We say task T2 depend on task T 1, if ta k T1
mu t be completed before task T2 can be carried out.
A build tool offers assistance in managing the build proces in the following
ways:
1. Allowing repetitive task to be carried out with a little effort a po ible.
2. Automatically detennining and enforcing the dependencie an1ong the ta k
while carrying out the task .

6.5.1 Ant-A Build Tool

Ant 6 i a powerful, easy-to-use, open- ource build tool for Java project . The proce
of project building is pecified in a b11ildfile in ML yntax. By default. the build file
is named build. xml . The following i a template of typical build file :

<project name• "projecr-,wme" def ault •"defaulr-rarger-111.1111e" >


. . . proper()' c/efi11irio11s .. .
. . . target defi11iriv11s .. .
</ project>

6. Ant uvailablc al j a.kart a . apache . org. Documentation and other infonnation about Ant can be found
al the site.
244 • From Building Blocks to Projects

Each build file defines the process for building a single project. The main body of
the build file consists of a set of property definitions and a set of target definitions. A
property is simply a name-value pair. The property name must be unique. A property
definition associates a value to a property name.
<property name•"property-name "value•"property-value" />

The value of a defined property can be referenced in the rest of the build file as
${property-name}.
A target is a step in the build process, which consists of a list of tasks that should
be perfonned together. Each target has a name and may also have a dependency list,
which is a comma-separated list of the names of other targets that this target depends
on. Target ft depends on another target t 2 if target t2 must be successfully completed
before target ft can be executed. A typical task definition is in the following form:
<target name•"target-name" depends•"dependent 1, ••• , dependent,,">
. . . task definitions . . .
</target>

Ant supports an extensive set of built-in tasks for building projects. Each task has
a name and various parameters. Commonly used tasks include the following :

Task Name Description

j avac Compile Java source code


j ava Execute Java byte-code
j avadoc Create documentation pages
copy Copy files
move Move files
mkdir Create a directory
exec Run an operating system command or a program
mail Send an e-mail
jar Package and compress files in JAR format
zip Package and compress files in ZIP format

Consult the Ant documentation for the details of using these tasks.
Th_e follo-:ving is an example of Ant build files for performing various tasks related
to the linked list classes and the test suites.
<project name•"LinkedList" default="dist">
<!-- set global properties for this build-->
<property name• "src" value="."/>
<property name .. "classes" value•"classes"/>
6.5 Project Build ■ 245

<property name="test " value="test"/>


<property narne="dist " value="dist" / >
<property name="junit" value="/home/jia/JUnit/junit3.7"/>
<target name="init">
<!-- Create the directory structure used by compile-->
<mkdir dir="${classes}"/>
</target>
<target name="compile" depends="init">
<!-- Compile the java code from ${src} into ${classes}-->
<javac srcdir="${src}"
destdir="${classes}"
includes="mylist/•"
source="1.4"/>
</target>
<target narne="dist" depends="compile">
<!-- Create the distribution directory-->
<mkdir dir="${dist}/ lib"/>
<!-- Pack the classes into the LinkedList . jar file-->
<jar jarfile="${dist}/ lib/LinkedList . jar"
basedir="${classes}"
includes="mylist/•" />
</target>
<target name="clean">
<!-- Delete the ${classes} and ${dist} directory trees -->
<delete dir="${build}" />
<delete dir="${dist}" />
</target>

</project>

This example contains a number of typical targets in build files:

Target Name Description

ini t Prepare for the build


compile Compile all ource code
dist Package the program for di tribution
clean Delete all none ential, generated file

To perfom1 a build using Ant, you invoke Ant with the de ired build target name
as the argument a follows:
venus¼ ant target-name

For more detail on using Ant, con ult the Ant documentation.
246 ■ From Building Blocks to Projects

CHAPTER SUMMARY

■ The members of classes should be ordered to maximize the readability.


■ The j avadoc tool i very useful for generating documentation of cla es and
packages. For each public class or interface, a doc comment documenting the
cla or interface should be provided, and a doc comment should be provided for
each public method or constructor.
■ A contract of a method i a specification of the behavior of the method. The
contract of a method can be specified using preconditions and postconditions of
the method. A precondition of a method is a boolean expression that must hold
when the method is invoked. A postcondition of a method is a boolean expression
that holds when the method invocation returns.
■ An invariant of a class is a formally specified condition that always hold on any
object of the class whenever it is not manipulated by any methods. lnvariants of
a class are usually concerned with the soundness of the implementation of the
class.
■ An assertion is a boolean condition at a given location of a program that should
be true whenever the flow of execution reaches that location. Java supports an
assertions statement, which checks assertions at run time.
■ The canonical form of public classes ensures that instances of those classes will
be well behaved when they are manipulated by the Java run-time environment
and many other classes, such as those classes in the collection framework. The
canonical form of public classes consists of the following elements:
No-arg constructor
Object equality
String representation
Cloning
Serialization
• There should be no nonfinal public fields, except when a class is final and the
field is unconstrained.
• The set of public methods defined in the class sho1,1ld provide full and convenient
acce s to the functionality of the class.
• When the functionality supported by a class can be implemented in different
ways, it i advisable to separate the interface from the implementation .
• Each method in a class must honor the contract of the respective method in it
superclass and/or the interfaces implemented by the cJass.
• Unit testing is carried out in order to test each unit, or component, independently,
before the units are integrated into the whole system.
• The term "project build" refers to both the process and the result of producing an
executable deliverable of the project.
Exercises • 24 7

FURTHER READINGS

Binder, R. V. (1999). Testing Object-Oriented Systems: Models, Patterns, and Tools.


Addison-Wesley.
Burke, E. M., and J. E. Tilly (2002). Ant: The Definitive Guide. O'Reilly.
Meyer, B. (2000). Object-Oriented Software Construction , 2nd ed., Prentice Hall.
Sykes, D. A., and J. D. McGregor (2001 ). Practical Guide to Testing Object-Oriented
Software. Addison-Wesley.

EXERCISES
6.1 Design and implement the classes di cussed • size: returns the number of objects currently
in the case study of Chapter 2: Customer and in the queue.
Address . You should follow the principles • first: returns the object at the front of the
discussed in Section 6. 1 and adopt the canonical queue without removing it from the queue.
form discussed in Section 6.3. • last: returns the object at the end of the
6.2 Make the LineSegment, Rectangle, and queue without removing it from the queue.
Circle classes in Exercise 5.1 conform to Formally specify the contract of each method.
the canonical form discussed in Section 6.3. 6.5 Design and implement a class that implement
6.3 Define an interface for a stack, which supports the Stack interface defined in Exerci e 6.3
the following methods: using an array.
• push: adds a new object onto the top of the (a) Formally pecify the invariant of the cla s.
stack. (b) U e as ertions to en ure that the invariant
• pop: removes the object at the top of the and contract hold at run time.
stack and returns the object. (c) Design and implement test ca e for unit
• size: returns the number of objects currently testing u ing JUnit.
in the stack.
• top: returns the object at the top of the stack 6.6 De ign and implement a clas that implement
without removing it from the stack. the Queue interface defined in Exerci e 6.4
using an array.
Formally pecify the contract of each method.
(a) Formally pecify the invariant of the clas..
6.4 Define an interface for a queue, which upport
(b) U e assertion to en ure that the invariant
the following methods:
and contract hold at run time.
• e11q11e11e: adds a new object at the end of the
(c) De ign and implement te t ca e. for unit
queue.
te ting using JUnit.
• deq11e11e : removes the object at the front of
the queue and returns the object.
Design by Abstraction

CHAPTER OVERVIEW
In this chapter, we introduce design patterns. The main focus of this chapter is designing
reusable and extensible components, using abstract classes, interfaces, and design
patterns. We d iscuss several important design patterns for abstraction: the Template
Method, Strategy, Factory, and Iterator. We also present a case study on the animation of
sorting algorithms.

One of the promises of object-oriented oftware development i reu ability. Although


reuse of implementations through inheritance i relatively ea y, reuse on a larger cale
(i.e., reuse of sy tern design and architecture ) has proven rather difficult. Among the
most important recent development of object-oriented technologie i the emergence
of design pattems andframeworks, which are intended to addre the reu e of oftware
design and architecture .

7.1 DESIGN PATTERNS

The concept of p<11tems wa · originally arti ulated b Chri. topht:r Alexander and his
colleague to describe architectural de ·ign . In ·earching for the e sence of great
buildings, great town., und beautiful place , Alexander propo ed in The Timeless Way

249
250 ■ O~ign by Abstraction

ofBuilding [Alexander 1979] and A Pattern Language- Towns, Buildings, Construc-


tion [Alexander et al. 1977] that the timeless way of building, which " is thousands of
years old, and the same today as it has always been," can be captured in 253 patterns.
He went on to ay:

Each pattern describes a problem which occurs over and over again in our environ-
ment, and then describes the core of the solution to that problem, in such a way that
you can u e this solution a million time over, without ever doing it the same way
twice. [Alexander et al. 1977]

In other words, each pattern represents a generic (i.e., reusable) olution to a


recurring problem. Only a relatively small number of patterns are needed to capture
the es ence of all architectural designs, and they can be adapted and combined in
many different ways to generate endless possibilities.
In some aspects, software design resembles architectural design. Similarities
between the two include the following:

■ Both are creative processes that unfold within a large design space, which com-
prises all possible designs.
■ The resulting design must satisfy the customer's needs.
■ The resulting design must be feasible to engineer.
■ The designers must balance many competing constraints and requirements.
■ The designers must seek certain intrinsic yet unquantifiable qualities, such as
elegance and extensibility.

Therefore, it is natural to adapt the concept of architectural patterns to software


design. Software design patterns are schematic descriptions of solutions to recurring
problems in software design. The main purposes of using software design patterns
are (1 ) to capture and document the experience acquired in software de ign in a
relatively small number of design patterns to help designers acquire design expertise;
(2) to support reuse in design and boost confidence in software systems that use
e tablished design patterns that have been proven effective; and (3) to provide a
common vocabulary for software designers to communicate about software design.
The pioneering work in software design patterns was done by Gamma et al. , who
published the first software design patterns catalog, Design Patterns [Gamma et al.
1995). It includes 23 of the most commonly used general-purpose design patterns
that are application domain independent. These design patterns are classified into
three categories:

1. Creatio11al patterns, which deal with the process of object creation.


2. Structural patterns, which deal primarily with the static composition and struc-
ture of classes and objects.
3. Behavioral pa/ferns, which deal primarily with dynamic interaction among
classes and objects. I
--
7.1 Design Patterns ■ 251

The Design Patterns catalog also popularized a common style for describing
design patterns. The description of each design pattern consists of some or all of the
following sections:

Pattern name: The essence of the pattern.


Category: Creational, structural, or behavioral.
Intent: A short description of the design issue or problem addressed.
Also known as: Other well-known names of the pattern.
Applicability: Situations in which the pattern can be applied.
Structure: A class or object diagram that depicts the participants of the pattern
and the relationships among them.
Participants: A list of classes and/or objects participating in the pattern.

The singleton class discussed in Chapter4 [p. 114] is one of the creational pattern
in the Design Patterns catalog. We use the Singleton pattern to illustrate the format
for describing design patterns.

7 .1 .1 Design Pattern: Singleton

Design Pattern Singleton

Category: Creational design pattern.


Intent: En ure that a class has only one instance and provide a global point of
access to it.
Applicability: Use the Singleton pattern when there must be exactly one in tance
of a class and it must be accessible to clients from a well-known acces
point.

The structure of the Singleton pattern i hown in the following diagram.1

Singleton
'\
static getlnstance() O······· return thelnstance
operation()
getData()

static Singleton thelnstance


data

I. The rcc1angular box with a ·haded comer <lcno1e · t1 nole.


252 ■ Design by Abstraction

There is only one participant in the Singleton pattern:

■ Singleton
declares the unique instance of the class as a static variable, and
defines a static method get Instance O for clients to access the unique
instance.
The following program segment is an implementation of the Singleton pattern in
Java.
public class Singleton {
public static Singleton getlnstance () {
return thelnstance;
}

private Singleton() {
(initialize instance fields)
}

(other fields and methods)


private static Singleton thelnstance = new Singleton() ;
}

In this book, we focus on domain-neutral design patterns. However, the concept


of patterns can also be applied to other aspects of software development, such as
patterns for software architecture [Shaw 1996] and object-oriented analysis [Fowler
1997], and to specific domains of software systems, such as patterns for concurrent
programming in Java [Lea 2000]. Patterns concerning implementation techniques in
a specific programming language are also known as idioms. Bloch [2001] discusses
the use of a variety of idioms in Java.

7.1 DESIGNING GENERIC COMPONENTS

By generic components, we mean program components, usually in the form of classes


or packages, that can be extended, adapted, and reused in many different contexts
without having to modify the source code. Generic components are also known as
reusable components. In this section we discuss two basic techniques of designing
generic components: refactoring and generalizing. The mechanisms used to build
generic components are inheritance and delegation. Abstract classes and interfaces
play important roles in implementing generic components.

7.2.1 Refactoring

One way to discov~r po~sible generic components is by identifying recurring code


segments that are 1den~1cal ~r nearly identical. Examples of such recurring code
~egments ~e th~ nearly 1dentJcal methods start() , stop (), and run () that appear
rn every annnatJon applet.
7.2 Designing Generic Components • 253

Refactoring consists of the following tasks:

• Identifying code segments in a program that implement the same logic, often in
the same exact code, in many different places.
• Capturing this logic in a generic component that is defined once.
• Restructuring the program so that every occurrence of the code segment is
replaced with a reference to the generic component.

Refactoring thus eliminates duplicate code segments, which are hazardous for main-
tenance. It ensures that a bug fix or logic enhancement need be implemented only
once-in the generic component. Without refactoring, every change to a recurring
code segment must be repeated everywhere the code segment occurs, some instances
of which may be overlooked or forgotten. Recurring code segments can easily " drift
apart," making them harder and harder to maintain.
Refactoring must be applied only to code that actually duplicates underlying
logic, however. Not all code that looks alike is alike.

Design Guideline Refactoring Recurring Code Segments


Recurring code segments based on the same logic are hazardous to maintenance. They
should be refactored so that the code segment occurs only once. Other occurrences
of the code segment should be replaced with references to the common code.

The simplest form of refactoring can be done through the use of f unction or
method invocation. For example, in the following class, the highlighted code segment
occurs in two different contexts:

class Computation {
void method! ( . . . ) {
II . ..
computeStep10 i
computeStep2 0 i
computeStep3 0 i
II . ..
}
void method2 ( . . . ) {
II . ..
computeStepl O i
computeStep2 0 i
computeStep3 0 i
II . ..
}
II . ..
}
254 ■ Design by Abs1raction

The common code sequence can be refactored by introducing a new method com-
puteAll () that contains the recurring code segment. The class can be restructured
as follows:

class RefactoredComputation {
void computeAll () {
computeStep10 ;
computeStep20;
computeStep3 0 ;
}
void metbod1 ( . . . ) {
II . ..
computeAll O ;
II . . .
}
void metbod2(. ) {
II . . .
computeAll O ;
II . ..
}
II . . .
}

The refactored version is functionally identical to the original version. If the code
in the common code segment needs to be modified, it now has to be modified in only
one place (in computeAll ()) instead of several places, as in the original version.
Refactoring by method invocation is effective only when each occurrence of the
recurring code segment is contained within a single method and all the methods that
contain the recurring code segment belong to the same class. When the recurring
code segment involves several methods, such as the methods start (), stop () , and
run() in animation applets-or when the recurring code segment occurs in several
classes-refactoring can be accomplished by using inheritance or delegation.

Refactoring by Inheritance
The refactoring of recurring code segments in different classes can be done through
inheritance. Consider the following example, in which an identical computation
sequence occurs in two different classes:

class ComputationA { class ComputationB {


void method1 (. . . ) { void method2 ( . . . ) {
II . . . II . . .
computeStep1 () i computeStep1 () ;
computeStep2 () ; computeStep2();
computeStep3 () ; computeStep3();
II . . . II . ..
} }
II . . . II . . .
} }
7.2 Designing Generic Components ■ 255

A common superclass of ComputationA and ComputationB is introduced. The


recurring computation sequence is placed in a method computeAll () in the super-
class.
class Common {
void computeAll( . . ) {
computeStep1 () ;
computeStep2 () ;
computeStep3 () ;
}
}

Each occurrence of the code sequence in the original method can now be
replaced by an invocation of the method compute All () .
class ComputationA class ComputationB
extends Common { extends Common {
void methodl ( . . . ) { void method2 (. . . ) {
II . . . II . ..
computeAll () ; compute All () ;
II . . . II . ..
} }
II . .. II .. .
} }

When extracting common code segments to a superclass, all the field involved
in the computation must also be extracted and moved to the superclas .

Refactoring by Delegation
Refactoring of recurring code segments in different clas e can al o be done through
delegation. To refactor the recurring code equence in the preceding example u ing
delegation, we can introduce a helper class and place the refactored code equence in
the computeAll () method of the helper cla
class Helper {
void computeAll( . . ) {
computeStep10 ;
computeStep2 0 ;
computeStep3 0 ;
}
}

Both ComputationA and ComputationB need to contain a reference to the


helper cla s, and each occurrence of the recurring code equence in the original
methods can be replaced by a call to helper . computeAll O.

class ComputationA { class ComputationB {


void compute (. , . ) { void compute (. . . ) {
II ... II ...
helper . computeAll (); helper . computeAll() ;
II ...
II . . .
}
}
256 ■ Design by Abstraction

Helper helper; Helper helper;


II . . . II ...
} }

Refactoring by inheritance and by delegation can achieve rather similar effect .


Refactoring by inheritance is u ually simpler than by delegation. Refactoring by del-
egation is more flexible, ince a class that wants access to the refactored method does
not need to be related via inheritance to the class of the method. This consideration i
important becau e of the restriction of single inheritance among classes in Java. Refac-
toring by inheritance may not be po sible at times. When either ComputationA or
ComputationB must be a subclass of a class that is not Object, for example, refac-
toring by inheritance is not possible. In contrast, refactoring can always be achieved
through delegation.

EXAMPLE 7.1 A Generic Animation Applet

PURPOSE
This example demonstrates refactoring by inheritance.

DESCRIPTION
A generic animation applet class AnimationApplet is defined by extracting the
common elements in animation applets: the methods start() , stop() , and run ().
The digital clock applet presented in Example 4.10 [p. 149] is reimplemented, using
the AnimationApplet class.

SOLUTION
The relationship among the classes Applet , AnimationApplet (the generic ani-
mation applet class), and Digi ta1Clock3 (the reimplemented digital clock applet
using the generic animation applet) are shown in Figure 7 .1. The methods of Anima-
t ionApplet are summarized in the following table:

Method Description

start(), stop() , and run() The common methods in animation applet


setDelay O and getDelay O Accessor and mutator of the delay field,
which specifies the interval between two con-
secutive animation frames in milliseconds

. Th_e setDelay O and getDelay O methods are introduced to make the generic
anunat1on applet class more extensible, allowing each concrete animation to set its
own refresh rate. Extensibility is essential to all generic components.
7.2 Designing Generic Components ■ 257

Figure 7.1 Applet AnimationApplet


start()
A generic anima-
stop()
tion applet. run()
setDelay()
getDelay()

Digita1Clock3
paint()

Design Guideline Maximize Extensibility


Rarely can components be reused without adaptation. Extensibility allows compo-
nents to be extended and adapted to different contexts. The more exten ible a com-
ponent is, the better the chance that it may be reused.

The AnimationApplet class is defined as follows:

Generic animation ~pplet: Ani.JDationApplet


import j ava . awt . *;
publ ic class AnimationApplet
ext ends java.applet.Applet
impl ements java .lang.Runnable {
public voi d s t art() {
animat ionThread = new Thread (this);
animationThread . start ();
}
public void stop() {
animationThread = null;
}
public void run() {
while (Thread. currentThread() == animationThread) {
t ry {
Thread.currentThread ().sleep (delay);
} catch (InterruptedException e){}
repaint();
}
}
final public void setDelay(int delay) {
t his .delay= delay ;
}

.,
- .. .. -
-- -
258 ■ Design by Abstraction

final public int getDelay () {


return delay;
}
protected Thread animationThread ;
protected int delay = 100;
}

Note that the fields used by the methods start () , stop () , and run () are also
tracted to the superclass. The AnimationApplet class avoids duplication of the
::art() , stop() , and run() methods in every animation applet. A pecific, or
concrete, animation applet can simply extend AnimationApplet and override the
paint() method to draw animation frames.

public class MyAnimationApplet extends AnimationApplet {


publi c void paint(Graphics g) {
(paint a frame)
}
(other fields and methods)
}

Using AnimationApplet, we can reimplement the digital clock applet in Sec-


tion 4.7 [p. 149).

Concrete animation a let: Digita1Clock3


import java . awt . •;
i mport java .util.Calendar;
public class Digita1Clock3 extends Animat i onApplet {
publi c Di gita1Clock3() {
setDelay(l000);
}
public void paint (Graphics g) {
(the body is identical to that of the paint () method in
DigitalClock in Section 4.7)
}
protected Font font = new Font("Monospaced" , Font . BOLD, 48);
protected Color color = Color . green;
}

The refactoring in Example 7.1 is rather straightforward. However, in reality
refactoring is often more complicated. Let us try to take the generic animation example
one step further. Another code segment that also recurs in many animation applet
deals with double-buffering. We can enhance the generic animation applet so that
it can ~andle double-buff~ring when necessary. However, several aspects of double-
buffenng must be detemuned by the specific animation classes which are subclasse
of the genenc. anunallon
. ' applet. '
7.2 Designing Generic Components • 259

Ex AM p L E 7 . 2 A Double-Buffered Generic Animation Applet

PURPOSE
This example illustrates how to resolve the complications involved in refactoring by
inheritance. It also addresses issues of extensibility and adaptability in the generic
animation applet.
DESCRIPTION
A double-buffered generic animation applet class DBAnimationApplet is defined,
using refactoring by inheritance. The bouncing ball applet in Example 5.4 [p. 195] is
reimplemented, using the DBAnimati onApplet class.
KEY ISSUES
■ Accommodating various sizes of the view area.
■ Allowing the subclasses to decide whether to use double-buffering.
■ Refactoring common code segments that contain changeable parts.
SOLUTION
With regard to the first key issue, in order to initialize the off-screen image buffer, we
need to know the dimensions of the viewing area. The problem is that the dimensions
of the viewing area are not known until the ini t () method is invoked. A simplistic
solution to this problem is to override the ini t () method in the generic animation
applet.
public class DBAnimationApplet extends AnimationApplet {
protected Dimension dim;
protected Image im;
protected Graphics offscreen;
public void i nit() {
dim = getSize () ;
im = createimage(dim . width, dim .height);
offscreen = im.getGraphics( );
}
II ...
}

This approach properly initializes the off-screen image buffer. However, a prob-
lem arises if a subclass of DBAnimationApplet require additional initialization to
be done in the ini t () method, such as getting parameters. Then it is necessary for
the ini t O method in the subclass to call super. init () to initialize the off-screen
image buffer properly.
public class MyAnimation extends DBAnimationApplet {
public void init() {
// additional Initialization specific to MyAnimatlon
String param = getParameter ( . . . ) ;
II . . .
-
260 • Design by Abstraction

// the following line is necessary


super . ini t O ;
}
II . . .
}

Not invoking super. ini t () will cause a null-pointer exception. The burden
of following this convention is entirely on the implementer of subclasses because its
violation will not be detected by the Java compiler. For various reasons, violation
of such conventions are very common, and they often lead to faults, or bugs, that
are difficult to trace. Although using a component without following the proper
conventions is a misuse of the component, designers of reusable components must
do their best to prevent the components being misused.

Design Guideline Prevent Misuses by Clients


Well-designed classes should prevent possible misuses by making violations of the
conventions of using the classes compilation errors, or using assertions to detect the
violations at run time (i.e., defensive programming).

A better solution for initializing the off-screen image buffer is to introduce a new
method initAnimator O for subclasses to perform subclass-specific initializations.
The ini t () method is made final, so that it cannot be accidentally overridden by the
subclasses. This approach ensures that initializations of dim, im, and off screen will
always be done properly. A null implementation is provided for ini tAnimator () .
A subclass needs to override this method only when there are subclas - pecific
initializations.

public class DBAnimationApplet extends AnimationApplet {


protected Dimension dim;
protected Image im;
protected Graphics offscreen ;
final public void init (Graphics g) {
di m .. getSize () ;
im .. createimage(dim.width, dim.height);
offscreen = im. getGraphi cs ();
i ni tAnimator () ;
}

protected void initAnimator() {}


}

publi c class MyAnimation extends DBAnimationApplet {


protected void ini tAnimator () {
II additional initialization specific to MyAnirnatlon
·--

7.2 Designing Generic Components ■ 261

String par am = getParameter (. . . ) ;


II . ..
}
II . . .
}

The second key issue involves giving the subclas es the flexibility to decide
whether to use double-buffering. Hence the generic animation applet should accom-
modate two scenarios:

1. When double-buffering is not needed, the default implementation of update ()


should be used and the paint () method should be overridden by the ubclass
to paint a frame.
2. When double-buffering is needed, the update O method should be overridden
to paint a frame.

The generic animation class should unify the two scenario o that it can handle
both. A boolean variable can be used to control the behavior of the update () method.
public class DBAnimationApplet extends AnimationApplet {
protected boolean doubleBuffered ;
void update(Graphics g) {
if (doubleBuffered) {
II do double-buffering
} else {
super . update (g) ; // use the default implementation
}
}
II . . .
}

The third key issue involves the use of double-buffering. The part of the up-
date () method that deal with double-buffering i common to all animation applet ,
whereas the part that deals with painting the frame varie from applet to applet. The
generic animation clas should refactor the common part and allow its ubclas e to
provide the variable part.
The refactoring here i a bit more complicated than before, ince the common
code egments are intemlixed with context-·pecific code. Let u con ider a more
general situation. Two method , methodl () and method2 () , have ome common
code segments that are intennixed with ome context- pecific code:

class ContextA { class ContextB {


void method( . . . ) { void method ( . . . ) {
(common code segment l ) (common ode egment I )
(context-specific code A ) (context- pecific code B)
(common cod~ segment 2) (common code ' egment 2)
} }
II . .. II . . .
} }
262 • Design by Abstraction

How can the common code segments be refactored? Two approaches are avail-
able. The first approach is to refactor each common code segment into a separate
method by using the refactoring by inheritance technique (p. 254].
class Common {
void commonCodel () {
(common code segment I}
}
void commonCode2 () {
(common code segment 2}
}
}

class ContextA class ContextB


extends Common { extends Common {
void method( .. . ) { void method ( . . . ) {
commonCode 1 () commonCodel ()
(context-specific code A} (context-specific code B}
commonCode2 0 commonCode2 ()
} }
II ... II .. .
} }

This approach works, and it is a perfectly good solution when the two common
code segments are relatively independent. However, this approach is error-prone when
the two common code segments are closely related and are merely pieces of a larger
process. Breaking the common code segments into two separate methods breaks the
logical flow and hampers the readability of the code. The invocation of common-
Code1 () and commonCode2 () in each context must be carefully coordinated . If one
of them is omitted or they are invoked in a different order, unpredictable outcomes
may result.
The second, more generic, approach is to extract the entire method that contai ns
both common and context-specific code to a superclass and then refactor the context-
specific code by introducing a new method as a placeholder, which is intended to be
overridden and customized in each subclass.
class Common {
void method( . . . ) {
(common code segment I)
contextSpecificCode ();
(common code segment 2)
}
void contextSpecificCode() { .. . }
}

class ContextA extend.a Common {


void contextSpecificCode() {
(context-specific code A}
}
II . . .
}
7 .2 Designing Generic Components • 263

class ContextB extends Common {


void contextSpecificCode () {
{context-specific code B}
}
II . ..
}

The contextSpecificCode O method serves as a placeholder in the Common


class. The question is, What implementation should be provided for contextSpe-
c if i cCode () in class Common? The answer is that no implementation is appropriate,
as Common has no knowledge of the specific context. Only the subclasses can provide
sensible implementation for this method. Methods that serve as J>laceholders are
declared abstract methods. The Common class in tfie preceding code fragment is an
abstract class with an abstract method. It can be declared as follows:

abstract class Common {


void method ( . . . ) {
{common code segment I}
contextSpecifi cCode ( );
{common code segment 2}
}
abstract void contextSpecificCode () ;
}

An abstract superclass provides a generic approach to the problem of refactoring


rec urring code segments intermixed with context-specific code. The recurring code i
extracted, as before, and an abstract method is declared to serve as a placeholder for
the context- pecific code. The Java compiler will compel the implementor of every
de rived eta s to implement the abstract method, allowing for the insertion of context-
pecific code. This approach achieves a high level of encapsulation of the otherwi e
common and recurring code segment. All maintenance of the common code may
be perfonued in one place, out of view and without concern to the de igner of the
ubclasses, who may focus their atte ntion instead on the context-specific code that is
germa ne to their problem.
This approach can be used to refactor double-buffering in the update () method
in the generic DBAnimationApplet . An ab tract method pai ntFrame O i intro-
duced for the subcla ses to override and to paint the frame . When we put all the
pieces together, we get the following program, which is the complete generic ani-
mation applet cla that upport double-buffering. The tructure of the program L
shown in Figure 7.2.

Double-buffered enerlc anlmadon applet: DliDillatioDAp~l•t


import java . awt . •;
publ ic a bstract class DBAnimationAppl et
extends Ani mationApplet {
264 • Design by Abstraction

final public void update(Graphics g) {


if (doubleBuffered) {
paintFrame(offscreen);
g.drawimage(im, 0, 0, this);
} else {
super.update O ;
}
}

final public void paint(Graphics g) {


paintFrame(g);
}

final public void init() {


d = getSize();
im = createimage(d.width, d.height);
offscreen = im.getGraphics();
ini tAnimator () ;
}

protected void initAnimator() {}


abstract protected void paintFrame(Graphics g);
protected DBAnimationApplet(boolean doubleBuffered) {
this.doubleBuffered = doubleBuffered;
}

protected DBAnimationApplet() {
this.doubleBuffered = true;
}

protected boolean doubleBuffered;


protected Dimension d;
protected Image im ;
protected Graphics offscreen;
}

Figure 7.2
I Applet I'-' AnimationApplet /1
"
DBAnimationApplet
start() update()
A double-buffered
stop() paint()
generic animation
run() init()
applet.
setDelay() initAnimator()
getDelay() paintFrame()

I
Digita1Clock3
i
BouncingBa112
paint()
initAnlmator()
palntFrame()
7.2 Designing Generic Components • 265

The ini tAnimatorO and paintFrame () methods are protected, as they are
intended only for the subclasses to override. The update() , paint() , and ini t ()
methods are final so that the subclasses cannot alter their implementation. The con-
ventions for using the double-buffered generic animation applet class are as follows:

■ A concrete animation applet should extend the DBAnimationApplet class.


The boolean parameter of the constructor indicates whether double-buffering is
needed.
■ Each concrete animation applet must override the paintFrame ( ) method to
paint the frames for each animation.
■ The ini tAnimator () method may be overridden to provide subclass-specific
initializations.

The bouncing ball applet developed in Chapter 5 can be reimplemented by using the
DBAnimationApplet class.

Concrete double-buffered pnhnaflon • B0111Lc1n--all2


i mport j ava.awt . • ;
public class Bouncing8all2 extends DBAnimationApplet {

public Bounci ng8all2 () {


super (true); // double buffering
}

protected void initAnimator() {


Stri ng att = getParameter("delay");
if (at t != null)
set Delay( Int eger.parseint(att));
x = d .widt h * 2 / 3 ;
y = d . height - radius;
}
I....
'
protect ed void pai ntFr ame(Graphics g) {
g. s et Col or(Color . white);
g.fillRect(0 , 0, d .width,d. height);
if (x < radi us I I x > d.width - r adi us) {
dx = -dx;
}
if (y < radius I I y > d. height - radius) {
dy = - dy;
}
x += dx ; y += dy;
g.setColor(color);
g . fillOval(x - radius, y - radius, radius* 2, radius* 2);
}

Protected int x, y;
dy = - 4;
protected int dx = - 2 '
protected int radius = 20; .
protecte d C0 lor color = Color.green,

}
. .

266 • Design by Abstraction

The co1~ 1onalities in animation and double-buffering can al o be refactored by


using deleganon.

7.2.2 Design Pattern: Template Method

The precedin~ example illustra_tes t~e use of an abstract class that_serves as a template
for classes with shared funct1onahty. An abstract class contams behavior that is
common to all of its subclas es. The common behavior is encapsulated in nonabstract
methods, which may even be declared final to prevent any modification. Using an
ab tract class en ures that all subclasses will inherit the same common behavior (i.e.,
implementation). The abstract methods in such templates require that context-specific
behavior be implemented for each concrete subclass.
The abstract method paint Frame () acts as a placeholder for the behavior that
is implemented differently for each specific context. We call such methods hook
methods, upon which context-specific behavior may be hung, or implemented. The
paintFrame () hook is placed, i.e., invoked, within the method update() , which
is common to all concrete animation applets. Methods containing hooks are called
te~plate._methods.
The double-buffered generic animation applet we just discussed illustrates the
Template Method design pattern. The abstract method paintFrame () represents
the behavior that is changeable, and its implementation is deferred to the concrete
animation applets. The paintFrame () method is a hook method. Using the hook
method, we are able to define the update() method, which represents a behavior
common to all the concrete animation applets. The update () method is a template
method. A template method uses hook methods to define a common behavior. Tem-
plate methods describe the fixed behaviors of a generic class, which are sometime
called frozen spots. Hook methods indicate the changeable behaviors of a generic
class, which are sometimes called hot spots.

Design Pattern Template Method

Category: Behavioral design pattern.


Intent: To define the skeleton of an algorithm in a method, deferring some step
to subclasses, thus allowing the subclasses to redefine certain steps of the
algorithm.
Applicability: The Template Method pattern should be used
• to implement ~e invariant parts of an algorithm once and leave it to the
subcflasses to implement behavior that can vary.
• to re actor.and. localize the common behav1or
· among subclasses to avot·d
code duplication.
-.- ......

7.2 Designing Generic Components ■ 26 7

T he structure of the Template Method design pattern is as follows:

GenericC/ass
templateMethod() O '\
hookMethod 1() hookMethod1 ()
hookMethod2()
0, hookMethod2()

ConcreteClass
hookMethod1 ()
hookMethod2()

The participants in the Template Method design pattern are these:

■ GenericClass (e.g. , DBAni rnat i onApplet), which defines ab tract hook meth-
ods (e.g., paintFrarne O) that concrete subclasses (e.g., BouncingBall2)
override to implement steps of an algorithm .and implements a template method
(e.g., update O ) that defines the skeleton of an algorithm by calling the hook
me thods.
■ ConcreteClass (e.g., Bounc i ngBall2), which implement the hook methods
(e.g., paintFrarne O) to carry out subclass-specific steps of the algorithm de-
fined in the template method .

In the Template Method design pattern, hook method do not have to be abstract.
T he generic c las may provide default implementations for the hook methods. Thus,
the subclasses have the option of overriding the hook method or u ing the default
imple mentation. The initAnirnator O method in DBAnimation.Applet is a non-
abstract hook method with a default implementation. The i n i t () method is another
te mplate method.
The following example illustrates the u e of ab tract classe and the Template
Method desig n pattern:

EXAMPLE 7 . 3 A Generic Function Plotter

PURPOSE
To illu trate the u e of the Template Method de ign pattern.

DESCRIPTION
De ign and implement a generic func1i.0 11 pl~tter applet Plotter for _plo tting arbitrary
sing le-variable func tion on a two-d11ne11s1on~l pace. T he genenc plo tter_~hould
capture the com mon behavior relaled to drawing and leave onJy the defi rntto n of
-- 268 • Design by Abstraction

the function to be plotted to its subclasses. A concrete plotter PlotSine will be


implemented to plot the function

y = sin x
A screen shot of running the Plot Sine applet is shown in Figure 7 .3.

KEY ISSUE
To represent an arbitrary single-variable function.

SOLUTION
The Template Method pattern offers a reasonable solution. The single-variable func-
tion to be plotted can be represented as a hook method in the generic plotter class. The
function-plotting method can then be defined in the generic plotter class as a template
method. The generic plotter class is outlined in the following program segment:

public abstract class Plotter {


II the hook method
public abstract double func (double x);
// the template method
protected void plotFunction(Graphics g) {
II ...
}
II .. .
}

Figure 7.3
Applet Viewer: PlotSine.class lllil a
A screen shot of Applet
PlotSine.

Applet started.

.
-
7.2 Designing Generic Components • 269

Figure 7.4 I Applet Plotter


tune()
A generic function
paint()
plotter.
plotFunetion()
drawCoordinates()

PlotSine
tune()
' I

PlotCosine
tune()

The concrete plotter will only need to extend the generic plotter and define the
function to be plotted by overriding the hook method. The program structure is shown
in Figure 7.4.
The generic function plotter Plotter is implemented as an applet. It has the
following methods:

Method Description

ini t () Initialization method to get the parameter


func ( ) Hook method representing the function to be
plotted
paint () Method to paint the image
plotFunctionO Template method, invoked by paint O to
plot the function
drawCoor dinates () Auxiliary function called by paint O to
draw the x - and y-axes and tjck marks.

The Pl otter applet takes the following parameter set in the applet tag:

xorigin The x coordinate of the origin on the canva


yorigin The y coorilinate of the origin on the canvas

xratio The number or pixels in the unit length on the x-axis

yratio The number of pixels in the unit length on the y-axis


270 ■ Design by Abstraction

Generic function ~otter: Plotter


import java.awt.•;
public abstract class Plotter extends java.applet . Applet {
public abstract double func(double x); // hook method
public void paint(Graphics g) {
drawCoordinates(g);
plotFunction(g);
}

protected void plotFunction(Graphics g) {


for (int px = 0; px < dim .width; px++) {
try {
double x = (double) (px - xorigin) / (double)xratio;
double y = func(x);
int py = yorigin - (int) (y * yratio);
g.fill0val(px - 1, py - 1, 3, 3);
} catch (Exception e) {}
}
}

public void init() {


(Get the parameters and initialize the fields)
}

protected void drawCoordinates(Graphics g) {


(Draw the x- and y-axes and tick marks)
}

protected Dimension dim ;


protected Color color= Color.black;
protected int xorigin, yorigin;
protected int xratio = 100, yratio = 100;
}

The bodies of the ini t () and drawCoordinates () methods are left as an


exercise. The complete source code is available online.
The class Plot Sine is a concrete function plotter that simply extends the Plot-
ter class and implements the method f unc () .

Concreteflllldloa
public class PlotSine extends Plotter {
public double func(double x) {
return Math . sin(x);
}
}

A screen shot of running the concrete function plotter PlotSine is shown in


Figure 7.3.
7.2 Designing Generic Components ■ 271

7.2.3 Generalizing

Generalizing is a process that takes a solution to a specific problem and restructures it


so that it not only solves the original problem but also solves a category of problems
that are similar to the original problem.

EXAMPLE 7.4 A Generic Function Plotter That Plots Multiple Functions

PURPOSE
This example demonstrates the generalizing technique and illustrates the use of the
Strategy design pattern.

DESCRIPTION
In this example, the generic function plotter class Plotter is generalized to plot
multiple single-variable functions overlaid on the same two-dimensional space. The
new generic multiple function plotter is named MultiPlotter. A concrete subclass
PlotSineCosine is implemented to plot

y =smx and y = cosx


A screen shot of running the PlotSineCosine applet is shown in Figure 7.5 .

KEY ISSUE
How to separate the functions to be plotted from the plotter.

Rgure 7.5

A screen shot of
PlotSineCosine.

Applet started,
272 ■ Design by Abstraction

SOLUTION
One way the oeneric Plotter class can be generalized to plot multiple function s is
by adding mo;e hook methods, one for each function to be plotted.

public class Plotter2 {


abstract double func1(double x) ;
abstract double func2(double x);
abstract double func3 (double x) ;
II . ..
}

This approach is rather inflexible and inelegant, allowing only a fixed number of
functions. Segments of nearly identical code will be duplicated to plot each function.
The problem of this approach is that the functions to be plotted and the mechanism to
plot the functions are coupled into the same class. Although the coupling is hannless
for a single-function plotter, it hampers the extensibility of the plotter. A more elegant
olution is to decouple the functions to be plotted from the mechanism to plot them.
Instead of representing each function as a method, we can represent each function
as an objecr.2 Different functions are instances of different fun ction classes. To
allow unifonn manipulation of the functions, all the function classes implement the
following common interface:

lntaface Function
interface Function {
doubl e apply (double x) ;
}

The functions sin x and cos x can be represented simply as follows:

Coaaele fundlons Sine and Cosine


publi c class Sine implements Function {
public double apply (double x) {
return Math.sin (x);
}
}

public class Cosi ne implements Function {


public double apply (double x) {
return Math .cos (x);
}
}

The tructure of the generic multiple function plotter is shown in Figure 7 .6. The
Mul tiPlotter class extends Plotter. It uses a pair of parallel arrays to store the

2. Objects that rcpre en! functions arc sometimes calledfun c/ors.


7.2 Designing Generic Components ■ 273

Figure 7.6 I Applet Plotter


tune()
Generic multiple paint()
tunction plotter. plotFunction()
drawCoordinates()

i
MultiPlotter k>
. Function
initMultiP/otter() apply()
init()
addFunction()
plotFunction()
1
tune();

i
PlotSineCosine
Creates
-- ----- -3>
Sine
apply() Cosine
in itM ulti Plotter() ---- - ----------- --> apply()
Creates

func tions to be plotted and the colors used to plot the functio ns. Although there is a
limit to the number of func tions, MAX_FUNCTIONS, this limit can be easily removed
without affecting any other parts of the program by using an unbounded container,
such as Li s t or Set (see Section 8.2 [p. 308]). The methods of Mu1 tiPl ott e r are
summarized in the following table:

Method Description

init MultiPlott erO Hook method for the ubclasses to er up the


functions to be plotted
ini t () Template method for initialization, which call
the hook method ini tMul tiPlotter 0
addFunctionO Method to add a fu nction to be plotted
plotFunction () Auxiliary function called by paint O to plot
the functions
func () Method inherited from class Plotter that is no
longer u cful in thi cla
274 ■ Design by Abstraction

Implementation of MultiPlotter is as follows:

Generic multi,!e function lotter: MultiPlotter


i mport java.awt.•;
public abstract class MultiPlotter extends Plotter {
abstract public void initMultiPlotter () ;
public void init ( ) {
super . ini t O ;
initMultiPlotter() ;
}

final public void addFunction(Function f, Color c) {


if (numOfFunctions < MAX_FUNCTIONS &&
f != null) {
functions[numOfFunctions] = f;
colors[numOfFunctions++) = c;
}
}

protected void plotFunction(Graphics g) {


for (inti= O; i < numOfFunctions; i++) {
if (functions [i] ! = null) {
Color c = colors[i];
if (c ! = null)
g . setColor(c);
else
g.setColor(Color.black);
for (int px = O; px < d.width; px++) {
try {
double x = (double) (px - xorigin) / (double ) xrat io ;
double y = functions[i] .apply(x);
int py = yorigin - (int) (y * yratio) ;
g .fillOval (px - 1, py - 1, 3, 3) ;
} catch (Exception e) {}
}
}
}
}

public double func(double x) {


return 0 . 0 ;
}

protected static i nt MAX_FUNCTIONS = 5 ;


protected i nt numOfFunctions = 0 ,
protected Function fun t i (] '
c ons = new Function[MAX FUNCTIONS]·
protected Color colors[] • new Col [MAX - '
or _FUNCTIONS] ;
}
7.2 Designing Generic Components • 27 5

The following concrete subclass of Mul tiPlotter class plots two functions :

y = sin x and y = cosx


utilizing the Sine and Cosine classes:

Concrete multi le function ~lotter: lfult1Plotter


import java.awt.Color;
public class PlotSineCosine extends MultiPlotter {
public void initMultiPlotter() {
addFunction(new Sine(), Color.green);
addFunction (new Cosine() , Color .blue );
}
}

I
7.2.4 Design Pattern: Strategy

The preceding generic plotter of multiple functions illustrates the Strategy design
pattern. The structure of the Strategy design pattern is shown in the following diagram:

St rategy
Context
A
V Strategy
ContextMethod() algorithm()
I;,.
I

r------- --~--- ---- ---


1 I
'
ConcreteStrategyA ConcreteStrategyB
algorithm() algorithm()

The participants in the Strategy design pattern are as follow :

■ Strategy (e.g., Function), which declares an interface common to all supported


algorithms.
• ConcreteStrategy (e.g., Sine and Cosine), which implement the algorithm
using the Strategy interface.
• Context (e.g., Mul tiPlotter), which maintain reference to one or more
Strategy objects (e.g., functions in MultiPlotter).

The Strategy design pattern can be considered a a variation of the Template


Method design pattern, in which the hook method and the template method reside in
two different classes. The abstract methods declared in the Strategy class are the
hook method , and the methods in the Context clas that call the hook methods are
the template methods.
27 6 • ~ by Abstraction

Design Pattern Strategy


Category: Behavioral de ign pattern.
Intent: To define a family of algorithms, encapsulate each one, and make them
interchangeable.
Applicabiliry: The Strategy pattern should be used when
• many related classes differ only in their behavior (e.g., plot different
functions).
• different variants of an algorithm are needed (e.g., the sort algorithms in
the case study in Section 7.4 [p. 284)).
• an algorithm uses data that clients should not know about (e.g., the
LayoutManager in AWT-see Section 8.3.3 [p. 338]).
• a class defines many behaviors, which appear as multiple conditional
statements in its methods (e.g., the TwoEndsTool in the case tudy in
Chapter 9 [p. 439]).

, comm
--
din C and C++
I

to pass functions as parameters.

7.3 ABSTRACT COUPLl~G

The Strategy design pattern is an example of abstract coupling. Abstract coupling


refers to a way in which clients couple with service providers. A client acces e a
service through an interface or an abstract class without knowing the actual concrete
class that provides the service.
Let us again use the telephone service analogy. The clients are the telephone cu -
tomers, and the telephone service providers are the telephone companies. Customers
can be coupled to the telephone companies directly or through abstract coupling, a
illu trated in Figure 7.7. Direct coupling uses each telephone company's proprietary
equipment and protocols to provide ervices to its customers. Each customer has to
choo ea company for telephone service. Switching to a different company would be
difficult because doing so would require replacing equipment and changing the way
customers make phone calls.
Abstract coupling_is accomplished by requiring all telephone companies co u e
the same tandard equipment and protocols. Doing so would make telephone service
interchangeable and al_low customers to switch easily among different companie .
Cu tomer would receive the same service without havi ng to replace equipment or
change the way they make phone calls.
7.3 Abstract CoupUng ■ 277

Figure 7.7
ICustomerf<fil<=-----------'>:.i/ Service provider A I
(a) Direct coupling
versus (b) abstract ICustomer1-<I<'-------->~/Service provider B I
coupling.
/ Customer/t--=<=----------'>~/ Service provider c I
(a)

Service provider A
Standard
Customer - - - - - ~ service
protocol
Service provider C
(b)

Abstract coupling is an important mechanism for enhancing extensibility and


reusability in object-oriented design and occurs in many design patterns. Abstract
coupling is also an application of the following important guideline of object-oriented
design:

Design Guideline Program to an Interface, Not to an Implementation


Separate interface from implementation. Clients of a class should access only the
functionalities of the class via its interface. Implementation should be hidden and
irrelevant to the client.

Programming to an implementation yields ad hoc, context-specific, and inflex-


ible solutions. Programming to an interface yields general. extensible, and reusable
solutions.
In Java, implementations are defined a the field and method in classes, and the
interfaces of the implementations can be declared separately as the abstract methods
in abstract classes or interfaces. An abslract method declares only the service or
the feature to be provided but define no implementation. Each abstract method also
defines a contract of the ervice to be provided. The contract of an abstract method
i a pecification that defines the serv~ce the clients c~ _e~~ect but ~ithout giving an
implementation. The contract also define the respon 1b1ht1e of the implementations
of the service.
278 ■ Design by Abstraction

7.3.1 Enumerating Elements

Enumerating the elements in a collection object, such as a list, is one of the most
common operations on any collection. Although it may seem to be a rather trivial
problem, it actually is not. Let us use the simple linked list class LinkedList
di cussed in Section 6.1.1 [p. 207] as an example. Suppose that we have populated
a list with instances of the Course class shown in the following code fragment.
The instance of the Course class represent courses offered at a university. The
isPrerequisi teOf () method returns true when the receiving object represents a
course that is a prerequisite of the course represented by the argument other.
Class COUl"II
public class Course {
public Course(String dept, String code, String title, int level) {
this .dept= dept;
this.code= code;
this .title= title;
this .level= level;
}

public String toString() {


return (dept+ ti ti+ code+ ti ti+ title);
}

public boolean isPrerequisiteOf(Course other) {


if (other!= null k&
this.dept== other.dept &&
this.level< other . level)
return true; {
} else {
return false;
}
}

public String dept, code , title;


public int level;
}

What we want to do is simply iterate through the list and print out the courses in
the list.

Solution A: Direct Access

People who are familiar with procedural programming could easily come up with the
following solution:
LinkedList list;
II populate the courses 11st
for (Li nkedList.Node cur= list.head;
cur I= null;
cur= cur .next) {
System.out .println(cur .element);
}
7.3 Abstract Coupling • 279

This simple solution works only if the fields of LinkedList and the inner class
Node are accessible to the clients of LinkedList, and such accessibility means
that the fields and the inner class must be public. As we discussed earlier, exposing
implementation details by making the fields public not only violates the principle of
encapsulation but could also compromi e the integrity of the class. This olution is a
poor design.

Solution B: Iterate via Method Invocation


A better alternative is to keep the field and the inner class Node nonpublic and
provide public methods for iterating through the list. The IterList cla extends
LinkedList by adding a field cur that point to the current position of the iteration
and providing the following methods for iterating through the list:

Method Description

reset () Moves the current position to the


beginning of the list
next () Retrieves the current element in the
list and advances the current position
hasNext () Returns true if the current position is
not at the end of the list

Class IterLiat
public class IterList extends LinkedList {

public void reset () {


cur= head;
}

public Object next () {


Object obj= null;
if (cur != null ) {
obj= cur . element ;
cur= cur .next ;
}
return obj;
}

publi c boolean hasNext () {


return (cur != null );
}

prote cted Node cur ;


}
280 • ~gn by Abstraction

Now, a client can iterate through the list as follows:


IterList list;
// populate the courses list . ..
for (list.reset(); list.hasNext () ; ) {
System.out .println(list.next());
}

This solution is clean and simple and is adequate for many situations. However,
what if we want to print a table of course prerequisites that lists all the pairs of courses
c 1and c2, in which c1is a prerequisite of c2. To print out the prerequisite table requires
nested loops. A logical solution seems to be the following:
for (list.reset() ; list.hasNext() ; ) {
Course cl= (Course) list .next();
for (list .reset (); list .hasNext() ;) {
Course c2 = (Course) list.next();
if (c1.isPrerequisiteOf(c2)) {
System.out.println(cl + 11 is a prerequisite of 11
+ c2);
}
}
}

Unfortunately, this solution does not work. The outer loop terminates after the
first iteration. The reason is that both the outer and inner loops iterate through the
same list, and when the inner loop completes one pass through the list, the cur index
of the list has been moved to the end of the list, causing the outer loop to terminate.
This type of iteration works only when two or more nested iterations are not on the
same list.3

Solution C: Separate the Iterator from the List


When there are simultaneous iterations through the same list, each iteration must have
its own current position index. In other words, the current position index should be
decoupled from the list. The following solution uses a separate iterator class that
contains the methods for iterating through the list and the current position index. It
contains a reference to the original list.

.....,IDrllllbd U.: Uaudl.tltiterator


public class LinkedListlterator {
public LinkedListiterator(LinkedList list) {
this . list= list;
cur = list.head;
}

public Object next() {


Object obj• null ;
i f (cur != null) {

3. Simultancou iteraLion may arise in unexpecled si1ua1io11 • d 10


·
recursion. s an much less ohvious forms, such n
7.3 Abstract Coupling ■ 281

obj= cur . element


cur= cur . next;
}
return obj;
}

public boolean hasNext() {


return (cur != null);
}

protected LinkedList . Node cur;


protected LinkedList list ;
}

Now, the following code segment prints the prerequisite table properly:
LinkedListiterator iter1 = new LinkedListLiterator(list);
LinkedListiterator iter2 = new LinkedListLiterator(list);
while (iter1 . hasNext()) {
Course c1 = (Course) iter1.next();
while (iter2 . hasNext ()) {
Course c2 = (Course) iter2.next();
if (c1.isPrerequisite □f(c2)) {
System . out.println(c1 + 11 is a prerequisite of 11 + c2);
}
}
}

Solution D: A Generalization
If all we needed to deal with were LinkedList, Solution C would be perfect. How-
ever, iterating through a collection is a rather common problem, and the collection
could be a list, a hash table, or a set. The implementation in Solution C is tied to a spe-
cific class-LinkedList-whereas the principle is applicable to iterating through
all kinds of collections. We can develop a uniform way to iterate through all kinds of
collections by usin - ~ piing. In other words, clients will not deal directly
with iterators tied to specific implementations; instead, clients will deal with abstract
iterators. The Java Class Library provides two interfaces for abstract iterator .4
interface Iterator {
boolean hasNext();
Object next O;
void remove();
}

interface Enumeration {
boolean hasMoreElements ();
Object nextElement ();
}

4. The reason for having two abstract itera tor interfaces in Java 2 i _to ensure bac~ward com~atibiliry. The
· ·inter face w,..,
En umerat1on ~" pro,•t'dcd by Java l ·O· The Iterator . interface
. was. introduced
. in Java 2 and
·
subsume l he Enumera t 10n 1 ·ntcrfa"e
~ •
Th•'
~
Enumeration interface ts used mainly in Java 1.0.x classes,
such as Ve ct or and Hashtable.
282 • Design by Abstraction

The contracts for the methods of Iterator and Enumeration are as follows:

Method Description

next() Retrieves the current element in the list and


advances the current position
hasNextO Returns true if the current position is not at the
end of the list
remove () Removes the current element in the list and
advances the current position
nextElement () Same as next() in Iterator
hasMoreElements () Sarne as hasNext () in Iterator

Each collection class that supports iteration should provide a concrete iterator
that implements the Iterator interface. Clients will deal only with the abstract
iterator and need no knowledge of the concrete iterators. They can iterate through
any collection in a uniform way by using the abstract iterator.
To illustrate the implementation of an iterator, we first add the following method
to the List interface:

public interface List {


public Iterator iterator();
II other methods .. .
}

The iterator () method returns an iterator to iterate through a li st. In the imple-
mentation of the LinkedList class, we add the implementation of the iterator ()
method, which returns a concrete iterator- an instance of the private inner cla s
LinkedListiterator.

LIDked lilt dall with ID abltnct lferator


public class LinkedList implements List {
II other methods and constructors
public Iterator iterator() {
return new LinkedListiterator();
}

private class LinkedListiterator implements Iterator {


public boolean hasNext() {
return cur != null;
}
-
7.3 Abstract Coupling • 283

public Object next() {


Object obj= null;
if (cur != null) {
obj= cur.element;
cur= cur . next;
}
return obj;
}

public void remove() {


throw new UnSupportedOperationException() ;
}

private LinkedList .Node cur;


LinkedListiterator() {
cur= head;
}
}
}

Clients iterate through a list by using abstract iterators.


List list= new LinkedList();
// populate the course list
Iterator iterl = list.iterator();
for(; iterl.hasNext(); ) {
Course cl= (Course) iterl.next() ;
Iterator iter2 = list.iterator();
for(; iter2 . hasNext(); ) {
Course c2 = (Course) iter2.next();
if (cl.isPrerequisiteOf(c2)) {
System . out.println(cl + 11 is a prerequisite of 11
+ c2);
}
}
}

Note that the concrete iterator class is private. CLient of the LinkedList cla
need to know nothing about the concrete iterator.

7.3.2 Design Pattern: Iterator

The use of ab tract iterator to iterate unifomlly through different concrete collections
is expres ed as the following de ign pattern:

Design Pattern Iterator


Categ01y: Behavioral de ign pattern.
Intent: To provide a way to acce the element of a collection equentially.
284 • ~sign by Abstraction

Applicability: The Iterator design pattern should be used


• to acce s the contents of a collection without exposing its internal rep-
resentation.
• to support multiple traversals of collections (e.g., the nested traversal of
the course list).
• to provide a uniform interface for traversing different collections (i.e.,
to support polymorphic iteration).

The structure of the Iterator pattern is illustrated in the following diagram:

AbstractCol/ection Iterator
iterator() hasNext()
6. next()

ConcreteCollection ~
r
Concretelterator
..... . . .. .......... . .. ... · ) hasNext()
iteratorO 0 Creates
next()

return new Concretelterator()

The participants of the Iterator pattern are as follows:


■ Iterator (e.g., Iterator), which defines an interface for accessing and traversing
the elements.
• Concrerelteraror (e.g., LinkedListiterator), which implements the iterator
interface and keeps track of the current position in the traversal of the collection.
• AbsrracrCollecrion (e.g., List), which defines an interface for creating a concrete
iterator (e.g., the i terator () method).
• ConcrereCol/ectio11 (e.g., LinkedList), which implements the iterator ()
method to return an instance of a proper concrete iterator.
The Iterator design pattern is used in the Java Collections Framework to provide
a uniform way of iterating through a variety of collection classes. See Section 8.2.3
[p. 315] for discus ion of the Java Collections Framework.

7.4 DESIGN CASE STUDY-ANIMATION OF SORTING ALGORITHMS


------
In this section'. we ~ev~lop an a~plet to animate different sorting algori thm.. A screen
hot of. the · h f
. an1mat1on
. 1s .shown
. m Figure. 7·8· W,e first
· develop a sl ra1g t orward and
monohth1c 1mplemen1at1on (1.e., the entire program 1·5 1·1npl d · I I )
emcnte as a smg e c a · .
7.4 Design Case Study-Animation of Sorting Algorithms
• 285

Figure 7.8
~ M·@l:1 .lclxl
A screen shot of
sort animation ap-
plet I
plet Sort. f

Applet started.

We then discuss the shortcomings of the initial implementation. Finally, we apply


design patterns to address the shortcomings and accomplish greater extensibility.

7.4.1 The Initial Implementation

We would like to use the generic animation applet DBAnimationApplet that we


developed in Example 7.2. Unfortunately, it is not quite suited for animating sorting
algorithms because it requires that the adjustment between two consecutive frames be
done in the paintFrame () method, immediately before or after a frame is painted.
Most sorting algorithms involve nested loops. Unwinding the loops so that each call to
paintFrame () advances one iteration of the inner loop would be rather awkward. A
better way to animate a complex algorithm is to let the algorithm control the progress
of the animation and pause when a new frame needs to be painted. The algorithm
animator class Algori thmAnimator extends DBAnimationApplet. Again, the
Template Method design pattern is used, and the Algori thmAnimator cJass defines
the following three methods:

Method Description

algorithm () Hook method for ubclassc to define the algorithm


to be animated
run() Template method, which . imply call the algo-
rithm() method to tart the animation
pause() Method to be called inside method algorithm ()
of the ubcla se when a frame need to be painted.
p·linting a frame and pau ing for a duration
pccified by delay
286 • Design by Abstraction

Each subclass of Algori thmAnimator is required to define the following


methods:

Method Description

algorithm () Define the algorithm to be animated. It should call


the pause () method when some progress is made
and a new frame needs to be painted.
paintFrame 0 Paint a frame.

Generic ~rltluira enbuUnn a •AlgorithaAniaator


public abstract class AlgorithmAnimator
extends DBAnimationApplet {
// the hook method
abstract protected void algorithm();
II the template method
public void run() {
algorithm O ;
}

final protected void pause() {


if (Thread.currentThread() == animationThread) {
try {
Thread.sleep(delay);
} catch (Interrupted.Exception e) {}
repaint();
}
}
}

The Monolithic Sort


Now, we are ready to use the generic algorithm animator to animate sorting algo-
rithms. The entire program is a single class Sort. In this animation, we sort an array
of nonnegative integers. The array to be sorted is declared as a field of Sort: pro-
tected int arr[] ; . The methods of Sort are summarized in the following table:

Method Description

scramble() Initialize and scramble the array to be sorted


pain tFr ame () Paint a frame
7.4 Design Case Study-Animation of Sorting Algorithms ■ 287

bubbleSort() Bubble sort algorithm


quickSort 0 Quick sort algorithm
ini tAnimator () Initialize the animator
algorithm 0 Sorting algorithm to be animated
swap() Auxiliary method used by sorting algorithms to
swap two numbers in the array to be sorted

The following is the overall structure of Sort:

Animation of so~ ~orlthms: Sort


import java.awt.*;

public class Sort extends AlgorithmAnimator {

(Method scramble () on page 287}


(Method paint Frame () on page 288}
(Method bubbleSort () on page 288}
(Method quickSort () on page 288)
(Method ini tAnimator () on page 289)
(Method algorithm () on page 289)
(Method swap() on page 289)

protected int arr[] ; II the array to be sorted


protected String algName; // the name of the algorithm to be animated
}

The scramble O method first determines the size of the array to be sorted.
Then it initializes the array and scrambles the numbers in the array. The array ize is
determined by the height of the viewing area. Because each number in the array will be
represented by a horizontal line that is 1 pixel wide-and adjacent line are eparated
by a I-pixel-wide gap-the size of the array i half the height of the viewing area in
pixels. Suppose that n is the size of the array and that the array contain numbers from
0 ton - I.

protected void scramble () {


arr= new int[getSize() .height / 2];
for (inti= arr . length ; --i >= O;) {
arr[i] = i;
}
for (inti= arr . length; --i >= O;) {
int j = (int)(i • Math.random ()) ;
swap(arr, i, j);
}
}
- .....
288 • Design by Abstraction

The paintFrame () method paints a frame based on the current contents of the
array arr. Each number in the array is represented by a horizontal line that is 1 pixel
wide. The length of each line is proportional to the number it represents, with the
longest line proportioned to occupy the entire width of the viewing area.

Method of Sort class: paintFru• ()


protected void paintFrame(Graphics g) {
Dimension d = getSize();
g . setColor(Color.white);
g.fillRect(O , 0, d.width, d.height);
g . setColor(Color . black);
int y = d . height - 1;
double f = d.width / (double) arr . length;
for (inti= arr.length; --i >= O; y -= 2) {
g . drawLine(O, y, (int)(arr[i) • f), y);
}
}

The bubbleSort O and quickSort O methods are the two algorithms to be


animated. Each time the program is invoked, one of them will be animated. The im-
plementation of the algorithms is rather straightforward, except that method pause ()
is called whenever progress is made during the sorting process, to briefly pause the
execution of the program and display the current state of the array.

Method of Sort class: bubbleSort()


protected void bubbleSort(int a[]) {
for (inti= a . length; --i >= O; )
for (int j = O; j < i; j++) {
if (a[j] > a[j+1]) {
swap(a, j, j + 1);
}
pause();
}
}

MeCbocl of Sort daa: gl!ictsort 0


protected void quickSort(int a[], int loO, int hiO) {
int lo= loO, hi= hiO, mid;
pause();
if (hiO > loO) {
mid z a[(loO + hiO) / 2];
while(lo <= hi) {
while ((lo< hiO) && (a[lo] < mid)) {
++lo;
}
while ((hi> loO) && (a[hi] > mid) ) {
- -hi;
}
if(lo <= hi) {
swap(a, lo, hi);
7.4 Design Case Study-Animation of Sorting Algorithms • 289

pause();
++lo;
--hi;
}
}
if (loO < hi) {
quickSort(a, loo, hi);
}
if(lo < hiO) {
quickSort(a, lo, hiO);
}
}
}

Which of the two algorithms to be animated is determined by the parameter alg


set in the <applet> tag. The ini tAnimator O method initializes the animation by
first retrieving the parameter alg, storing it in the field algName, and then calling
scramble() to set up the array to be sorted.

Melled Sort mss: Wt.Animator()


protected void initAnimator() {
algName = 11 BubbleSort 11 ;
String at = getParameter( 11 alg 11 ) ;
if (at != null) {
algName = at;
}
setDelay(20) ;
scramble() ;
}

The algorithm() method simply chooses one of the algorithms to animate,


based on the value of the field algName.

Metlal of Sort dais: oritlm()


protected void algorithm () {
if ( "BubbleSort 11 • equals (algName)) {
bubbleSort (arr);
} else if ( 11 QuickSort 11 . equals (algName)) {
quickSort (arr, 0, arr.length - 1);
} else {
bubbleSort (arr );
}
}

Finally, the auxiliary method swap () -wap two number in the array.

Method of Sort class: ■n ()


private void swap(int a(], inti, int j) {
int temp c a(i]; a[i] = a(j]; a(j] = temp;
}
290 • Design by Abstraction

7.4.2 Separating Algorithms

The initial version of the sorting algorithm animation applet works, except that the
design of the program is not object oriented. It lumps everything into a single class,
which for those who are accustomed to procedural programming is a natural thing
to do. However, this approach usually results in huge classes that contain a wide
variety of functionalities, and thus lack of cohesion. The resulting programs are overly
complex, inflexible, and difficult to maintain in the long run.
Large classes usually contain loosely coupled elements. Such elements in a class
usually address different concerns in the problem to be solved. Separating these
concerns into different classes can reduce the complexity of the design and enhance
its extensibility and maintainability.

Design Guideline Separate Functionalities That Address Different Concerns


lf a class contains components that address different concerns, these components are
candidates for separation from the original class.

By separating different concerns in a loosely coupled class, we end up with


classes that are smaller and tightly coupled (i.e., all their components are closely
related and highly interdependent). When separation is combined with the Template
Method and Strategy design patterns, the resulting design can offer great extensibility
in solving each aspect of the problem.
The initial version of the sorting algorithm animation Sort is rather loosely
coupled. The sorting algorithms are good candidates for separating because they
should be independent of the animation mechanism. Adding or replacing sorting
algorithms should have little impact on the animation mechani sm . The algorithm
can be separated from the animation class, and the Strategy design pattern can be
used to make the algorithms to be animated interchangeable. The structure of the new
design is shown in Figure 7.9.

The Abstract Sorting Algorithm


The abstract class sortAlgorithm is the strategy in the Strategy design pattern. It
represents an abstract sorting algorithm. The sort () method is the hook method.
The pause () and swap () methods are to be invoked by the subclasses of Sort Al -
gorithm.

Abstract sorting al&orltbm: SortAl orithm


public abstract class SortAlgorithm {
abstract public void sort(int a[));
protected SortAlgorithm(AlgorithmAnimator animator) {
this.animator= animator ;
}
sD a

7.4 Design Case Study-Animation of Sorting Algorithms ■ 291

protected void pause() {


if (animator!= null) {
animator.pause() ;
}
}

protected static void swap (int a[] , inti , int j) {


int temp;
temp= a[i] ; a[i] = a[j]; a [j] = temp;
}

private Algorithm.Animator animator ;


}

The implementation of the SortAlgori thm class shows some interesting and
common variations of the Strategy design pattern. The first variation is that the
abstract strategy, SortAlgorithm, is an abstract class instead of an interface. Using
an abstract class for the abstract strategy allows some utility methods needed by
concrete strategy classes to be implemented in the abstract strategy class. In the
SortAlgori th.m class, two utility methods, pause () and swap (), are implemented.
The second variation is that the abstract strategy class SortAlgorithm contains a
reference to the client, the Algori th.mAnimator class. This reference allows the
abstract and concrete strategy classes to access the client's interface. In this example,
the reference to Algori th.mAnimator is used in the pause () method to delegate
the invocation of pause () .

The Concrete Algorithms


The algorithms to be animated are represented as concrete strategies. The Bubble-
SortAlgorith.m and Qui ckSortAlgori thm classes both extend SortAlgori th.m.
They define the bubble sort and the quick sort algorithms. respectively.

Figure 7.9 AlgorithmAnimator


algorithm()
Separating and en-
capsulating sorting
algorithms, using
the Strategy design
pattern.
I
Sort2
, . _ theAlgorithm
V SortA/gorithm
algorithm() . tor v
A
sort()
an1ma
? I
I
BubbleSortAlgorithm QuickSortAlgorithm
sort() sort()
■ Design by Abstraction

Concrete so~ ~oritbm: BubbleSortAlgorithm


public class BubbleSortAlgoritbm extends SortAlgorithm {
public void sort(int a[]) {
for (inti= a.length; --i >= O; ) {
for (int j = O; j < i; j++) {
if (a(j] > a[j+l]) {
s-wap(a, j, j+l);
}
pause();
}
}
}
public BubbleSortAlgorithm(AlgorithmAnimator animator) {
super(animator);
}
}

Concrete so~ ~tbm: QuickSort'Algo:rithm


public class QuickSortAlgorithm extends SortAlgorithm {
protected void qsort(int a[], int loO, int hiO) {
int lo= loO;
int hi= hiO;
int mid;
pause();
if (hiO > loO) {
mid= a[(loO + hiO) / 2];
-while (lo<= hi) {
-while ((lo< hiO) && (a[lo] < mid)) {
lo++;
}
-while ((hi> loO) && (a[hi] > mid)) {
hi--;
}
i f (lo <= hi) {
s-wap(a, lo, hi);
pause();
lo++;
hi--;
}
}
if (loO < hi) {
qsort(a, loO, hi);
}
if (lo< hiO) {
qsort(a, lo, hiO);
}
}
}

public void sort(int a[]) {


qsort(a, 0, a.length - 1);
}
7.4 Design Case Study-Animation of Sorting Algorithms ■ 293

public QuickSortAlgorithm(AlgorithmAnimator animator) {


super(animator);
}
}

Creating Instances of Concrete Algorithms


We utilize the concrete algorithms in a new version of sort animation-Sort2. The
question is, Where are instances of these concrete algorithms to be created? One
option is to create the instances in the ini tAnimator () method of Sort 2.
public class Sort2 extends AlgorithmAnimator {
protected SortAlgorithm theAlgorithm;
protected void initAnimator() {
algName = "BubbleSort";
String at = getParameter("alg");
if (at != null) {
algName = at;
}
if ("BubbleSort" .equals(algName)) {
theAlgorithm = new BubbleSortAlgorithm(this) ;
} else if ("QuickSort".equals(algName)) {
theAlgorithm = new QuickSortAlgorithm (this);
} else { // default algorithm
theAlgorithm = new BubbleSortAlgorithm(this);
}
setDelay(20);
scramble() ;
}

II other methods and fields . . .


}

Although this solution works, it leaves the client (the Sort2 class) of the Strategy
design pattern (the SortingAlgorithm class) to deal directly with the concrete
strategies (the BubbleSortAlgori thm and QuickSortAlgorithm classes). This
result negates one of the key advantages of using the Strategy design pattern-that
is, to make concrete strategies interchangeable by decoupling the clients from the
strategies. The client should be completely unaware of the concrete strategies.
A better alternative is to use a separate class whose sole respon ibility is to create
instances of concrete sorting algorithms. We call thi class a factory. U ing the factory,
the client (the Sort2 class) can be completely decoupled from the concrete sorting
algorithms.
The factory class can be implemented in everal different ways. To allow the
flexibility of using different factorie and to in ulate the client from future changes in
them, the Strategy design pattern once again can be applied to make different factories
interchangeable. The revised design of the orting algorithm animation is shown in
Figure 7.10. The abstract algorithm factory, AlgorithmFactory, is defined as an
interface. The contract of the hook method makeSortAlgori thm () is
Given tho name of a sorting algorithm, return an instance of a subclass of Sort
Algorithm that implement the named orting algorithm.
\ I
294 • Design by Abstraction

Figure 7.10 AlgorithmAnimator


algorithm()

r
A revised design
of sort animation,
using factories.

Sort2 A theAlgorithm SortAlgorithm


V A

algorithm() animator
V
sort()
0
algorithm Factory
I
1
I
BubbleSortAlgorithm QuickSortAlgorithm
sort() sort()
A
I
A
I

: Creates : Creates
I I
I

Algorithm Factory I _..., StaticAlgoFactory


makeSortA/gorithm() makeSortAlgorithm()

public interface AlgorithmFactory {


public SortAlgorithm makeSortAlgorithm(String algName) ;
}

A straightforward implementation of a concrete algorithm factory is the


StaticAlgoFactory class.

public class StaticAlgoFactory implements AlgorithmFactory {


public StaticAlgoFactory(AlgorithmAnimator animator) {
this .animator~ animator;
}

public SortAlgori thm makeSortAlgorithm(String algName) {


if ("BubbleSort".equals(algName)) {
return new BubbleSortAlgorithm(animator);
} else if ("QuickSort" .equals (algName)) {
return new QuickSortAlgorithm (animator);
} else {
return new BubbleSortAlgorithm(animat or);
}
}

protected AlgorithmAnimator animator;


}
7.4 Design Case Study-Animation of Sorting Algorithms • 295

This static algorithm factory is tightly coupled with concrete sorting algorithms.
Adding or changing concrete sorting algorithms requires changes to this factory.
However, using Java's dynamic class-loading capability, we can build a dynamic
sorting algorithm factory that is completely decoupled from the concrete sorting
algorithms; that is, adding or changing concrete sorting algorithms does not affect
the dynamic factory !5

The Revised Sort Animation


The revised sorting algorithm animation applet Sort2 is now completely decoupled
from the concrete sorting algorithms.

&llimatlon ~ Sort2
public class Sort2 extends AlgorithmAnimator {
protected SortAlgorith.m theAlgorith.m;
protected SortAlgorith.mFactory algorithmFactory;
protected void initAnimator() {
algName = "BubbleSort";
String at = getParameter ( "alg") ;
if (at != null) {
algName = at;
}
algorithmFactory • new StaticAlgoFactory(this);
theAlgorith.m = algorith.mFactory.makeSortAlgorith.m(algName) ;
setDelay(20);
scramble O ;
}

protected void algorithm() {


if (theAlgorith.m !• null)
theAlgorith.m .sort(arr) ;
}

protected void scramble() {


(The body is identical to that of scramble O in Sort on page _87)
}

protected void paintFrame(Graphics g) {


(The body is identical to that of paint Frame O in Sort on page 28
}

protected int arr[] i


protected String algName ;
}

5. An .unplementnuon
.
o1· a dyn,·1mic hctory
' is included in the 011/i11e S11pple111e111 to this book.
_,If

296 • Design by Abstraction

7 .4.3 Design Pattern: Factory

The use of factories to create concrete sorting algorithms illustrates another useful
design pattern- Factory.

Design Pattern Factory


Category: Creational design pattern.
Intent: To define an interface for creating objects but let subclasses decide which
class to instantiate and how.
Applicability: The Factory design pattern should be used when a system should
be independent of how its products are created.

The structure of the Factory design pattern is shown in the following diagram:

AbstractFactory
Product
makeProduct()

'
ConcreteFactory
makeProduct()
Creates
-------- - ConcreteProduct

The participants in the Factory design pattern are the following:


■ Product (e.g., SortAlgori thm), which defines an interface of the objects that
Lhe factory will create.
■ ConcreteProduct (e.g., QuickSortAlgori thm), which implements the Prod-
uct interface.
■ AbstractFactory (e.g., Algori thmFactory), which defines a factory method
(e.g., makeSortAlgorithm () ) that returns an object of type Product.
■ ConcreteFactory (e.g., StaticSortAlgori thmFactory), which overrides the
factory method to return an instance of ConcreteProduct.
The Factory design pattern is closely related to but distict from two other cre-
ational design patterns: Abstract Factory and Factory Method, which will be discussed
in Section 10.2.2 [p. 484] and Section 9.5.6 [p. 447], respectively.

7.4.4 Separating Display Strategies

If we examine the revised sorting algorithm animation applet Sort2 closely, we find
there is another loosely coupled component- the display strategy (i.e., the visual rep-
resentation) of the array. The display strategy should be independent of the algorithms
being animated and the animation mechanism. Changing the display strategy should

I
7.4 Design Case Study-Animation of Sorting Algorithms ■ 297

not affect the algorithms and the animation mechanism. The display strategy can be
decoupled from the animation mechanism and the algorithms by using the Strategy
and Factory design patterns so that different display strategies become interchange-
able. The revised design is shown in Figure 7.11.

Three Design Options


The key issue here is the design of the interface of the display strategy-the SortDis-
play interface, which represents an abstract display strategy. We start by designing
the interface.

public interface SortDisplay {


public void display(int a[], Graphics g, Dimension d) ;
}

The interface defines a single method for displaying the array. The parameters
of the method are necessary for displaying the array. This solution is rather straight-
forward, and the interface is small, resulting in low coupling. The change needed
in the main class Sort3 is for the paintFrame O method to delegate the painting
responsibility to the display strategy.
public class Sort3 extends Sort2 {
protected SortDisplay theDisplay;
public void paintFrame(Graphics g) {
theDisplay.display(arr, g, getSize () ) ;
}
II . ..
}

This solution works, but it is inflexible because there is a subtle coupling between
the display strategy and the display size. In the preceding implementation, the ize of
the array is determined in the scramble() method of the Sort class based on the
height of the viewing area. As a result, the scramble () method is tightly coupled to
the displaying scheme.

Figure 7.11 HSortDisplay


Sort2 I
I
I
Encapsulating the I
I
display strategies, VSortDisplay - - -,I
I
using the Strategy the Display I
Sort3 SortDisplay I
design pattern. I
I
BSortDisplay - -i
I
displayFactory I
I
I
I

BarSortDlsplay - - .J
I
SortDisplayFactory I
I
I

......----~-:--~:--:=:-:--7
StaticSortDisplayFactory ______ g!~~~~-------------------J

298 • Design by Abstract/on

The second design option is to move the scramble O method to the SortDis-
play interface so that tightly coupled functionalities are in the same class.
public interface SortDisplay {
public void scramble(int a[], Dimension d);
public void display(int a[], Graphics g, Dimension d);
}

The new scramble O method of Sort3 will now also delegate the responsibility
of initializing the array to the display strategy.
public class Sort3 extends Sort2 {
protected SortDisplay theDisplay;
public void scramble() {
theDisplay . scramble(arr, getSize());
}

public void paintFrame(Graphics g) {


theDisplay.display(arr, g, getSize());
}
II . . .
}

Now, coupling between the display strategy and its client is reduced. However,
SortDisplay is not very cohesive, as initializing the array and scrambling it have
nothing to do with displaying it. These are basically independent functionalities,
except that they both must agree on the size of the array. So, the third and best
design option is to move scramble O's functionality back into the Sort3 class.
We introduce a new method, getArraySize O , in the SortDisplay interface to
capture the interdependency among these methods.

import java.avt.*;
public interface SortDisplay {
public int getArraySize(Dimension d);
public void display(int a[], Graphics g, Dimension d);
}

The design of the SortDisplay interface illustrates the following design guide-
line:

Design Guideline Minimize the Interface


Design the smallest possible interface that provides the needed functionality. Interface
size is determined by the number of methods and their parameters. Large interfaces
usually indicate high levels of coupling with collaborating classes and high com-
plexity.
7.4 Design Case Study-Animation of Sorting Algorithms ■ 299

Concrete Display Strategies


The original display strategy that uses 1-pixel horizontal lines can be reimplemented
as a concrete strategy HSortDisplay that implements the SortDisplay interface.

. ISortJ>i~la
import java.awt . •;
public class HSortDisplay implements SortDi splay {
public int getArraySize (Dimension d) {
return d .height / 2;
}

public void display(int aO, Graphi cs g, Dimension d) {


double f = d .width / (double) a . length ;
g . setColor(Color . white);
g.fillRect(0 , 0, d.width , d .height);
int y = d .height - 1;
g . setColor(Color .black) ;
y = d .height - 1;
for (inti= a . length ; - -i >= 0; y - = 2)
g .drawLine (O, y, (int)(a [i] • f ) , y);
}
}

The revised design allows us to introduce new display strategies easily. 1\vo
variations of the original display strategy are implemented:
■ VSortDisplay: Use vertical lines, with all lines flush against the top edge and
extending downward, as shown in Figure 7.12(a).
■ BSortDisplay: Use vertical lines, with all lines flush against the bottom edge
and extending upward, as shown in Figure 7. l 2(b).
The implementations follow :

Concrete
impor t java . awt.•;
public class VSortDi splay implements SortDispl ay {
public i nt getArr aySi ze (Dimension d) {
retur n ct .wi dt h / 2;
}

public voi d display(int a[), Graphics g, Dimension d) {


g . setColor (Color .white);
g . f i l l Rect (0, 0, ct .wi dth, cl .height) ;
i nt x = d . wi dth - 1;
double f = d .height / (double) a.length ;
g.setColor(Color. black);
for (inti= a .length ; - - i >z 0; x -= 2)
g . drawLine(x, 0, x, (int)(a[i) * f));
}
}
. . ---- ----- ... -

300 • Design by Abstraction

Figure 7.12

Screen shots of
Sort3 showing
various display
strategies.

1'1 --------.--l'
. Applet started. Applet started.

(a) (b)

1MfflQfMWM s I□Ix 1
Apple_t _ _ _ _ _ _ _--:

j Applet started.
(c)

: BSortDisP,la
import java.awt.•;
public class BSortDisplay implements SortDisplay {
public int getArraySize(Dimension d) {
return d.width / 2;
}

public void display(int a[], Graphics g, Dimension d) {


g.setColor(Color.white);
g.fillRect(O, Q, d.width, d .height);
double f = d.height / (double) a.length;
int x • d.width - 1;
g.setColor(Color.black);
for (inti• a.length; - -i >= O; x -= 2)
g.drawLine(x, d.height, x, d .height - (int)(a(i] * f));
}
}

A more interesting display strategy is the BarSortDisplay, shown in Fig-


ure 7.12(c). The vertical lines are 3 pixels wide, the heights of the lines are proportional
) II 1 1

7.4 Design Case Study-Animation of Sorting Algorithms ■ 301

to the numbers they represent, and different colors are used to represent different
numbers.

public class BarSortDisplay implements SortDisplay {


public int getArraySize(Dimension d) {
return d.width / 3;
}
public void display(int a[], Graphics g, Dimension d) {
g . setColor(Color.white);
g.fillRect(0, 0, d.width, d .height);
double f = d.height / (double) a . length;
double cf= 255 . 0 / (double) a.length ;
int x = d.width - 3;
for (inti= a.length ; --i >= 0; x -= 3) {
g.setColor(new Color((int)(a[i]•cf/1.5) , (int)(a[i]•cf), 0)) ;
g.fillRect(x, d.height - (int)(a[i)•f) , 3, (int)(a[i]•f));
}
}
}

Display Factory
The instances of concrete display strategies are also created by a factory. The structure
is similar to the algorithm factories. Implementation of a simple static factory is as
follows:

Abstract ~lay facto : SortDiaplay:Facto!J:


public interface SortDisplayFactory {
SortDisplay makeSortDisplay (String name) ;
}

Concrete dis Y. facto~: Static-Sortl>iapl


public class StaticSortDisplayFactory
implements SortDisplayFactory {
public SortDisplay makeSortDi splay (String name) {
i f ("horizontal" . equals (name))
return new HSortDisplay( );
else if ("vertical". equals (name))
return new VSortDisplay ();
else if ("bottom" . equals(name))
return new BSortDisplay () ;
else if ("bar" .equals(name) )
return new BarSortDisplay();
else
return new HSortDisplay O;
}
}
,.

302 • Design by Abstraction

Putting All the Pieces Together


So~ al rithm animation a let: Sort3
import java . awt . •;
public class Sort3 extends Sort2 {
protected SortDisplay theDisplay ;
protected SortDisplayFactory displayFactory ;
protected voi d initAnimator () {
String att • getParameter("dis ") i
displayFactory • new StaticSortDisplayFactory ( ) ;
theDisplay • displayFactory.makeSortDi splay(att) ;
super . initAnimator();
}

protected voi d scramble () {


int n • theDisplay . getArraySize(getSize());
arr= new int [n] ;
for ( inti= arr . length; - -i >= O; )
arr[i ] = i;
for (inti= arr.length ; --i >= 0;) {
int j = ( int ) ( i * Math.random ()) ;
SortAlgorithm . swap ( arr, i, j );
}
}

protected v oi d paintFrame(Graphics g) {
theDisplay.display(arr, g, getSize());
}
}

CHAPTER SUMMARY

■ Design patterns are schematic descriptions of solutions to recurring problems


in software design. Each pattern represents a generic (i.e., reusable) solution to
a recurring problem. Only a relatively small number of patterns are needed to
capture the essence of the design process, and they can be adapted and combined
in countless ways to generate endless possibilities.
■ Generic components are program components, usually classes and packages, that
can be adapted and used in many different contexts without modification o f the
source code. Generic components are also known as reusable components.
■ Refactoring is used to identify recurring, identical, or nearly ide ntical code
segments and to restructure a program so that the recurring code segme nts are
captured as a generic component that is defined once and is usable in m any
different contexts. Refactoring can be accomplished by inheritance or delegation.
■ Generalization is a process that takes a solution to a speci fic problem and re lruc-
tures it so that it not only solves the original proble m but also solves a category
Chapter Summary ■ 303

of problems that are similar to the original problem. Generalization is often ac-
complished by using abstract classes or interfaces and design patterns, such as
Template Method and Strategy.
• An abstract method is a method whose implementation is deferred to its sub-
classes. Abstract methods are also known as deferred methods. An abstract class
is a class that includes or inherits at least one abstract method.
■ The Template Method design pattern defines the skeleton of an algorithm in a
method, deferring some steps to subclasses and thus allowing the subclasses
to redefine certain steps of the algorithm. The Template Method design pattern
should be used to implement the invariant parts of an algorithm or to refactor and
localize common behavior among subclasses.
■ The Strategy design pattern defines a family of algorithms, encapsulates each
one, and makes them interchangeable. The Strategy design pattern should be
used when many related classes differ only in their behavior or when different
variants of an algorithm are needed.
■ The Factory design pattern defines an interface for creating objects but lets
subclasses decide which class to instantiate and how. The Factory design pattern
should be used when a system should be independent of how its products are
created.
• Important design guidelines include the following:
Re/actor recurring code segments: Recurring code segments based on the
same logic are hazardous to maintenance. They should be refactored so
that the code segment occurs only once. Other occurrences of the code
segment should be replaced with references to the common code.
Maximize extensibility: Rarely can components be reused without adapta-
tion. Extensibility allows components to be extended and adapted to
different contexts. The more extensible a component i , the better the
chance that it may be reused.
Prevent misuses by clients: Well-designed cla se hould prevent po sible
misuses by making violations of the convention of u ing the classes
compilation errors, or using as ertions to detect the violations at run
time (i.e., defensive progranuning).
Program to an inte,face, not to an implementation: Separate interface from
implementation. Client of a clas hould acce only the functionalities
of the clas via its interface. Implementation hould be hidden and
irrelevant to the client.
Separate functionalities that address differeflt concerns: If a cla
contains
components that addre di fferent concern , these components are
candidates for eparation from the original clas .
Minimize the inte1face: De ign the malle t po ible interface that provides
the needed functionality. Interface ize i detennined by the number of
method and their paran1eter . Large interface u ually indicate high
level of coupling with collaborating classes and high complexity.
304 • Design by Abstraction

FURTHER READINGS

Gamma, E., et al. (1995). Design Patterns-Elements of Reusable Object-Oriented


Software. Addison-Wesley.
Metsker, S. J. (2002). Design Patterns Java Workbook. Addison-Wesley.
Shalloway, A., and J. R. Trott (2001). Design Patterns Explained: A New Perspective
011 Object-Oriented Design. Addison-Wesley.

EXERCISES

7.1 Extend the generic function plotter class class. The balls should bounce off each other
Plotter to plot the following functions: when they collide. Bonus: Allow the u ser to add
more balls.
(a) Y = .fi
(b) y = lnx 7.6 Enhance the latest version of the animation
applet Sort3 by adding the following capabil-
(c) y = tan x ities:
(d) y 3x 2 + 5x + 8
= x3 - (a) New sorting algorithms, such as insertion
7.2 Write a generic plotter applet that takes a pa- sort.
ramet.er func that represents a single-variable
(b) New display strategies.
polynomial function and plots the function. For
example, the input might be in the form 7.7 Refactor the bouncing ball applet in Exam-
ple 7 .2 to separate the ball object from the
animator. Extend the applet so that the object
being bounced around can be any shape or
7.3 Extend the generic multiple function plotter
image.
MultiPlotter to plot the following functions
in the same two-dimensional canvas:
(a) y = 5x
(b) y = 2x2 PROJECT
(c) y = 0.5x 3
(d) y = 0.2ex 7.1 Develop animation applets to animate graph
(e) y = ln x algorithms. Candidates include the following:
7.4 Write an applet that combines the applets in (a) Kruskal's and Prim's algorithms for min-
Exercises 5.7 and 5.8. It should support two imum spanning trees [Thomas, Corman,
display stra1.egies: a bar chart or a pie chart. and Rivest 1990). Animate the process of
The user should be able to choose either of the adding edges to the minimum spanning
two display strategies by setting a parameter. tree.
The display strategies are to be implemented (b) Dijkstra's algorithm for single- ource
by using the Strategy design pattern. shortest path [Thomas, Corman, and Rivest
7.5 Write an animated applet with two bouncing 1990]. Animate the process of discovering
balls by extending the DBAnimationApplet the edges on the shortest path.
•• ➔

Object-Oriented Application
Frameworks

CHAPTER OVERVIEW
In this chapter, we discuss the design of application frameworks and the use of design
patterns. We present three important frameworks in the Java Class Lib@ry- the collections
framework, the input/output framework, and the g@phical user interface (GUI) framework
using AWT and Swing.

8.1 APPLICATION FRAMEWORKS


··•
An object-oriented application framework. or Jrame1vork for hort, i. a et of co-
operating clas e that repre ent reusable de ign of oftware y tems in a particular
application domain. An application framework typically con i t · of a -et of ab tract
cla es and interface that are part' of emicomplete application that can be pe-
cialized to produce custom applications. An application framework often pre cribes
a set of conventions for extending the ab tra t cla e . implementing the interfaces,
and aJlowinoo their instances to interact with one anolher. The main goal of applica-
tion framework. is to support the reuse of de ign and implementation in particular
application domains and thereby greatly implify them.
305
--
f !

306 • Object-Oriented Application Frameworks

The generic animation applet classes that we developed in Chapter 7 (Anima-


tionApplet and DBAnimationApplet) can be considered as mini-frameworks.
They exhibit some of the characteristics of application frameworks in that they are
reusable and extensible, they are semicomplete programs, and the reusable desig_n a nd
implementation is captured in abstract classes with hook methods to be overridden
by the subclasses.
Real application frameworks are of much larger scale. Some examples are graph-
ical user interface (GUl) frameworks such as the Abstract Windows Toolkit (AWT)
and Swing portion of the Java Foundation Classes (JFC), and the Microsoft Founda-
tion Classes (MFC) for C++; and distributed computing frameworks such as the Java
Remote Method Invocation (RMI) and the Common Object Request Broker Archi-
tecture (CORBA).

8.1 .1 Characteristics

Key characteristics of application frameworks are discussed under the following


headings.

Extensibility
An application framework typically consists of a set of abstract classes and interfaces
to be extended and specialized. Its changeable aspects, also known as the hot spots of
the framework. are often represented as book methods. Custom applications use the
framework by extending or implementing the classes and interfaces in the framework
and by overriding the book methods to provide customized behaviors.
For example, a concrete animation applet, such as BouncingBall2, simply
extends the DBAnimationApplet class and overrides the hook methods paint-
Frame () and ini tAnimator O .

Inversion of Control
When we use a conventionaJ library of routines and classes, the applications usually
control the flow of execution. In other words, the applications are acting as the masters,
whereas the routines and classes in the libraries are acting as servants to provide
services. When we use an application framework, the control of the flow of execution
often resides in the framework, not in the applications. In other words, the framework
is acting as the master, whereas the applications are acting as servants to fill in the hot
spots.
For example, with the LinkedList class, the applications are in control, and the
LinkedList class simply provides services. That approach is quite different from
using the Applet class, which can be considered a mini-framework. The control
resides in the Applet .class and the applet context. A specific applet extends the
Applet class and ovemdes the hook methods, ini t (), start (), and the like, which
represent the hot spots of the applet framework.
ti

8.1 Application Frameworks • 307

Design Patterns as Building Blocks


Although both design patterns and frameworks are mechanisms used to capture
reusable designs, they are quite different. On the one hand, design patterns are
schematic descriptions of reusable designs that are not concrete programs and that
are language independent. On the other hand, frameworks are compilable programs
written in a specific programming language and often contain abstract classes and
interfaces. Design patterns are the architectural building blocks of application frame-
works. They help make application frameworks extensible and reusable. Frameworks
usually contain implementations of many cooperating design patterns. For example,
the design patterns Template Method and Strategy are used in almost all application
frameworks.

8.1.2 Design Requirements

Designing application frameworks is more challenging than designing specific ap-


plications. A designer of application frameworks must foresee the potential changes
and the variabilities required by potential applications that may use the frameworks.
Booch [ 1994] bas suggested the following design requirements of application frame-
works.

Completeness
The framework must provide a family of classes, united by a shared interface but each
employing a different representation. Developers can then select the classes having
the time and space semantics most appropriate to their given application.

Adaptability
All platform-specific aspects of the framework must be clearly identified and isolated.
Doing so permits local substitutions to be made.

Efficiency
Components must be easily assembled (efficient in tern1S of compilation re ources).
They must impose minimal run-time and memory overhead (efficient in execution
resources). And they must be more reliable than hand-built mechanisms (efficient in
developer resources).

Safety
Each abstraction must be type-safe o that latic a umptions about the behavior of
a class may be enforced by the compilation ys1em. Exception should be used to
signify nm-time violations of the contracl concerning the dynamic ernanlic of the
class. Rai ing an exception mu I nol corrupt lhe tate of lhe object that threw lhe
exception.
--

308 • Object-Oriented Application Frameworks

Simplicity
The framework must have a clear and consistent organization that makes it easy to
identify and select appropriate concrete classes.

Extensibility
Developers must be able to add new classes independently. At the same time they
must be able to preserve the architectural integrity of the framework.

8.1.3 Specific Frameworks Considered

To use application frameworks effectively, you must understand the interfaces, con-
ventions, and restrictions of a framework. In return, using well-designed frameworks
can significantly simplify the design and implementation of applications. The Java 2
Platform class library includes several well-designed and powerful application frame-
works. In the remainder of this chapter, we discuss the interfaces, conventions, and
designs of th~ following Java application frameworks:

The collections framework: Collections are also known as containers.1 The


collections framework is a set of interfaces and classes that support storing
and retrieving objects in collections of varying data structures, algorithms,
and time-space complexities.
The graphical user interfaces framework: The graphical user interfaces frame-
work consists of the Abstract Windows Toolkit (AWT) and the Swing
portion of the Java Foundation Classes. It is a set of interfaces and classes
that support the construction of graphical user interfaces with a great deal
of versatility.
The input/output framework: The input/output framework is a set of interfaces
and classes that support the input and output of different types of objects to
and from different media with varying capabilities.

8.1 THE COLLECTIONS FRAMEWORK

A collection is an object that contains other objects, which are called the elements of
the collection. Based on their structures and capabilities, collections can be classified
in a few major categories known as abstract collections.

I. Not to be confused with the Container class in AWT. They are unrelated .
8.2 The Collections Framework • 309

s. 2.1 Abstract Collections

There are four major types of collections: bags, sets, lists, and maps.

Bags
A bag is an unordered collection of elements that may contain duplicate elements.
Bags are also known as multi'sets. Bags are the least restrictive and most general
fonn of collections. In the Java collections framework, bags are represented by
the Collection interface. Bags are rarely used directly. More restrictive forms of
collections, such as sets and lists, are used much more often.

Sets
A set is an unordered collection of elements. No duplicate elements are allowed in
sets. In other words, inserting the same elements in a set twice is the same as inserting
the element just once. A set is denoted

where e 1, e2, ••• , en are the elements of the set. For example, the following is a set of
languages:
{ "English", "Chinese", "German" }

A variation of sets is sorted sets, whose elements are automatically sorted ac-
cording to a certain order. Sorted sets are also known as ordered sets. For example,
the following is an ordered set of languages sorted in alphabetical order:

{ "Chinese", "English", "Germani' }

In the Java collections framework, sets are represented by the Set interface, and
sorted sets are represented by the SortedSet interface.

Lists
A list is an ordered collection of elements. Lists are also known a sequences.
Elements in a list are indexed sequentially starting from 0. Duplicate elements are
allowed in lists. A list is denoted

where ei, e2 , ... , e,, are the elements of the list. For example, the following are three
different lists:

( "Chinese", "German", "English" )


( "English", "Chinese", "German" )
( "English", "Chinese" , "English'\ "German" )

In the Java collections framework, list are repre ented by the List interface.
31 0 • Object-Oriented Application Frameworks

Maps
A map is an unordered collection of key-value pairs, denoted key 1-+ value. Map are
aJso known as functions, dictionaries, or associative arrays. The keys in a map muS t
be unique (i.e., each key can map to al most one value). A map is denoted

where ki, k2 , . .. , k,, are the keys and v 1, v2 , . . . , v11 are the values of the map. For
example, the following is a very small EngHsh-Chinese dictionary:

{"welcome" ~ "J;!:i!!" , " software" ~ 11


-it i!f" , " coffee"~" ,JD"-1~" }

A variation of maps is sorted maps, whose elements are automatically sorted by keys
according to a certain order. Sorted maps are also known as ordered maps. In the Java
collections framework, maps are represented by the Map interface, and sorted maps
are represented by the Sorted.Map interface.

8.2.2 Interfaces of Collections

The abstract collections are represented by a set of interfaces, as shown in Figure 8. 1.


The implementations of these collections are defined as classes that implement the
abstract collection interfaces. The following design guideline is important to the
design of the Java collections framework:

Design Guideline Maximize the Uniformity of Common Aspects


of Related Classes/Interfaces
The common aspects of related classes should be handled in a uniform way. These
common aspects are usually captured as interfaces or abstract classes. The greater the
uniformity, the simpler and more useful is the design.

Figure 8.1
Collection Map
The abstract
collections.

Set List SortedMap

SortedSet
8.2 The Collections Framework • 311

Although each kind of abstract collection has its unique characteristics, together
they have a lot in common. For example, all collections support insertion and deletion
of elements. It is simpler if the insertion and deletion of different collections can
be expressed by using the same methods and following the same conventions. More
important, different implementations of an abstract collection will all implement the
same interface. Thus, knowing the interface of an abstract collection is adequate for
use of all the implementations of an abstract collection. For example, the Set interface
can be implemented by using an array, a linked list, a tree, and so on. A user needs
to study only the Set interface in order to use all its implementations, and switching
from one implementation to another is easy. However, we often need to know about the
concrete implementation classes so that we c~ make informed decisions on the most
suitable implementation, depending on a number of factors, such as performance,
memory consumption, thread safety, and so on.

The Collection Interface


The Collection interface represents a bag. The methods of the Collection inter-
face are summarized in Table 8.1. In these methods, parameter o is of type Object
and parameter c is of type Collection.

TABLE 1.1
Methods of Interface Collection

Method Description

add(o) Adds a new element o to this collection


addAll(c) Adds all the elements in the collection c to this collection
clear() Removes all the elements from thi collection
contains (o) Returns true if this collection contains an element that equal o
containsAll(c) Returns true if this collection contains all the element in the
collection c
isEmptyO Returns true if this collection contains no element
iterator() Returns an iterator over the element in thi collection ( ee Section
8.2.4 [p. 3 19])
remove(o) Removes an element that equal o from thi collection if uch an
element ex.i t
removeAll(c ) Remove from thi collection all the element that are contained in
the collection c
ret ainAll ( c ) Retain, only the elements in thi collection that are contained in
the collection c, and remove all other elements
size () Returns the number of element in thi collection
\\
31 2 • Object-Oriented Application Frameworks

The elements of a collection can be any objects (i.e., the elements can be 0 ~ any
reference type). Values of primitive types cannot be directly stored in collec~ions.
To be stored in a collection, the values of primitive types must be wrapped 111 an
object of a suitable wrapper class (see Section 4.4.9). In the methods contains C) •
containsAll (c) , remove (o) , removeAll (c), and retainAll(c) , the notion
of equality between two elements is defined by the equals() method of the class of
the elements.

The Set Interface


The Set interface extends the Collection interface and represents a set. All the
methods of the Collection interface are inherited, so the Set interface introduces no
new methods. However, some of the methods have different semantics, or contracts,
than those in the Collection interface, owing to the restriction of sets. The methods
with altered semantics in the Set interface are summarized in Table 8.2. In these
methods, parameter o is of type Object and parameter c is of type Collection.
Here is an example of using sets:

Set set = new HashSet () ; II HashSet is the class that implements Set
set. add("foo "); II the set is { foo}
set. add (" bar") ; // the set is { foo, bar}
int n = set . size() ; JI n is 2
if (set.contains("foo")) //istrue
System .out .println("foo is in");

The SortedSet interface further extends the Set interface. We discuss it in


Section 8.2.5 [p. 325].

The List Interface


The List interface also extends the Collection interface, and the List interface
introduces some new methods and also alters the semantics of many of the methods
inherited from the Collection interface because of the ordering imposed by lists.

Methods of Interface Set

Method Description

add(o) Adds the element o to this set if it is not already present


addAll(c) Adds all the elements in the collection c to this set if they are not already
present
8.2 The Collections Framework • 31 3

The new methods and the methods with altered semantics in the List interface are
summarized in Table 8.3. In these methods, parameter o is of type Object, parameter
c is of type Collection, and parameters i and j are integers representing indices.

The Hap Interface


The Map interface represents a map. Although maps are conceptually sets of key-
value pairs and are often implemented as sets, the Map interface does not extend the
Collection interface; nor does it extend the Set interface. Maps are more restrictive
than sets because maps contain only key-value pairs and do not allow duplicate keys.
Therefore, some of the methods in the Set and Collection interfaces cannot be

TABLE e·.3
Methods of Interface List

Method Description

add(i, o) Inserts the element o at the i -th position in this list


add(o) Appends the element o to the end of this list
addAll(c) Appends all the elements in the collection c to the end of this
list, in the order that they are returned by the iterator of c
addAll ( i, c) Inserts all the elements in the collection c into this list, starting
at the i-th position
get(i) Returns the element at the i -th position in this li t
indexOf(o) Returns the index in this list of the first occurrence of the element
o, or -1 if the element is not pre ent in this list
lastindexOf(o) Returns the index in this list of the last occurrence of the element
o, or -1 if the element is not pre ent in this li t
listiterator () Returns a Listiterator of the element in thi li t ( ee
Section 8.2.4 [p. 319])
listiterator(i) Returns a Listiterator of the element in thi Ii t, staning
from the i-th position (see Section 8.2.4 [p. 3 19))
remove(i) Removes the element at the i -th position in this list and return
the removed element
remove(o) Removes the first occurrence of the element o in thi Ii t
set(i, o) Replace the element at the i -th po ition in this list with the
element o, and return the element that was replaced
sublist (i, j) Relums a ubli t of thi Ii t from the i -th position, inclusive, to
the j -th position, exclu ive
314 • Object-Oriented Application Frameworks

Methods of Interface Hap

Method Description

clear O Removes all entries from this map


containsKey(k) Returns true if this map contains an entry for the key k
contains Value ( v) Returns true if this map contains one or more entries with the
value v
entrySetO Returns the entry set view of this map
get(k) Returns the value to which this map maps the key k
isEmptyO Returns true if this map contains no entries (i.e., key-value
pairs)
keySet() Returns the key set view of this map
put(k, v) Maps the key k to the value v in this map
putAll(m) Copies all the entries from map m to this map
remove(k) Removes the entry for key k from this map if present
size() Returns the number of entries (i.e., key-value pairs) in this map
values() Returns the value collection view of this map

supported by maps. Maps are not sets, but they can be viewed as sets if so desired.
The Map interface provides the following views of maps:

■ The entry set view: The set of all the entries (i.e., key-value pairs) of the map.
■ The key set view: The set of all the keys contained in the map. It is a set because
keys are unique.
■ The value collection view: The collection of all the values contained in the map.
It is a collection rather than a set because different keys can be mapped to the
same value, in which case the value occurs multiple times in the value collection.

The methods of the Map interface are summarized in Table 8.4. l n these methods,
parameterk is of type Object and represents a key, parameterv is also of type Object
and represents a value, and parameter mis of type Map.
The keys and values of a map can be of any reference type. Here is an example
of using maps:

Map map= new HashMap(); // HashMap is class that implements Map


map.put("a", "X"); // the map Is {a-> X}
map . put("b", "Y"); // the map is { a -> X, b -> y }
map . put("c", "X"); // the map Is { a -> X, b -> Y, c -> X }
8.2 The Collections Framework ■ 315

map.put( 11 a 11 , 11 Z11 ) ; //themapis{a->Z, b->Y, c->X}


Object val = map.get( 11 a 11 ) ; // val Is •z•
map . remove ( 11 a 11 ) ; / / the map is { b -> Y, c -> X }

The Sorted.Map interface extends the Map interface. We discuss it in Sec-


tion 8.2.5 [p. 324].

s.2.3 Implementations of Collections

Each of the abstract collections can be implemented with various data structures
and algorithms. An implementation of the abstract collections is called a concrete
collection. The implementations may vary in many respects, including the following:
■ Whether the collection is bounded (i.e., has a fixed maximum size) or is un-
bounded (i.e., may grow and shrink dynamically as needed).
■ The time and space complexity of various operations, such as searching, inser-
tion, deletion, and iteration.
Common data structures used to implement the abstract collections include arrays,
linked lists, trees, and bash tables.
Each concrete collection is a class that implements the interface of an abstract
collection. This characteristic makes different implementations of the same abstract
collection interchangeable. For example, an array implementation of a list can be
switched to a linked list implementation without affecting the clients. The concrete
collectlons supported by the collections framework are summarized in Table 8.5. All
these concrete collections support unbounded collections. The collections can grow
and shrink dynamically as needed.

Choosing Implementations of Collections


Implementations for Set Three different implementations of the Set interface are
provided:

• The HashSet implementation stores the elements in a hash table. The HashSet
implementation is very efficient in insertion (add () ) and membership checking
(contains()). The time complexity of these operations i 0 ( 1). However,
the HashSet implementation doe not maintain any particular order among
the elements. The iteration order i unpredictable and determined by the hash
function of the elements.
■ The LinkedHashSet implementation i very imilar to the HashSet implemen-
tation, except the iteration order i predictable and u ually is the in ertion order.
■ The TreeSet implementation tore the element in a red-black tree, a form of
balanced binary tree. The TreeSet cla implement the SortedSet interface.
The elements are ordered ba ed on either their natural order or a u er-defined
order (see Section 8.2.5 [p. 324]). The TreeSet implementation i le efficient
than the HashSet implementation in in ertion and member-hip checking. The
time complexity of the e operation i O (log " ), where " i the ize of the set.
31 6 • Object-Oriented Application Frameworks

TAILE 1.5
Concrete Collections

Concrete Interface Data Structure/


Collection Implemented Description

HashSet Set Hash table


LinkedHashSet Set Hash table and doubly linked list
Ensures predictable iteration order
TreeSet SortedSet Balanced binary tree
Ensures elements are in ascending order
ArrayList List Resizable array
LinkedList List Doubly linked list
Vector List Resizable array
Supports legacy methods that are available since
JDK 1.0

HashMap Map Hash table


Identi tyHashMap Map Hash table
Comparison on keys is based on identity not
equality
LinkedHashMap Map Hash table
Ensures predictable iteration order
TreeMap SortedMap Balanced binary tree
Ensures entries are in ascending key order
Hashtable Map Hash table
Supports legacy methods that are available since
JDK 1.0

When choosing a concrete set implementation, if the elements should maintain a


certain order, then the TreeSet should be used. Otherwise, HashSet should be used
for the sake of efficiency.
The HashSet implementation requires that the equals () and hashCode ()
methods be defined properly in the class of the elements.

Implementations for List Two different implementations of the List interface


are provided. The ArrayList implementation uses an array to store the elements, '
8.2 The Collections Framework • 31 7

whereas LinkedList implementation uses a doubly linked list to store the elements.
The main differences are as follows:
■ The ArrayList uses an array that is large enough to store all the elements. Often,
a large portion of the array is unused. The LinkedList wastes no space.
■ The ArrayList incurs a significant penalty of performance when the size of
the list exceeds the size of the array, in which case a new larger array must be
allocated and all the elements copied to the new array. The LinkedList incurs
no extra penalty on performance when the list grows.
■ The ArrayList is more efficient than the LinkedLi st for methods involving
indices, such as get O and set O methods. The time complexity of these oper-
ations is 0(1) for ArrayList and O (n) for LinkedList, where n is the size
of the list.
The Vector implementation is the same as the ArrayList implementation.
The main difference is that the Vector class supports additional "legacy methods"
that predate the collection framework. The Vector class is also thread-safe; i.e., its
objects are always well behaved even in the presence of multiple threads manipulat-
ing the same object (see discussion in Chapter 11). As a consequence, the Vector
implementation incurs a significant overhead in performance. When you are choos-
ing a concrete list implementation, the volatility of the list is the main factor. The
LinkedList implementation is more suitable for volatile lists (i.e., lists that grow
and shrink a lot). The ArrayList is more efficient for relatively stable lists.

Implementations for Map Four different implementations of the Map interface are
provided:
■ The HashMap implementation stores the entries in a bash table. The HashMap
implementation is very efficient in insertion (put() ) and mapping (get () ). The
time complexity of these operations is 0 (1). However, the HashSet implemen-
tation does not maintain any particular order among the entrie . The iteration
order is unpredictable and determined by the hash function of the key .
■ The LinkedHashMap implementation i very similar to the HashMap implemen-
tation, except the iteration order is predictable and usually is the in ertion order.
■ The Identi tyHashMap implementation is al o very imilar to the HashMap
implementation, except that the compari on on key is ba ed on reference identity
not object equality. This fact makes the implementation more efficient than the
HashMap implementation, when it i appropriate to u e identity compari on
instead of equality comparison.
■ The TreeMap implementation tore the entrie in a red-black tree. The TreeMap
class implements the SortedMap interface. The entrie are ordered based on
either the natural order or a u er-defined order of the key ( ee Section 8.2.5
[p. 324]). The TreeMap implementation i le s efficient than the HashMap im-
plementation insertion and mapping. The time complexity of these operations is
O(log 11), where II is the ize of the et.
--

318 • Object-Oriented Application Frameworks

When choosing a concrete map implementation, if the entries should maintai? a


certain order according to their keys, then the TreeMap should be used. Otherwi_se,
the HashMap should be used for the sake of efficiency. The HashMap implementation
requires that the equals() and hashCode () methods be defined properly in the
class of the keys.
The Hashtable class is implemented the same way as the HashMap class. The
main difference is that the Hashtable class supports additional "legacy methods"
that predate the collection framework. Similar to the Vector class, the Hashtable
class is also thread-safe. As a consequence, the Hashtable implementation incurs a
significant overhead in performance.

EXAMPLES . 1 Count the Number of Different Words

PURPOSE
This example demonstrates the use of a set.

DESCRIPTION
This program reads a piece of text as input. It counts the total number of words and
the number of different words in the text input.

SOLUTION
This example is an extension of the program Words in Example4.7 [p. 127]. In Words,
we break the text input into words. In this example, we first add a simple counter to
count the total number of words in the text input. If a word occurs multiple times,
it will be counted multiple times. To count the number of different words in the text
input, we must remember the words that have already occurred. A set serves this
purpose well. We use a HashSet to store the different words in the text input. Each
word occurs only once in the set even if it occurs multiple times in the input text.

CIIIIII Ccnm.tVords
import java.util . •;
import java. io . •;
public class CountWords {
static public void main(String[] args) {
Set words ~ new HashSet();
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
String delim • " \t\n.,: ;? 1- /()[]\"\ ' ";
Stri ng l i ne ;
int count = O;
try {
while ((line= in . readLi ne()) != nul l ) {
St ringTokenizer st= new Str i ngTokenizer( l i ne , delim) ;
~ -

8.2 The Collections Framework • 319

while (st.hasMoreTokens()) {
count++;
words.add(st.nextToken().toLowerCase() );
}
}
} catch (I0Exception e) {
e.printStackTrace();
}
System.out.println( 11 Total number of words: 11 + count);
System.out.println( 11 Number of different words: 11 +
words. size()) ;
}
}

Using President Lincoln's Gettysburg Address as the input, we have the following
output:

Total number of words: 270


Number of different words: 140

8.2.4 Iterators of Collections

The Iterator pattern is used to provide a uniform way to iterate through different
concrete collections (i.e., polymorphic iteration). Two iterator interfaces are defined:
Iterator and Listiterator. Instances of Iterator suppon traversal of collec-
tions in one direction (forward), and instances of Listiterator suppon traversal
of lists in forward and backward directions. The methods of the Iterator interface
are summarized in Table 8.6.

Methods of Interface Iterator

Method Description

hasNext () Returns true if the iteration has more elements


next() Returns the next element in the iteration
remove () Removes the last element returned by the iterator from the underlying
collection
·--,

320 ■ Object-Oriented Application Frameworks

The return type of next() is Object. Usually, the elements returned by th e


iterator must be downcast to their actual classes before they can be manipulated. Here
is an example of iterating through a set:

Iterator iter = set . iterator () ;


while (iter . hasNext ()) {
II assume the elements are strings
Strings= (String) iter.next( );
II . ..
}

The Listlterator interface extends the Iterator interface. The methods


of the Listlterator interface are summarized in Table 8.7. In these methods,
parameter o is of type Object.
Iterations on all collections are done uniformly using these two abstract iterator
interfaces. Each concrete collection provides a concrete iterator that implements
the Iterator interface. Furthermore, each concrete collection that implements the
List interface provides a concrete list iterator that implements the List Iterator

TABLE 8.7
Methods of Interface Listlterator

Method Description

add ( o) Inserts the element o into the list at the current position
hasNext O Returns true if this list iterator has more elements when
traversing the list in the forward direction
hasPrevious O Returns true if this list iterator has more elements when
traversing the list in the reverse direction
next () Returns the next element in the list
nextlndex O Returns the index of the element that would be returned by a
subsequent caU to next 0
previous O Returns the previous element in the list
previous Index() Returns the index of the element that would be returned by a
subsequent call to previous()
remove O Removes from the list the last element that was returned by
next() or previous()
set (o) Replaces the last element returned by next () or previous ()
with the element o
8.2 The Collections Framework • 321

interface. Instances of concrete iterators are obtam·ed through one of the followmg
• th .
me th ods defin ed m e Collection and List interfaces, respective · 1y:
■ Defined in the Collection interface:
Iterator iterator()

■ Defined in the List interface:


Listiterator listiterator()

Iterate through the Views of Maps


Usually, figu?ng out the classes of the elements in a collection is straightforward. In
the ca~e of views created by maps, the classes of the elements in these views are the
followmg:

View Type of Elements

Key set The class of keys


Value collection The class of values
Entry set Map . Entry

Here is an example of iterating through the key set view of a map:


Set keys= map . keySet () ;
Iterator iter = keys.iterator () ;
while (iter . hasNext ()) {
II assume the keys are strings
String key= (Stri ng) i ter . next ();
II . . .
}

The interface Map. Entry is defined inside the Map interface as a ne ted interface.
The elements in the entry set view of maps are instances of Map . Entry. The method
of the Map. Entry interface are summarized in the following table. In these method ,
parameter v is of type Object and represents a value in a key-value pair.

Method Description

getKey() Returns the key corre ponding to thi entry


get Value 0 Returns the value corre ponding to thi entry
setValue (v) Replaces the value corre ponding to this entry with the value v
322 • Object-Oriented Application Frameworks

Here is ilJl example of iterating through the entry et view of a map:

Set entries= map . entrySet ();


Iterator iter = entries . iterator();
while ( iter.hasNext ()) {
Map.Entry entry= (Map .Entry) iter .next ( );
II assume the keys and values are strings
String key~ (String) entry.getKey ( );
Stri ng value = (String) entry.getValue ( );
II . . .
}

EXAMPLE 8.2 Count the Number of Occurrences of Each Word

PURPOSE
This example demonstrates iterating through the entry set of a map.

DESCRIPTION
This program reads a piece of text as input. It counts the number of occurrences of
each word in the text input.

SOLUTION
In order to count the number of occurrences of each word, we need a map that maps
each word to the number of its occurrences. To print out the result, we first obtain the
entry set view of the map. We then iterate through the entry set and print out e ach
entry.
The values in the map are instances of the following classes:

C..Count
public class Count {
public Count(String word, inti) {
this . word= word;
this.i = i;
}

public String word ;


public inti;
}

ClallVordFr
import java .util . •;
import java . io . •;
public class WordFrequency {
static public void main (String[)args ) {
Map words = new Has hMap( );
8.2 The Collections Framework ■ 323

String delim =" \t\n.,:;?!-/0()\"\'";


BufferedReader in= new BufferedReader(
new InputStreamReader (System.in) );
String line, word;
Count count;
try {
while ((line= in.readLine()) != null) {
StringTokenizer st= new StringTokenizer (line, delim);
while (st.hasMoreTokens()) {
t word= st .nextToken().toLowerCase ();
count= (Count) words . get(word);
if (count== null) {
words.put(word, new Count(word, 1));
} else {
count . i++ ;
}
}
}
} catch (!□Exception e) {
e .printStackTrace();
}

Set set= words .entrySet ();


Iterator iter = set.iterator();
while (iter . hasNext ()) {
Map.Entry entry= (Map.Entry) iter.next();
word= (String) entry.getKey ();
count= (Count) entry .getValue ();
System.out .println(word +
(word.length () < 8 ? "\t\t" "\t") +
count.i);
}
}
}

Using President Lincoln's Gettysburg Address as the input, we have the following
output:

devotion 2
years 1 men 2
civil 1 remember 1
place 1 who 3
gave 2 did 1
they 3 work 1
struggled 1 rather 2
fathers 1

Note that the words in the output are not in any particular order. •
324 • Object-Oriented Application Frameworks

8.2.5 Ordering and Sorting

An order, more precisely a partial order, is a binary relation between two objects
that is transitive. Let -< denote a partial order; then, for any three objects a, b,.ao<l c •
a -< b and b -< c implies a -< c. If a partial order ensures that for any two ~bJects a
and b, a-< band b-< a implies a= b, then it is called a total order . A part1al_order
that is not a total order is called a strictly partial order. A sorted collection is one
that orders its elements according to a certain order. Sorting ananges the elements of
a collection so that they are ordered according to a certain order.
There are two ways to define orders on objects:
1. Each class can define a natural order among its instances by implementing the
Comparable int~rface.
2. Arbitrary orders among different objects can be defined by comparators, 01;
classes that implement the Comparator interfa~e.

Defining Natural Orders


The natural orderof a class can be defined by implementing the Comparable interface
and providing an implementation for the only method compare To () declared in the
interface. The Comparable interface is defined as follows:

public interface Comparable {


public int compareTo(Object o);
}

Toe compareTo O method compares the receiving object this with the param-
eter o. The result of the comparison is an integer. The contract of the method is

Result < 0, if this precedes o;


Result 0, if neither this precedes o, nor o precedes this; or
Result> 0, if o precedes this.

The compareTo O method must proper1y define a total order. For any two objects
a and b,

a. compareTo (b) > 0 implies that b. compareTo (a) < O,


a. compareTo (b) < 0 implies that b. compareTo (a) > 0, and
a. compareTo (b) = 0 implies that b. compareTo (a)= O.

Also, the definition of the compareTo O method must be consistent with the
definition of the equals () method. For any two objects a and b,

a. equals (b) is true if and only if a. compareTo (b) is o.


The natural order of many classes in JDK, such as String, is already define d .
8.2 The Collections Framework ■ 325

Defining User-Defined Orders


User-defined orders among objects can be defined using comparators. User-defined
orders can be used to sort collections with elements for which no natural order
is defined, or to sort the elements in an order different from the natural order of
the_ elements. ~ comparator implements the Comparator interface and provides
an 1mplementat1on for the only method, compare(), declared in the interface. The
Comparator interface is defined as follows:

public interface Comparator {


int compare(Object ol, Object o2);
}

The compare O method compares the two parameters. The result of the com-
parison is an integer. The contract of the method is

Result< 0, if o1 precedes o2;


Result 0, if neither o1 precedes o2, nor o2 precedes o1; or
Result> 0, if o2 precedes o1.

Similar to the preceding compareTo O method, the compare() method must


properly define a total order. For any two objects a and b, and a comparator c,

c . compare (a, b) > 0 implies that c. compare (b, a ) < O;


c. compare (a, b) < 0 implies that c . compare (b, a)> O; and
c. compare (a, b) = 0 implies that c. compare (b, a )= 0.

Also, the definition of the compare () method must be consistent with the
definition of the equals() method in the class of objects being compared. For any
two objects a and b, and a comparator c,

c . compare (a, b) is O if and only if both a . equals (b) and b. equals (a) are
true ;
a. equals (b) is true implies c . compare (a, b) is O; and
c. compare (a, b) > Oimplies c. compare (b, a) < 0, and c . compare (a, b) < 0
implies c. compare (b, a) > 0.

Sorted Collections
There are two sorted abstract collections: SortedSet and SortedMap. The Sort-
edSet interface extends the Set interface. All the method of the Set interface
are inherited. The new method in the SortedSet interfaces are summarized in the
following table. The parameters of the methods, o, ol, and o2, are all Objects rep-
resenting elements.
326 ■ Object-Oriented Application Frameworks

Method Description

comparator() Return the comparator associated with this sorted cl , or null


if the natural order is used
first 0 Returns the first (lowest) element currently in this sorted set
headSet(o) Returns a set view of the portion of this sorted et whose
clements are strictly less than o
last() Returns the last (highest) element currently in this sorted set
subSet(o1, o2) Returns a set view of the portion of this sorted set whose
elements range from o1, inclusive, to o2, exclusive
tailSet(o) Returns a set view of the portion of this sorted set whose
clements are greater than or equal to o

The Sorted.Map interface extends the Map interface. All the methods of the Map
interface are inherited. The new methods in the Sorted.Map interface are summarized
in the following table. The parameters of the methods, k , kl, and k2, are all of type
Object representing keys.

Method Description

comparator () Returns the comparator associated with this sorted map, or null
if the natural order is used
firstKey() Returns the first (lowest) key currently in this sorted map
headMap (k) Returns a map view of the portion of this sorted map whose
keys are strictly less than k
lastKey() Returns the last (highest) key currently in this sorted map
subMap(k1, k2) Returns a map view of the portion of this sorted map whose
keys range from k1, inclusive, to k2 , exclusive
tailMap(k) Returns a map view of the portion of this sorted map who e
keys are greater than or equal to k

A concrete sorted collection SC that implements the SortedSet or SortedMap


interface provides at least two constructors:

• SC() creates a sorted collection that is sorted according to the natural order of
the elements.
• SC ( Comparator c) creates a sorted collection that is sorted according to the
order defi ned by the specified comparator.
8.2 The Collections Framework • 327

EXAMPLE 8.3 List Word Count in Alphabetical Order

PURPOSE
This example demonstrates the use of a sorted map ordered according to the natural
order of its keys.

DESCRIPTION
This program reads a piece of text as input. It counts the number of occurrences of
each word in the text input and prints out the results as a list sorted according to the
alphabetical order of the words.

SOLUTION
This is a variation of Example 8.2 [p. 322). The words are the keys of the map, and
the alphabetical order of words is the natural order of String. So we can imply use
a TreeMap, which by default maintains its entries according to the natural order of
its keys.

CIIIIIVordFre
import java.util.•;
import java . io . •;
public class WordFrequency2 {
static public void main (StringO args) {
Hap words • new TreeHap();
String delim =" \t\n . ,: ;? !-/O [] \"\'" ;
BufferedReader in= new BufferedReader(
new InputStreamReader (System. in));
String line, word;
Count count;
try {
while ((line= i n.readLine () ) != null) {
Stri ngTokenizer st= new StringTokenizer(line, delim);
while (st .hasMoreTokens() ) {
word= st.nextToken (). toLowerCase ();
if (words . containsKey (word)) {
count= (Count ) words . get (word);
count . i++ ;
} else {
words.put (word, new Count (word , 1));
}
}
}
} catch (I0Exception e) {
e.printStackTrace();
}
Set set= words . entrySet ();
Iterator iter = set . iterator () ;

6
328 ■ Object-Oriented Application Frameworks

while ( iter . hasNext( )) {


Map.Entry entry= (Map.Entry) iter .next();
word= (String) entry. getKey();
count z (Count ) entry . getValue ();
System . out .println (word +
(word . length () < 8 ? " \t\t" "\t" ) +
count.i );
}
}
}

Note that the class WordFrequency2 is nearly identical to class WordFre-


quency in Example 8.2 [p. 322); the exception is the line in boldface, where the
TreeMap implementation is used instead of the HashMap implementation. Using
President Lincoln's Gettysburg Address as the input, we have the following output:

a 7
above 1 whether 1
add 1 which 2
address 1 who 3
advanced 1 will 1
ago 1 work 1
all 1 world 1
years 1
~

EXAMPLE 8.4 List Word Count in Reverse Alphabetical Order

PURPOSE

This example demonstrates the use of a sorted map ordered according to a user-defined
order on the keys.

DESCRIPTION

This program reads a piece of text as input. It counts the number of occurrences of
each word in the text input and prints out the results as a list sorted according to the
reverse alphabetical order of the words .

SOLUTION

This is another variation of Example 8.2 [p. 322]. To sort the entries according to
an order other than the natural order of the keys, we need to define a user-defined
order. An instance of TreeMap can be instantiated by specifying the user-defined
comparator in the constructor.
The following program contains the comparator for the reverse alphabetical order
of strings. To obtain the reverse alphabetical order, we simply negate the sign of the
result of the compareTo O method of the String class.
8.2 The Collections Framework • 329

Com~ for nftl'le al betlcal order: Str tor


public class StringComparator implements Comparator {
public int compare(0bject ol, Object o2) {
if (ol !• null &&
o2 !• null &&
ol instanceof String &&
o2 instanceof String) {
String sl • (String) ol;
String s2 = (String) o2;
return - (sl . compareTo(s2)) ;
} else {
return 0;
}
}
}

a.. WordFr G9"3


import java.util.•;
import java.io.•;
public class WordFrequency3 {
stati c public void main(StringO args) {
Hap words• new TreeHap(new StringComparator ());
String delim =" \t\n.,:;?!-/0 O\"\"' ;
BufferedReader in= new BufferedReader(
new InputStreamReader (System . in));
String line, word;
Count count;
try {
while ((line= in.readLine ()) != null) {
StringTokenizer st= new StringTokenizer(line, delim) ;
while (st.hasMoreTokens( ) ) {
word= st .nextToken() .toLowerCase ();
if (words.containsKey(word)) {
count= (Count) words.get (word) ;
count . i++;
} else {
words.put(word, new Count (word, l )) ;
}
}
}
} catch (I0Exception e) {
e.printStackTrace();
}

Set set= words .entries ();


Iterator iter: set.iterator ();
while (iter.hasNext()) {
Map . Entry entry (Map . Entry) iter . next ();
2

word= (String) entry.getKey () ;


330 ■ Object-Oriented Application Frameworks

count= (Count) entry.getValue();


System.out.println(word +
(word . length() < 8 ? "\t\t" "\t") +
count . i);
}
}
}

Again, note that the class WordFrequency3 is also nearly identical to class
WordFrequency in Example 8.2 [p. 322); the exception is the line in boldface,
where a user-defined comparator is provided as the parameter of the constructor of
the TreeMap. Using President Lincoln's Gettysburg Address as the input, we obtain
the following output:
years 1
world 1 all 1
work 1 ago 1
will 1 advanced 1
who 3 address 1
which 2 add 1
whether 1 above 1
a 7

Utilities on Collections
Besides the sorted collections, the collection framework also provides a set of useful
utilities and algorithms related to ordering and sorting. They are provided as static
methods of the Collections class. These methods are summarized in Table 8.8.
Parameter 1 is of type List, parameter c is of type Collection, parameter k is of
type Object, and parameter comp is of type Comparator.

EXAMPLE 8 .5 List Word Count in the Order of Frequency

PURPOSE
This example demonstrates the use of sorting utilities to sort collections according to
user-defined orders.
DESCRIPTION
This program reads a piece of text as input. It counts the number of occurrence of
each word in the text input and prints out the results as a list sorted by the freq uencies
of occurrences of the words.
SOLUTION
This is another variation of Example 8.2 [p. 322). In this case, the desired order is not
based on keys but on values, so sorted maps offer no help. We use a HashMap to tore
the word counts. After all the words have been counted, we obtain a collection view
of the values contained in the map and then sort the value collection according to a
user-defined comparator CountComparator.
The comparator is used to sort the frequency list.
8.2 The Collections Framework ■ 331

TAILI •• I
Utility Mdhods of the Collections Class

Method Description

sort (1) Sorts the list 1 according to the natural order of the
elements
sort (1, comp) Sorts the list 1 according to the order defined by the
comparator comp
binarySearch(l, k) Searches for the element kin the list l, using the
binary search algorithm. The list 1 is sorted according
to the natural order of the elements. The return value
is the index of element k if it is present in the li t, or
(insertion point) - I. The insertion point is the index
at which the element should be inserted.
binarySearch (1, k, comp) Same as preceding, except that the li t 1 is sorted
according to the order defined by the comparator comp
min ( c) Returns the minimum element in the collection c
according to the natural order of the elements in the
collection
min ( c, comp) Returns the minimum element in the collection c
according to the order defined by the comparator comp
max ( c) Returns the maximum element in the collection c
according to the natural order of the elements in the
collection
max ( c, comp) Returns the maximum element in the collection c
according to the order defined by the comparator comp

Com · :!:'r.!..
tor for word count: CoutC,!! ,.........
· _.
publi c class CountComparator implements Comparator {
publ ic int compare(Object ol, Object o2) {
if (ol != null &&
o2 != null &&
ol instanceof Count &&
o2 instanceof Count) {
Count cl= (Count) ol;
Count c2 = (Count) o2;
return (c2.i - cl . i);
} else {
return O;
}
}
}
••

332 • Obj ect-Oriented Application Frameworks

,
I Class WordFrequency4
import java . util.•;
import java.io . •;
public class WordFrequency4 {
static public void main(String[) args ) {
Map words= new HashMap ();
String delim =" \t\n . ,:;?!-/()[)\ 11 \ ' " ;
BufferedReader in= new BufferedReader (
new InputStrearnReader (System . in) );
String line, word;
Count count;
try {
while ((line= in . readLine ()) != null) {
StringTokenizer st= new StringTokenizer (line, delim);
while (st.hasMoreTokens()) {
word= st .nextToken () .toLowerCase ();
count= (Count) words . get(word);
if (count== null) {
wor ds . put(word, new Count(word, l) );
} else {
count.i++;
}
}
}
} catch (IOException e) {
e.printStackTrace() ;
}
List list= new ArrayLi s t(words . val ue s ());
Collections . sort(lis t, new CountComparat or()) ;
I terat or iter s list . iter at or () ;
while (iter .hasNext()) {
count= (Count) iter.next();
word = count.word ;
System . out .println(word +
(word.length() < 8 ? 11 \t\t" "\t ") +
count . i);
}
}
}

Using President Li ncoln's Gettysburg Address as the input, we have the following
output:
the 13
that 12 consecrate 1
we 10 world 1
here 8 consecrated 1
to 8 remember 1
a 7 did 1
and 6 work 1
fathers 1
8.3 The Graphical User Interface Framework-AWT and Swing • 333

8.3 THE GRAPHICAL USER INTERFACE FRAMEWORK-AWT AND SWING

Java provides an extensive framework for building high-quality graphical user inter-
faces (GUI). The graphical user interface framework i part of the Java Foundation
Classes (JFC). We focus on two important packages in the JFC: the Abstract Windows
Toolkit (AWT) and Swing.
The Java GUI framework consists of several categories of classes. The main
categories are the following:

GUI components: Components, also known as widgets, are the building blocks
of the visual aspect of graphical user interfaces. Each GUI component is
characterized by its its visual appearance, the look, and its behavior pattern
in response to user input, the feel. Each type of GUI component is defined
by a GUI component class. Examples of the GUI component classes include
Button, Label, Checkbox, Scrollbar, Frame, and Dialog.
Layout managers: Layout managers define strategies for laying out GUI compo-
nents in windows. Commonly used layout managers include FlowLayout
and BorderLayout.
Events and event listeners: Events represent user input or action . Each event
class represents a particular type of user input, such as the KeyEvent cla
for keyboard input and the MouseEvent class for mouse actions. Each event
class is associated with an event listener class responsible for handling this
type of event. For example, the KeyListener class is associated with
the KeyEvent class, and the MouseListener cla is associated with the
MouseEvent class.
Graphics and imaging classes: The e clas es allow component to draw their
visual appearances and include graphics (Color, Font, Graphi cs, etc.),
geometry (Point, Rectangle, Dimension. etc.), and imaging (Image.
Icon, etc.).

The Abstract Windows Toolkit (AWT) package provides the ba ic upport for
building graphical u er interface . The Swing package i an extension of AWT,
which provides extensive support for building ophi ticated-looking and high-quality
graphical user interfaces.

8.3.1 The GUI Components


The inheritance hierarchy of GUI component classes in AWT is hown in Figure 8.2.
All classes shown in the class diagram belong to the package j ava. awt, except the
Applet class, which belong · to package j ava . applet . All Java GU_I component
classes are subclasses of the Component class. The Component class ts an abstract
class that defines the characteristics and behaviors that are common to all the GUI
334 • Object-Oriented Application Frameworks

Figure 8.2

The GUI compo-


nent classes in Button
AWT.
Canvas

Checkbox r------,

Choice

Label Frame

List

Scrollbar File Dialog

TextComponent . . - - - -
java.applet.Applet

TextArea TextField

components. The subclasses of Component class are divided into two groups: primi-
tive components, which do not contain other components, and con tain ers, which may
contain other primitive components and containers.
The Container class is an abstract class that defines the characteristics and be-
haviors common to all the containers. In Figure 8.2, the left branch of the Component
class consists of the primitive components, and the right branch of the Component
class consists of the containers. Using containers, we can construct graphical user
interfaces by organizing components into tree structures, in which the primitive com-
ponents are the leaves and the containers are the interior nodes. Furthermore, as both
primitive components and containers are subclasses of the Component class, they can
be treated uniformly.

The AWT and Swing Components


The primitive components and containers in AWT are summarized in Table 8.9.
The Swing package supports building high-quality graphical user interface . It
is an extension of AWT and provides many more varieties of more sophisticated
components than AWT. A major difference between the AWT component and the
Swing components is that most of the Swing components are lightweight , wherea all
the components in AWT are heavyweight. Heavyweight components are a sociated
with narive components created by the underlying window system, such as Win32
or X-Window. The native component associated with an AWT component is known
"
8.3 The Graphical User Interface Framewor1<-AWT and Swing ■ 335

TABLE 8.9
Component and Container Classes in AWT

AWT Component Description

Button Text labeled "push" button that responds to mouse clicks


Canvas Blank rectangular area of the screen onto which the application can
draw or from which the application can trap input events from the
user
Checkbox A two- late component that can be either "on" (true) or "off' (false)
Choi c e Pop-up menu of text choices from which the user can choose
Label Component with a text that does not respond to any user input
List Scrolling list of text items from which the user can choo e
Scrollbar Scroll bar
Text Component Superclass of any component that allows the editing of ome text
TextField Text component that allows for the editing of a single line of text
TextArea Multiline region that displays text and allows editing of multiline
text

AWT Container Description

Dialog Window that take input from the user


FileDialog Dialog window from which the user can elect a file

I Frame
Panel
Window
Top-level window with a title and a border
Borderless, titleles , and transparent container
Borderless and titlele top-level window

a its peer component. The appearance of the AWT component i determined by it


peer component and i thu platform dependent. In other \ ord , a GUl application
written in Java with AWT can run on different platfonn with the ame behavior but
different look . Lightweight component have no peers. Their look are determined
by the Java run-time environment, not the underlying windo-. y tern. Lightweight
components eliminate the overhead a sociated with peer component and en ure that
GUI application written in Java -. ill not only run on different platform with the
same behav ior, but al o have the ame look. Furthennore, lightweight components
emulate the look of variou' window y tem · regardle of the underlying window
336 ■ Object-Oriented Application Frameworks

Figure 8.3 Component Container k 1 - - -- ---------,

Swing counterparts
of AWT compo-
JComponent
nents.
JButton

Jlabel Window

JComboBox ,____ _.

JCheckBox JApplet Dialog

Jlist

JScrollBar JFrame JDialog

JTextComponent
JWindow

JTextField JTextArea

system. For example, a Java GUI application using Swing can have a Motif look even
when it is running on a Win32 platform. This capability is known as the pluggable
look and feel.
The Swing package consists of several hundred classes and numerous subpack-
ages, of which we cover only a few. The most straightforward part of Swing consists
of classes with counterparts in AWT. These classes are shown in Figure 8.3, with the
shaded boxes being classes in AWT. The names of Swing component classes begin
with a prefix J followed by the name of their counterparts in AWT.2 The subclasses of
JComponent represent lightweight components. Note that JWindow, JFrame, and
JDia1og are not lightweight components. The differences between these Swing com-
ponents and their AWT counterparts are summarized in Table 8. I 0.

8.3.2 Design Pattern: Composite

The design of the GUI components hierarchy illustrates a commonly used design
pattern : the Composite pattern.

2. There arc some exceptions to this naming convention.


8.3 The Graphical User Interface Framewor1<-AWT and Swing • 337

TAILll.10
Component and Container Classes in Swing

Swing Component Description

JButton Allows an image icon and a text label; supports keyboard


accelerators
JCheckBox Allows user-defined icons for the on and off states
JComboBox An ex.tension of Choice. The items are not limited to text. They
can be icons or components. The choice can be editable.
JLabel Allows an image icon and a text label
J List The items are not limited to text. They can be icons or components.
JScrollbar Lightweight scroll bar
JTextComponent Superclass of lightweight components for text editing
JTextField Lightweight single-line text component; supports multilingual text
displaying and editing
JTextArea Lightweight multiline text component; supports multilingual text
displaying and editing
JPanel Lightweight container
JDialog Dialog tailored for lightweight components
JFrame Frame tailored for lightweight components
JWindow Window tailored for lightweight components

Design Pattern Composite

Category: Structural design pattern.


Intent: Compo e object into tree structure to repre ent a part-whole hierarchy.
Composite let client treat individual object and compo ite object
uniformly.
Applicability: Use the Compo ite pattern
• when you want to repre ent a part-whole hierarchy of objects.
• w'hen you want client to be able to ignore the difference between
composite (?bject and individual objects (client will treat all objects
in the-compo ite tructure unifonnly).
338 • Object-Oriented Application Frameworks

• design
The structure of the Composite • pattern 1s · tl1e following diagram:
· shown rn

Component *

Client f · .. .. .. · . . ••.. ·:> operation()


add(Component)
remove(Component)
getChild(int)

f
I
Leaf Composite 0-
operation() operation()
add(Component)
remove(Component)
getChild(int)

The participants of the Composite design pattern are the following:


■ Component (e.g., Component), which declares the common interface for all
classes in the composite; implements default behavior common to all classes,
as appropriate; and (optionally) defines an interface for accessing a component's
parent in the hierarchy.
■ Leaf (e.g., Button, Label, and Checkbox), which defines behavior for primi-
tive objects in the composition.
■ Composite (e.g., Container and Panel), which declares an inte~ace for access-
ing and managing its child components, defines behavior for components having
children, stores child components, and implements child-related operations in
the Component interface.
■ Client, which manipulates objects in the composition through the Component
interface.

8.3.3 Layout Managers

Each container has a layout manager, which handles the layout of the components
contained in the container. Several layout managers are provided in the JDK, as
illustrated in Figure 8.4. Each layout manager defines a layout strategy. One of the
advantages of using layout managers is that we do not have to specify the absolute
coordinates and dimensions of each component. The components in the container
are displayed based on their relative positions. The relative positions of components
can be specified explicitly with positional constraints or implicitly by the order in
which they are inserted in the container. The positions and sizes of the components
are computed by the layout manager based on the dimensions of the container and
automatically adjusted whenever the dimensions of the container change, which is
usually caused by resizing the top-level window.
8.3 The Graphical User Interface Framework-AWT and Swing • 339

Figure 8.4 Container LayoutManager


The layout man-
agers. Borderlayout
LayoutManager2
Cardlayout

Flow layout
GridBaglayout
Gridlayout

We discuss and illustrate the use of several commonly used layout managers:
FlowLayout, GridLayout, and BorderLayout. We also demonstrate how to do
customized layout without using the layout managers.

Using Layout Managers


The design of layout managers uses the Strategy design pattern, with each layout
manager implemented as a strategy. Different layout managers can be used uniformly
and interchangeably. The following methods of the Container class deal with the use
of layout managers. In the parameters of the methods, lm represents a layout manager,
comp represents a component, and est represents a positional constraint.

Method Description

setLayout (l m) Sets lm as the layout manager of thi container


add (c omp) Adds component comp to thi container
add (comp, est) Adds component comp to thi container with a po itional
con traint est (e.g., the po ition key in BorderLayout)

Flow Layout
The flow layout is the most straightforward layout trategy. It arrange the components
in a left-to-right flow, in the order in which they were in erted in the container. If the
container is not wide enough for all the component , it break the flow into several
lines. Each line i centered by default, and each component i ized to its nat11rc1l size.
340 • Object-Oriented Application Frameworks

A flow layout manager can be created by using one of the following constructors of
the FlowLayout class:

Constructor Description

FlowLayout (al ign, hGap, vGap) Creates a flow layout manager with the
alignment set to align and the horizontal
and vertical gap set to hGap and yGap,
respectively
FlowLayout (align) Creates a flow layout manager with the
alignment set to align and the horizontal
and vertical gaps set to the default value
FlowLayout O Creates a flow layout manager with the
alignment and the horizontal and vertical
gaps all set to their respective default
values

The alignment of the flow layout can be one of the following:

FlowLayout . LEFT The components are flush left.


FlowLayout. CENTER The components are centered.
FlowLayout. RIGHT The components are flush right.

The default alignment is centered. The horizontal and vertical gaps are the gaps
between the adjacent components, specified in pixels. The defaults are 5 pixels for
both the horizontal and vertical gaps.

EXAM Pl E 8. 6 Using Flow Layout

PURPOSE
This example demonstrates the use of the flow layout and the creation of buttons.

DESCRIPTION
The Applet class is a subclass of Panel, so the drawing area of an applet is actually
a container. We can add any component to the panel. Here, we simply create and
add six buttons to the panel. The results are shown in Figure 8.5. The layout shown in
Figure 8.5(a) is the result of using the flow layout in a panel that is 400 pixels wide and
50 pixels high. The layout shown in Figure 8.5(b) is the result of using the same flow
layout in a panel that is 100 pixels wide and l 20 pixels high. Note the correspondence
between the order of insertion and order of buttons in the layouts.
8.3 The Graphical User Interface Framework-AWT and Swing ■ 341

Figure 8.5
~ Applet Viewer: Flow.class [jjiiijlr
■l ~
Applet
Flow layouts.
I- Java I C++ I Pert I Ada I ~ I Elffef I
!Applet started.

(a)

I- .
i
□R

l Java I
C++
I
;I Pert I I Ada

\II Smallalc I
11 EHW I
,,!•Applet started.
(b)

SOLUTION
A FlowLayout object is created first and is set as the layout manager of the container
(i.e., the Panel object) using the setLayout () method of the Container class. A
button can be created as follows:

new Button (label)

where label is the label of the button. Each button is added to the container using the
add () method of the Contai ner class. Since the order of the components in a flow
layout is determined by the insertion order, no positional constraints are necessary
for the add () method when using FlowLayout.

Flow laY9_!1t a
import java . awt.•;
i mport java . applet . Applet ;
public class Flow extends Applet {
public Flow() {
setLayout(new FlowLayout());
add(new Button ("J ava")) ;
add(new Button("C++" ));
add(new Button("Perl ") ) ;
add (new Button ( "Ada")) ;
add (new Button ( "Smalltalk" )) ;
add (new Button( "Eiffel") ) ;
}
}
34 2 • Object-Oriented Application Frameworks

Grid Layout
The grid layout arranges components in a rectangular grid, and all compo~ents are
given the same size. The components can be stretched vertically and honzontally,
if necessary, to fill the entire space of the container. A grid layout manager can be
created by using one of the following constructors of the GridLayout class:

Constructor Description

GridLayout (r , c, hGap, vGap) Creates a grid layout manager with r rows


and c columns and with the horizontal
and vertical gaps set to hGap and vGap,
respectively
GridLayout (r, c) Creates a grid layout manager with r row
and c columns and with the horizontal and
vertical gaps set to 0
GridLayout O Creates a grid layout manager with a single
row and with the horizontal gap set to 0

When the first two constructors are used, r and c must be nonnegative integers.
One of them, but not both, can be 0. If r is 0, the grid may have any number of rows,
depending on the number of components in the container. Similarly, if c is 0 , the grid
may have any number of columns, also depending on the number of components in
the container.

EXAMPLE 8. 7 Using Grid Layout

PURPOSE
This example demonstrates the use of the grid layout.
DESCRIPTION
This program is similar to the one in Example 8.6. The main difference is that a
grid layout manager is used here. The numbers of rows and columns of the grid are
specified as the parameters of the applet.
Figure 8.6 shows the effects of grid layout in a panel with six buttons. The layout
shown in Figure 8.6(a) is the result of specifying 1 row and O column, or ( 1, O).
The layout shown in Figure 8.6(b) is the result of specifying 3 rows and 2 columns,
or (3, 2). The layout shown in Figure 8.6(c) is the result of specifying O row a nd J
column, or (0, l ).

SOLUTION
First, the numbers of rows and columns of the grid are determined based on the
values of two parameters specified in the <applet> tag: row and col. Second, a
8 ·3 The Graphical User Interface Framework-AWT and Swing • 343

Figure 8.6
!MJ®$19iMMifflGG%t
Applet
- ; !g1fx1
Grid layouts. r,..-
1• l .,--- - ..,
Java C++ Perl Ada Eiffel
1
I
Smallalk
I
!Applet started.
-

(a)

Applet Applet

Java

Ada C++

Smalltalk Eiffel Perl

1,Applet started
Ada
(b)

Smalla1k

l
Applet started.

(c)

GridLayout object is created with the given number of row and column and i set
as the layout manager of the container. Third, a number of button are inserted into
the container. The position of components in a grid layout i al o determined by the
order of insertion, so no positional con traint are nece ary for the add () method
when using GridLayout.

Grid la~ut aeelet: Grid


import java.awt.*;
import j ava . applet . Applet;
public class Grid extends Applet {
public void init () {
int row = 0, col = 0;
344 • Object-Oriented Application Frameworks

St r i ng att = get Parameter ( "row");


if (att != null )
r ow= Integer . parseint(att);
att = getParameter ( "col");
if (att ! = null)
col = Integer . parseint(att) ;
if ( r ow == 0 && col== 0) {
r ow = 3; col= 2 ;
}
setLayout (new GridLayout(row, col)) ;
add (new Button ( "Java"));
add (new Button ( "C++"));
add (new Button ( "Perl"));
add(new Button ( "Ada" ) );
add(new Button ( "Smalltalk")) ;
add (new Button("Eiffel"));
}
}

Border Layout
The border layout is one of the most versatile layout managers. It arranges as many as
five components in five positions identified as North , South, East, West, and Center,
as illustrated in Figure 8.7. The North and South 9omponents are placed at the top
and bottom of the container, respectively. They are set to their natural heights and
may be stretched horizontally to fill the entire width of the container. The East and
West components are placed at the right and left sides of the container, respectively.
They are set to their natural widths and may be stretched vertically to fill the space
between the North and South components. The Center component may be stretched
horizontally and vertically to fill the space left in the center. If one or more of the
components, except the Center component, are absent, the remaining components
will be stretched vertically and/or horizontally to fill the space left by the missing
components. A border layout manager can be created by using one of the following
constructors of the BorderLayout class:

Method Description

BorderLayout (hGap, vGap) Creates a border layout manager with the


horizontal and vertical gaps set to hGap and
vGap, respectively
BorderLayout ( ) Creates a border layout manager with the
horizontal and verlical gaps set to 0
8.3 The Graphical User Interface Framework-AWT and Swing • 345

Figure 8.7 [~ Applet Viewer: Bord ...iiir


■l ~
Applet
Border layout.

The components managed by a border layout manager should be inserted with


one of the following positional constraints, which are symbolic constants defined in
the BorderLayout class:

BorderLayout.NORTH Places the component in the North position


BorderLayout.SOlITH Places the component in the South position
BorderLayout.EAST Places the component in the East position
BorderLayout.WEST Places the component in the West position
BorderLayout.CENTER Places the component in the Center position

EXAMPLE 8.8 Using Border Layout

PURPOSE
This example demonstrates the use of the border layout.
DESCRIPTION
This applet uses a border layout manager and simply inserts a button in each of the
five positions.
SOLUTION
The buttons are inserted using the add() method with a positional con traint.

import java .awt .•;


import java.applet .Applet ;
public class Border extends Applet {
public Border () {
setLayout(new BorderLayout ());
346 • Object-Oriented Application Frameworks

add (new Button( "North"), BorderLayout .NORTH);


add (new Button("South"), BorderLayout. SOUTH);
add (new Button ("East "), BorderLayout . EAST);
add (new Button (" West"), BorderLayout.WEST) ;
add (new Button("Center" ) , BorderLayout.CENTER);
}
}

EXAMPLE 8.9 Nested Border Layouts

PURPOSE
This example demonstrates the nesting of containers and creation of choices and
labels.

DESCRIPTION
This applet yields the layout shown in Figure 8.8. The outer panel contain two nested
panels and uses a border layout manager. Three buttons are inserted in the North, East,
and West positions of the outer panel. The center position contains another panel,
which also uses a border layout manager. A button is inserted in each of the five
positions of the panel at the center. The South position of the outer panel contains yet
another panel, which uses a flow layout manager. A button, a choice, and a label are
inserted in the pane] in the South position.

Figure 8.8 ri Applet Viewer: NestedPanels.class ■[ii £j


Applet
Nested border
layout. . North
north

Welt west center

IOUlh

Help I! i:J
one This is a message bar.

Applet started.
8.3 The Graphical User lntertac~ Framcwork-AWT and Swing • 34 7

SOLUTION
Nested : NeatedPanela
import java .awt .• ;
import java.applet .Applet ;
public class NestedPanels extends Applet {
public NestedPanel s () {
II set up the center panel
Panel center= new Panel ();
center.setLayout (new BorderLayout ());
center.add (new Button(" south" ), BorderLayout .SOtrrH);
center.add (new Button("north" ) , BorderLayout .NORTH);
center.add (new Button("east"), BorderLayout . EAST) ;
center . add(new Button("west "), BorderLayout .WEST);
center .add(new Button("center" ), BorderLayout .CENTER);
II set up the south panel
Panel south= new Panel ();
south.setLayout(new FlowLayout ());
south . add(new Button("Help") );
choice= new Choice ();
choice.addltem("one");
choice.addltem("two");
choice . addltem("three") ;
choice .addltem("four");
choice.addltem("five" ) ;
south . add(choice) ;
messageBar = new Label("This is a message bar .");
south.add(messageBar) ;
II set up the outer panel
setLayout(new BorderLayout ());
add(new Button ("North" ), BorderLayout .NORTH);
add(new Button("East" ) , BorderLayout . EAST);
add(new Button ("West" ), BorderLayout .WEST);
add(south, BorderLayout .SOUTH) ;
add(center, BorderLayout .CENTER);
}

protected Label messageBar ;


protected Choice choice;
}

Other layout manager in JDK include the GridBagLayout and CardLayout in


AWT and the BoxLayout in Swing. For details about th~ u e of the e layout manager
ee Chan and Lee [ 1998] and Walrath and Campione [1999].
- .2 - -- . .
348 • Object-Oriented Application Frameworks

8.3.4 Handling Events

GUI components communicate with the rest of the applications through events, which
represent user inputs or actions. The source of an event is the component from which
the event is originated. A listener of an event is an object that receives and processes
the event. Events are classified into different types, each of which is represented by
an event class, which is a subclass of AWTEvent . The class hierarchy of event classes
is shown in Figure 8.9. Each type of event is associated with a listener interface,
which is implemented by the listeners of that event type. The listener interfaces and
their associations with event classes are also shown in Figure 8.9. Listeners must be
registered to their respective sources before they can receive events from their sources.
Event handling in Java programs involves the following steps:

■ Determine the types of events to be handled and their associated listener inter-
faces . For example, to handle button clicks, the event class is the ActionEvent
and the associated listener interface is ActionListener.
■ Define listener classes that implement the listener interfaces and implement all
the methods of the interfaces. For example, to handle button clicks, the listener
class must implement the ActionListener interface.

class MyButtonHandler implements ActionListener {


public void actionPerformed(ActionEvent event) {
II. . . handle button click
}
}

Naming Convention Events and Listeners


The fo1Jowing conventions are used throughout AWT and Swing:

■ The names of event classes end with the suffix Event.


■ For event class XyzEvent, the associated listener interface is usually named
XyzListener. If there is also an associated adapter class, it will be named
XyzAdapter.
■ For a listener that implements interface XyzListener, the name of the method
to regi ster the listener to its source is addXyzListener.

■ Create instances of the components, which are the sources of the events. For
example,

Button button!= new Button("One");


....... '

8.3 The Graphical User Interface framewor1<-AWT and Swing • 349

Figure 8.9 java.util.EventObject


java.util.Event.Listener
The event classes java.awt.AWTEvent
and listeners. java.awt.event
,---------
I
- - - ----'
i---1
I
ActionListener \ ... -.. -........ --....... ••••••· L___A_ct_io
_n_E
_v_e_n_t _....,

i---1
I
AdjustmentListener I·•• .... --•---•.. •••.. •.. •······ AdjustmentEvent
I
I
ComponentListener · · · ·· ·· · ·.. · · · -·· · .... ·· · .... · - ComponentEvent

'--4 ComponentAdapter I
I
I
1--
1
ContainerListener ContainerEvent

:
1
- - -I ContainerAdapter \

:---I~~'f~Fi_o_cu_s_L_is_t_en_e_r_~\ ···.... ·· •.... -.... ·.. ·· L...-_F_o_c_us_E_v_e_n_t_....,


I

I
1
\ - - ~~- -
F_
oc_u_s_A_d_a_p_te_r_ ~\ lnputEvent

\--4 KeyListener \-.. -...... · ·.. -- .____


K_e_yE
__
ve_n_t_ ~

I
t -4.____ K_ey_A_da
_p_te_r _ __.\

:---I. . ~~M_o_u_se_L_i_st_e_n_e_r_......,\· -· · -...... .... • MouseEvent

: t _-\~__ M_o_u_se_A
_d_a_p_t_
er_ __,\ '--r-------

,--
1
MouseMotionListener · · · · ·-· · · · · -· · · · ·· · PaintEvent
I

: '---I MouseMotionAdapter \
I

\- - -1~-~W
_ in_d_o_w_L_is_t_e_
ne_r_ ~\ · .. ·· · ·· · -· .. · · ...... · · · .___W
_ in_d_o_w_E_v_e_n_
t ___.

\
I
~ - -\.___W_in_d_o_w_A_d_a_p_te_r_......,\
\-
I
- -\ Item Listener ~ · .. · ··· ·· · ·· ···.... · ·.. · -·· .. ·· ItemEvent
.___ __ _ __ ~

:_ - -\ TextListener ~ .. ·.. ··· · ·· .. · .... ·· .. · ·· ·.... · .____~_e_xt_E_v_e_n_t- ~

■ Create instances of the li tener , and regi ter the li teners to their sources. For
example, to handle button click u ing MyButtonHandler u e

MyButtonHandler handler = new MyButtonHandler();


buttonl .addActionListener(handler);
,
-
350 • Object-Oriented Application Frameworks

A listener may listen to several sources, and a source can have multiple listeners.
Continuing with the preceding example, we can create another button, button2, and
let the listener handler handle clicks on both buttons:

Button button2 = new Button (" Two");


button2 .addActionListener(handler);

A listener may also listen to different types of events by simply implementing


multiple listener interfaces.
Event adapters are classes that implement listener interfaces and provide null
implementations for aJI the methods. They are provided for listener interfaces with
more than one method, for the sake of convenience. A listener class may implement
the appropriate listener interface or extend the corresponding event adapter. The
difference is that implementing a listener interface requires all methods declared in
the interface to be implemented in the listener class, while extending the event adapter
saves the effort of implementing methods of no interest in a particular context.
The event-handling process can roughly be described as follows:

1. When an event is triggered, the Java run time first determines its source and type.
2. If a listener for this type of event is registered with the source, then an event
object is created.
3. For each listener that listens to this type of event, the Java run time invokes the
appropriate event-handling method of the listener and passes the event object as
the parameter.

The interaction among the source, its listeners, and the event objects is shown in
Figure 8.10.

Figure 8.10

The event
handling.

<<create>>
aEvent
notify(aEvent)
query()

--------- ------- ----- ---- ------


8.3 The Graphical User Interface Framewori<-AWT and Swing • 351

EXAMPLE 8 .1 0 Handling Events

PURPOSE
This example demonstrates event handling.

DESCRIPTION
This program extends the Nested.Panel class presented in Example 8.9 by adding
event handling. When one of the buttons is clicked or one of the items in the choice
is selected, a message is displayed in the message bar, which is a label. The class
NestedPanels2 listens for both types of events and listens to all the buttons and the
choice contained in the nested panel.

SOLUTION
The event class for button click is ActionEvent, and the associated listener interface
is ActionListener. The event class for choice item election i ItemEvent, and
the associated listener interface is ItemListener.

Nested ~els applet with event~: RestedPanela2


import java.awt . *;
import java . awt . event . *;

public class NestedPanels2 extends NestedPanels


implements ActionListener, ItemListener {
public NestedPanels2 () {
super () ; // create all the components
choice. addltemListener (this ); // register item listener
registerButtonHandler (this); // register action listener
}

(Event handling methods on page 35 l )


(Method registerButtonHandler O on page 352)
}

The method i temStateChanged () i declared in the ItemListener interface


for handling choice item elections. The method actionPerf armed () i declared
in the ActionListener interface for handling button click .

Event handling methods of ems NeatedPanela2


public void itemStateChanged (ItemEvent event ) {
if (event .getStateChange() == I temEvent.SELECTED) {
messageBar . setText("Choice selected : " + event .getltemO);
}
}

public void actionPerformed (ActionEvent event) {


Button source = (Button) event. get Source O ;
messageBar . setText ( "Button pushed : " + source. get Label O) ;
}
352 • Object-Oriented Application Frameworks

The registerButtonHandler () method registers this object to all the but-


tons contained in the component comp. If comp is a container, it _recursivel~ r_egisters
this object to all the components contained in the container. This method 1s mvoked
in the constructor of the NestedPanels2 class (p. 351] with the top-level panel as
the argument.

Method of class llestedPanels2: registerBut1;onHandler,


protected void registerButtonHandler(Component comp) {
if (comp != null) {
if (comp instanceof Button) {
Button button= (Button) comp;
button.addActionListener(this);
} else if (comp instanceof Container) {
Container container= (Container) comp;
int n = container .getComponentCount();
for (inti= O; i < n; i++)
registerButtonHandler(container.getComponent(i));
}
}
}

Event listener classes are usually very small classes. To avoid unnecessary pro-
liferation of small classes and files, event listener classes are commonly defined as
nested classes. Furthennore, being nested classes also gives the listener classes access
to the nonpublic fields of the enclosing class, which is often needed for handling the
events.

EXAM PL E 8 . 1 1 Event Handlers as Nested Classes


L

PURPOSE
This example demonstrates the use of nested classes as event listener classes.
DESCRIPTION
This program behaves the same as the program in Example 8.10. It uses two nested
classes: one handles the button cli°cks, and the other handles the choice item selections.
SOLUTION
Neaed panels applet lllinl nested class event listeners: NestedPanels_3
import java.awt . •;
import java . awt.event . • ;
publi c class NestedPanels3
extends NestedPanels {
public NestedPanels3() {
super();
ChoiceEventHandler cHandler = new ChoiceEvent Handler () ;
I , f • 1

8 ·3 The Graphical User Interface FramewOt'k-AWT and Swing • 353

choice . addltemListener (cHandler);


ButtonEventHandler bHandler = new ButtonEventHandler();
bHandler.registerButtonHandler(this);
}

(Nested class ChoiceEventHandler on page 353)


(Nested class ButtonEventHandler on page 353)
}

·Neste«! class ~ HHtedPanels3: ChoiceEventBandler


class ChoiceEventHandler implements ItemListener {
public void itemStateChanged(ItemEvent event) {
if (event.getStateChange() == ItemEvent.SELECTED) {
messageBar. setText ("Choice selected : " + event. get Item());
}
}
}

Nested class of HeatedPanels3: ButtonEventHandl.er


class ButtonEventHandler implements ActionListener {
public void actionPerformed (ActionEvent event) {
Button source= (Button) event .getSource();
messageBar. setText ( "Button pushed : " + source . getLabel O) ;
}

protected void registerButtonHandler (Component comp) {


if (comp != null) {
if (comp instanceof Button) {
Button button= (Button) comp ;
button.addActionListener(this);
} else if (comp instanceof Container) {
Container container= (Container) comp;
int n = container . getComponentCount ();
for (int i= O; i < n; i++)
registerButtonHandler(container.getComponent(i));
}
}
}
}

EXAM PLE 8 . 1 2 Bouncing Ball Applet with Controls

PURPOSE
This example demonstrates factorization by delegation and the u e of buttons and
choices to control animation .
354 • Object-Oriented Application Frameworks

DESCRIPTION
This is an enhancement of the bouncing ball applet in Example 5.4 using Swing
components. The layout of the applet is shown in Figure 8.11. Three GUI component
are added to the original applet to control the animation:

• A start button to start the animation .


• A stop button to stop the animation .
• A choice to choose the color of the ball .

SOLUTION
In the original bouncing ball applet, the animation occupies the entire viewing area.
The program consists of only one GUI component, the top-level panel. All methods
dealing with animation belong to the applet class. In the reusable animation applet
class AnimationApplet in Example 7.1 [p. 256], we assumed that the animation
component occupies the entire viewing area. However, in the present case, the ani-
mation component occupies only a portion of the viewing area. We need a reusable
animation component that is not tied to the top-level panel of an applet but that can be
associated with any GUI component. In this case, it is better to use delegation to refac-
tor the animation component so that the concrete animation class does not have to be
the subclass of the reusable animation class. This approach permits multiple instances
of the reusable animation class in a single program. The structure of the delegation-

Figure 8.11
Applet
The bouncing ball
applet with con-
trols.

------~;...._-----II
start

[ Applet started.
stop red
S.3 The Graphical User Interface Framcwor1<-AWT and Swing • 355

based reusable animation clas is shown in Figure 8.12. The implementation is shown
in the following program:

Del __ tor:bi■ator
import java.awt.•;

public class Animator implements Runnable {


public Animator (Component comp) {
this.comp= comp;
}
final public void setDelay(int delay) {
this . delay= delay;
}
final public int getDelay() {
return delay ;
}
public void start() {
animationThread = new Thread(this);
animationThread.start ();
}
public void stop() {
animationThread = null;
}
public void run() {
while (Thread.currentThread() = animationThread) {
try {
Thread.sleep(delay);
} catch (Interrupted.Exception e) {}
comp. repaint() ;
}
}

protected Component comp ; II the component to be animated


protected int delay= 100;
protected Thread animationThread;
}

Figure 8.12 Runnable


SuperClass Component
2S
The delegation- I

based reusable
Comp
animation class. Animator K>------1 MyComponent
start()
stop()
run()
setDelay()
getDelay()
356 • Object-Oriented Application Frameworks

Figure 8.13
I Applet
L;::.
The structure of
the bouncing ball
Animator
applet with con-
trols. I BouncingBall3 ,~ animator
start()
k>comp
iComponent
L;::..
I
()
stop()
canvas run()
setDelay()
getDelay() I JPenel j
L;::.,

BouncingBallCanvas
paint()

In the bouncing ball applet with controls, the animation component is the Bounc-
ingBallCanvas class, which extends the JPanel class in Swing. The JPanel class
supports double-buffering. The top-level applet is the BouncingBall3 class. The
structure of the entire program is shown in Figure 8.13.

Doable-butfeted camas: Bounc~lCuvu


import java.awt.•;
import javax.swing.*;
public class BouncingBallCanvas
extends JPanel {
public BouncingBallCanvas() {
super(true); // double-buffered
}

public void initCanvas() {


d = getSize O ;
x = d.width * 2 / 3 ;
y = d . height - radius;
}

public void paint (Graphics g) {


g.setColor(Color.white);
g.fillRect(O, 0, d.width, d.height);
if (x < radius I I x > d .width - radius)
dx = -dx;
if (y < radius I I y > d .height - radius)
dy = -dy;
x += dx; y += dy;
g.setColor(ballcolor);
g . fillDval(x - radius, y - radius, radius* 2, radius* 2);
}
8.3 The Graphical User Interface FrarMWork-AWT and Swing ■ 357

public void setBaUColor(Color c) {


ballcolor = c;
}

pubhl~c void setBallPosition(int x, int y) {


t is . x = x; this.y = y;
}

protected int x, y, dx = -2, dy = -4, radius 20;


protected Color ballcolor = Color .red;
protected Dimension d;
}

The BouncingBall3 class contains two nested classes for handling the events
from the start and stop buttons and the color-choice box.

B o ~ ball ap let with controls: BoUllcins@!J 13. ja.va.


import java.awt.•;
import java.awt.event . •;
import javax . swing.*;
public class BouncingBall3
extends java . applet.Applet {
public BouncingBall3 () {
setlayout(new Borderlayout ());
canvas= new BouncingBallCanvas ();
add("Center", canvas);
animator= new Animator(canvas) ;
Dimension d;
controlPanel = new JPanel();
controlPanel.setLayout(new Gridlayout(l,O)) ;
JButton startButton = new JButton (" start ");
controlPanel . add (startButton);
JButton stopButton = new JButton (" stop" );
controlPanel.add(stopButton);
JComboBox choice= new JComboBox ();
choice.addltem(" red ");
choice. addltem("green" );
choice . addltem("blue" );
controlPanel.add (choice) ;
add("South", controlPanel);
startButton.addActionListener (
new ButtonHandler (ButtonHandler .START_ANIMATION)) ;
stopButton. addActionListener(
new ButtonHandler (ButtonHandler .STOP_ANIMATION));
choice.additemListener(new ColorChoiceHandler());
}
358 • ObJcct-Orfcntcd AppllcatJon Frameworks

publi c void init() {


String att • getParameter("delay") ;
i f (at t !• null) {
i nt delay • Integer .parselnt(att) ;
animat or .setDelay(delay) ;
}
canvas . i nitCanvas();
}

publi c voi d start() {


animator .start() ;
}

publi c voi d stop () {


animator . stop ();
}

protected BouncingBallCanvas canvas ;


protected Animator animator;
protect ed JPanel controlPanel ;
protected class ButtonHandler implements Act ionListener {
static final int START_ANIMATION = 1;
static final int STOP_ANIMATION = 2;
public ButtonHandler (int cmd) {
thi s . cmd = cmd ;
}

public vo id actionPerformed (ActionEvent event ) {


S\/i tch (cmd) {
case START_ANIMATION : start () ; break ;
case STOP_ANIMATION: stop ( ) ; break ;
}
}

prote cted i nt cmd ;


}

protected class Col or ChoiceHandl er i mplements ItemLis t ener {


public void itemSt ateChanged (ItemEvent event) {
JComboBox choice = (JComboBox) event .getSource () ;
if (choice != nul l) {
if ("red".equals (event . getltem()) )
canvas.setBallColor(Color . red);
else if ("green". equals (event. get Item ()))
canvas .setBallColor (Color.gr een);
els e if ( "bl ue" . equals (event.getlt em ()))
canvas . set BallColor(Color . blue) ;
canvas . repaint () ;
}
}
}
}
- 8.3 The Graphical User Interface Framework-AWT and Swing • 359

8.3.5 Frames and Dialogs

A GUI application starts with a frame. Aframe is a top-level window with a border,
a title bar, and control buttons located at the upper comers of the frame. The main
work area of a GUI app is usually contained within a frame. A dialog is a pop-up
window that usually appears for only a short period of time and requires the immediate
attention of the user. Dialog windows are used for displaying messages or getting
input.

EXAM p LE 8 . 1 3 A Simple Online-Ordering GUI Application Using Swing


=::::..:======--
PURPOSE
This example demonstrates building GUI apps with frames and dialogs; the use
of Swing components, including image labels, borders, and bunon groups; and
custonuzed layout.

DESCRIPTION
The frame of the online ordering app contains two components: an image label, which
consists of an image and a text, and the Order button. When the Order button is pushed,
a dialog box pops up. The dialog box bas three compartments, as shown in Figure 8.14.

■ The top compartment contains text labels and text fields. The layouts of the labels
and the fields are not handled by a layout manager. Rather, a customized layout
is specified, using the absolute position and dimension of each component.
■ The nuddle compartment has a titled border with the title Credit Card. It contains
three check boxes that are exclusive.
■ The bottom compartment has an etched border. It contains the OK and Cancel
buttons. An order is completed when the OK button i pushed, and the order
information will be printed to the tandard output.

The structure of the online-ordering app i hown in Figure 8.15. The Order cla 1
the top-level class of the GUI application. It extends the JFrame class.

SOLUTION
The onllne shopein& app: Order
import java.awt. • ;
import java . awt .event.•;
import javax . sw ing . •;
public class Order extends JFrame {
publi c Order(String i mageFile) {
setTitle (imageFile);
getContentPane() .setLayout(new BorderLayout());
I con image = new Imageicon(imageFile) ;
360 ■
Object-Oriented Appllcatlon Frameworks

Figure 8.14 Frame control menu Frame title bar Frame control buttons
- - - -~ -
The online
ordering fonn.

To order the Item shown above, click ttie button

Name IMichael Owen


Addrea I123 Elmwood Street
City ILake Forest State 165431

E-Mail Imowen@aol.com
~~~ ~~- = ====~
CredttCirc:1 -=-....£.......

rettvtu !
~ !IMaiteiCarc:1 (ii1 Dlacover
1
p

8.3 The Graphical User Interface Framewor1<-AWT and Swing ■ 361

JLabel center=
new JLabel( "To order the item shown above, click the button",
image,
SwingConstants.CENTER);
center . setHorizontalTextPosition(SwingConstants.CENTER);
center . setVerticalTextPosition(SwingConstants . BOTTOM);
JPanel bottom= new JPanel();
JButton orderButton = new JButton("Order") ;
bottom.add(orderButton);
orderButton . addActionListener(makeOrderHandler());
getContentPane() . add(center, BorderLayout . CENTER) ;
getContentPane().add(bottom, BorderLayout.SOUTH) ;
addWindowListener(new AppCloser());
}

ActionListener makeOrderHandler() {
return new OrderHandler();
}

(Nested class AppCloser on page 362)


(Nested class OrderHandler on page 362)
(The main () method on page 362)
}

The AppCloser is an event listener that listens to the window events-the


events triggered by the frame menus and control buttons that usually are located
at the upper left and upper right comers of the window frame, respectively (see
Figure 8.14). The AppCloser simply terminates the program when the close window
(X) button is pushed. Without the AppCloser, pushing the close window button will
not close the window. The implementation of AppCloser here is simplistic. In real
applications, before terminating the program the user should be prompted to confirm
the action, and any unsaved data should be saved to disk to prevent data loss. Note
that the AppCloser extends the WindowAdapter class instead of implementing the
WindowListener interface.

Figure 8.15 JFrame JDialog JPanel

The structure of
the onllne
shopping app. Order K>----1 DialogPanel

OrderHandler Button Handler


- -- ----, - ------
I

ActionListener
362 ■
Object-Oriented Application Frameworks

lass of Orde;·: AppCloser


stat ic class AppCloser extends WindowAdapter {
public voi d window Closing(Wi ndowEvent e) {
System. exit (O);
}
}

The OrderHandler is the listener class of the Order button. The first time the
Order button is pushed, an instance of OrderDialog, which represents the order
dialog box, is created with the invocation of
new OrderDialog(Order . thi s )

The order dialog box becomes visible when dialog. show () is invoked. The order
dialog box is created only once. Subsequently, pushing the Order button causes the
order dialog box to reappear by invoking dialog. show () .

Nested class of Order: OrderHandler


class OrderHandler implements ActionListener {
JDialog dialog ;
public void actionPerformed(ActionEvent evt) {
if (dialog== null) {
dialog= new OrderDialog(Order . this);
}
dialog.show();
}
}

The main () method creates an instance of the order frame. The order frame
becomes visible when frame. show O is invoked.

Tbe aaiDO method of Order dais


public static void main(String[] args) {
if (args . length > 0) {
Order frame= new Order(args[O]);
frame . show();
}
}

The OrderDialog class represents a dialog box that pops up when the Order
button in the order frame is pushed. It contains the OK and Cancel buttons and the
order information panel.

Tbe order dial~ cl888: OrderDialo


import java . awt.•;
import java . awt.event. • ;
import javax.swing.•;
public class OrderDi alog extends JDialog {
JPanel bottom;
JButton okButton, cancelButton ;
8.3 The Graphical User Interface Framework-AWT and Swing ■ 363

DialogPanel dialogPanel;
public OrderDialog(JFrame owner) {
super(owner, true) ;
setTitle("Order");
okButton = new JButton("Ok");
cancelButton = new JButton ("Cancel");
ButtonHandler bHandler = new ButtonHandler ();
okButton.addActionListener(bHandler);
cancelButton.addActionListener(bHandler);
bottom= new JPanel();
bottom.add(okButton);
bottom.add(cancelButton);
bottom . setBorder(BorderFactory.createEtchedBorder() ) ;
dialogPanel = new DialogPanel();
getContentPane() . setLayout(new BorderLayout () );
getContentPane() . add(bottom, BorderLayout.SOUTH);
getContentPane ().add(dialogPanel, BorderLayout .CENTER) ;
}

(Nested class ButtonHandler on page 366}


}

The DialogPanel class represents the order-information panel that contains text
fields for the customer's name, address, and e-mail. Each text field is accompanied
by a text label. It also contains three check boxes for choosing a credit card. The
selection of the check boxes is exclusive; i.e., only one of them can be selected. The
check boxes are contained in another nested panel.

An amiliaey elm: Dialo~an•l


class DialogPanel extends JPanel {
JLabel nameLabel;
JTextField nameField;
JLabel addressLabel;
JTextField addressField;
JLabel cityLabel;
JTextField cityField;
JLabel stateLabel;
JTextField stateField;
JLabel zipLabel;
JTextField zipField ;
JLabel emailLabel ;
JTextField emailField ;
JCheckBox visaBox, mcBox, discoverBox ;
JPanel creditCard;
ButtonGroup group;
(The constructor on page 364)
(The layout methods on page 365)
(The reset() method on page 365)
}
364 • Object-Oriented Application Frameworks

The constructor creates instances of the text fields and labels. Swing components
may have borders. The panel for check boxes has a titled border. The three check boxes
are added to the same button group, so that they are exclusive.

1be c:omtrudor ofD1alo Pan:el dus


DialogPanel () {
nameLabel .. new JLabel("Name");
nameField .. new JTextField();
addressLabel • new JLabel("Address");
addressField = new JTextField();
cityLabel = new JLabel("City");
cityField = new JTextField();
stateLabel = new JLabel("State");
stateField = new JTextField();
zipLabel = new JLabel("ZIP");
zipField = new JTextField();
emailLabel = new JLabel ("E-Mail") ;
emailField = new JTextField();
creditCard = new JPanel();
visaBox = new JCheckBox("Visa", true);
mcBox = new JCheckBox("MasterCard");
discoverBox = new JCheckBox("Discover");
ere di tCard. add (visaBox) ·;
creditCard. add(mcBox);
creditCard.add(discoverBox);
creditCard.setBorder(BorderFactory.createTitledBorder(
"Credit Card"));
group= new ButtonGroup();
group . add(visaBox);
group.add(mcBox);
group .add(discoverBox);
add(nameLabel) ;
add(nameField);
add(addressLabel);
add(addressField);
add(cityLabel);
add(cityField);
add(stateLabel);
add(stateField) ;
add(zipLabel);
add(zipField);
add(emailLabel);
add(emailField);
add(creditCard) ;
}

The layout of the components contained in a container is handled by the do Lay-


out() method. By default, the doLayout O method delegates the responsibility to
the layout manager associated with the container. To create a customized layout, you
need to override the doLayout O method. Furthermore, you should also override the
8.3 The Graphical User Interface Framcwor1<-AWT and Swing • 365

get Pref erredSize O and getMinimumSize () methods to return the preferred


and minimum sizes of the container, respectively. The sizes returned by the get-
Pref erredSize O and getMinimumSize () methods should be determined by the
layout strategy implemented in the doLayout () method. The actual layout is done
in the doLayout O method. The position and dimension of each component are set
using the setBounds () methods defined in the Component class.

The la methods or Dialo~aul class


public Dimension getPreferredSize() {
return new Dimension(350, 200);
}

public Dimension getMinimwnSize() {


return new Dimension(350, 200);
}

public void doLayout() {


nameLabel.setBounds(10, 10, 60, 30);
nameField.setBounds(70, 15, 270, 20);
addressLabel.setBounds(10, 40, 60, 30);
addressField.setBounds(70, 45, 270, 20);
cityLabel.setBounds(10, 70, 60, 30);
cityField.setBounds(70, 75, 100, 20);
stateLabel . setBounds(180, 70, 40, 30);
stateField.setBounds(220, 75, 30, 20);
zipLabel.setBounds(260, 70, 30, 30);
zipField.setBounds(290, 75, 50, 20);
emailLabel.setBounds(10, 100, 60, 30);
emailField.setBounds(70, 105, 270, 20);
creditCard.setBounds(10, 140, 330, 50);
}

The reset() method clears the text fields so the same dialog panel can be used
by the next customer.

Method of claa Dialo~uel: reset{)


public void reset() {
nameField . setText( 1111 ) ;
addressField.setText( 1111 ) ;
cityField . setText( 1111 ) ;
stateField.setText( 1111 ) ;
zipField.setText( 1111 ) ;
emailField.setText( 1111 ) ;
visaBox.setSelected(true);
}

The ButtonHandler cla s i the event Ii tener cln for the buttons in the order
dialog box. If the OK button i pu hed, the order infom1ation is printed to the tand ard
output. When either the OK or the Cancel button is pushed, the text fields are cleared,
and the dialog box is closed.
366 • Object-Oriented Application Frameworks

Nested class of OrderDialog: ButtonHandler


class ButtonHandler implements ActionListener {
public void actionPerformed ( ActionEvent evt) {
JButton button= (JButton ) evt.getSource ();
String label= button .getText ( );
if ("Dk" . equals (label )) {
System.out.println (" An order is recei ved: " );
System.out . println (" \tName : " +
dialogPanel . nameField .getText( ));
System.out . println("\tAddress: "+
dialogPanel.addressField . getText());
System . out. println (" \ tCi ty: " +
dialogPanel.cityField.getText ());
System. out. println (" \ tState : " +
dialogPanel.stateField . getText( ));
System. out. pri ntln( "\tZIP: " +
dialogPanel . zipField.getText ());
System . out.println("\tE-Mail : "+
dialogPanel.emailField . getText ());
System.out . print("\tCredit card: ");
if (dialogPanel.visaBox.isSelected()) {
System.out.println("Visa") ;
} else if (dialogPanel.mcBox.isSelected()) {
System.out.println( "MasterCard") ;
} else if (dialogPanel.discoverBox.isSelected()) {
System . out . println("Discover" );
}
}
dialogPanel.reset();
set Visible (false) ; // close the dialog box
}
}

8.4 THE INPUT/OUTPUT FRAMEWORK

The Java input/output framework is designed to be flexible and easily configurable.


Java supports two types of input/output (I/0):

1. Stream 1/0: A stream is a sequence of bytes. Stream-based 1/0 supports reading


or writing data sequentially, that is, reading or writing data successively in one
direction. A stream may be opened for reading or writing, but not for both reading
and writing.
2. Random Access 1/0: Random access I/0 supports reading and writing d ata at
any position of a file . A random access fil e may be opened for both reading and
writing.
8.4 The Input/Output Framework • 367

There are two kinds of streams: byte streams and character streams. Byte streams
support reading and writing data of any type, including strings, in the binary format.
Character streams support reading and writing of text, using locale-dependent char-
acter encodings.

8.4.1 Byte Streams

The most basic and primitive stream 1/0 capabilities are declared in two abstract
classes: InputStream and OutputStream. They support reading and writing of a
single byte and a sequence of bytes in an array. The methods for reading and writing
are summarized in the following table. In these methods, b is a byte, ba is a byte
array, off and len are integers that specify a segment of an array; off represents the
offset (i.e., the starting index of the segment), and len represents the length (i.e., the
number of bytes in the segment); n is a long integer.

Input Method Output Method Description

read() write(b) Reads/writes a single byte


read(ba) write (ba) Reads/writes an entire byte array
read(ba,off,len) write(ba,off,len) Reads/writes a segment of the byte
array
skip(n) Skips over n bytes
close() close() Closes the stream

Two of the concrete classes for byte stream I/0, which extend the two abstract
classes, are FileinputStream and FileOutputStream, as shown in Figure 8.16.
Instances of these two classes can be constructed by pecifying the filename.

Figure 8.16 OutputStream


lnputStream
The file Input
stream and the file
output stream.
FilelnputStream FileOutputStream

_ .... I
368 • Object-Oriented Application Frameworks

Constructor Description

FileinputStream(filename) Create, an input trcam reading


from the file named filename . An
IOException i thrown if the file
does not exist.
FileOutputStream (filename) Create, an output stream writing
to the file named filename . If the
file does not exist, a new file is
created. If the file already exists,
the original file is overwritten.
FileOutputStream(filename, append) Create, an output stream writing to
the file named filename . lf the file
does not exist, a new file is created.
If the file already exists, the new
data will be appended to the end
of the original file if the boolean
flag append is true ; otherwise,
the original file is overwritten.

The primitive I/O capabilities provided in these classes make reading and writing
data of any type possible, but hardly convenient.

EXAM PLE 8 . 1 4 Using Basic Byte 1/0 Streams

PURPOSE
This example demonstrates reading and writing a two-dimensional matrix using the
primitive I/O capabilities provided in the FileinputStream and FileOutput-
Stream classes.

DESCRIPTION
Wri teMatrix1 writes a 2 x 3 matrix to a file. It first writes the number of rows and
column of the matrix, followed by the numbers in the matrix. ReadMatrix1 reads
the file written by Wri teMatrix1 and restores the matrix.
SOLUTION
The data to be written to file are defined in the following interface:

Matrix data
public interface MatrixData {
double [][] data= {
8.4 The Input/Output Framework • 369

{ Math.exp(2.0), Math.exp(3 .0) , Math .exp(4 .0) },


{ Math .exp(-2 .0), Math.exp(-3.0), Math.exp(-4 .0) },
} ;
}

In the following Wri teMatrix1 class, the wri teint () method converts an
integer value to a byte array and writes the byte array to the output stream. Similarly,
the wri teDouble O method converts a double value to a byte array and writes the
byte array to the output stream.

'Class WriteMatrix1
import java.io .• ;
public class WriteMatrixl implements MatrixData {
public static void main(String[] args) {
int row= data.length;
int col= data[O] . length;
inti, j;
for (i = O; i < row; i++) {
for (j = O; j < col; j++) {
System . out . println( 11 data[" + i + "] [ 11 + j + "] = II +
data[i] [j]);
}
}

if (args.length > 0) {
try {
FileOutputStream out= new FileOutputStream(args[O]);
write!nt(row, out);
write!nt(col, out);
for (i = O; i < row ; i++) {
for (j = O; j < col; j++) {
writeDouble(data[i] [j], out);
}
}
out.close();
} catch (! □Exception e) {
e.printStackTrace() ;
}
}
}

public static void writeint (int i, OutputStream out)


throws IOException {
byte[] buf = new byte(4] i
for (int k = 3 ; k >= 0 ; k--) {
buf[k] = (byte)(i &OxFF);
i >»= 8;
}
out.write(buf) ;
}
370 • Object-Oriented Application Frameworks

public static void writeDouble(double d, OutputStream out)


throws IOException {
byte(] buf = new byte(8];
long 1 = Double.doubleToLongBits(d);
for (int k = 7 ; k >= O; k--) {
buf(k] = (byte)(l & OxFF);
1 »>= 8;
}
out . write(buf);
}
}

Running this program creates a file named data! . out and produces the follow-
ing output:

venusi. java WriteMatrix1 data1.out


data(O] (O] = 7.38905609893065
data[O] [1] = 20.085536923187668
data[O] [2] = 54.598150033144236
data(1] [OJ = 0.1353352832366127
data[1] [1] = 0.049787068367863944
data[1] [2] = 0.01831563888873418

In the following Read.Matrix! class, the readint () method reads 4 bytes from
the input stream into a byte array and converts the byte array to an integer value.
Similarly, the readDouble O method reads 8 bytes from the input stream into a byte
array and converts the byte array to a double value.

a- BeadNatrist
import java.io.•;
public class ReadMatrixl {
static double[][] data;
public static void main(String[] args) {
if (args . length > 0) {
try {
FileinputStream in= new FileinputStream(args(O]) ;
int row = readint(in);
System . out .println( 11 row = 11 + row);
int col= readint(in);
System.out.println( 11 col = 11 + col);
data • new double[row] [col];
for (inti= 0; i < row; i++) {
for (int j • O; j < col ; j++) {
data[i] (j] • readDouble(in);
System.out .println( 11 data[ 11 + i + "] (" + j + "] = 11 +
data(i] [j]);
}
}
.--
8.4 The Input/Output Framework
• 371

} catch (!□Exception e) {
e .printStackTrace () ;
}
}
}

public static int readint (InputStream in)


throws !□Exception {
byte[] buf = new byte[ 4].
in.read(buf); '
inti= O;
for (int k = O; k < 4; k++) {
i «= 8 ;
i += ( ( (int) buf[k]) & OxFF);
}
return i;
}

public static double readDouble(InputStream in)


throws !□Exception {
byte[] buf = new byte(8] ;
in . read(buf);
long 1 = O;
for (int k = O; k < 8; k++) {
1 «= 8;
1 += (((int) buf(k]) & OxFF);
}
return Double.longBitsToDouble (l) ;
}
}

Running this program with the datal. out file produced by Wri teMatrix1 as
the input produces the following output:

venus¾ java ReadMatrix1 data1 .out


row = 2
col = 3
data(O] [OJ = 7. 38905609893065
data(O] (1) = 20 . 085536923187668
data[O] (2) = 54 . 598150033144236
data[!] [OJ = 0 .1353352832366127
data(l] (1) = 0 .049787068367863944
data(!] (2) = 0 . 01831563888873418

Data Input and Data Output Streams


Such laboring of reading and writing data by u ing bytes is rarely necessary. The
methods for reading and writing data of variou types are declared in two interfaces,
372 ■ Object-Oriented Application Frameworks

Datainput and DataOutput, respectively. These methods are summarized in the


following table:

Input Method Output Method Description

read.Boolean() writeBoolean (b) Reads/write a boolean value


read.Byte() writeByte(b) Reads/writes a byte value
readChar () writeChar (c) Reads/writes a char value
readDouble () writeDouble (d) Reads/writes a doubl e value
readFloatO writeFloat (f) Reads/writes a float value
readint () wri teint (i) Read /writes an int value
readLongO writeLong (l) Reads/writes a long value
readShort 0 writeShort(s) Reads/writes a short value
readUTF () writeUTF(s) Reads/writes a String value

Two concrete classes, DatainputStream and DataOutputStream, imple-


ment the Data!nput and DataOutput interfaces, respectively, as illustrated in Fig-
ure 8.17. The DatainputStream and DataOutputStream are defined as filters, or

Figure 8.17
lnputStream Datalnput
,5
The data input I
I
stream and the I
I
I
data output I
FilelnputStream FilterlnputStream I
stream. I
I
I
I
I
I
I
I
OatalnputStream -----------'

OutputStream DataOutput
,5
I
I
I
I
I
I
FileOutputStream FilterOutputStream I
I
I
I
I
I
I
I
I
DataOutputStream -------- - - '
8.4 The Input/Output Framework • 3 73

decorators· that is they tak 1/0 . . .. ...


. ' • . e streams with pmrutive 1/0 capabilities and transform
th
em m~o ~O streams with data input and output capabilities. The Decorator design
pattern is discussed on page 380.

Constructor Description

DatainputStream(in) Creates a data input stream that reads data from the
specified input stream in
Data0utputStream(out) Creates a data output stream that writes data to the
specified output stream out

Ex AM p LE 8. 1 5 Using Datainput and Data0utput Streams

PURPOSE
This example demonstrates reading and writing a two-dimensional matrix using the
1/0 decorators DatainputStream and Data□utputStream.
DESCRIPTION
The Wri teMatrix2 and ReadMatrix2 classes perform the same tasks as the
Wri teMatrix1 and ReadMatrixl classes in Example 8.14. The main differences
are the construction of the data input and output streams, which are shown in boldface
in the following programs. The wri teint (), wri teDouble O, readint O , and
readDouble () methods in the previous examples are no longer needed.

SOLUTION
Class WriteMatrix2
import java .io.• ;
public class WriteMatrix2 implements MatrixData {
public static void main(String[) args) {
int row= data.length;
int col= data[0) .length;
int i, j;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
System.out .pri ntln ( "data [ II + 1• + ") [ " + J. + 11) : II +
data[i) [j));
}
}

if (args.length > 0) {
try {
DataOutputStream out• .
new OataOutputStream(new FileOutputStream(args[O])),
374 ■
Object-Oriented Application Frameworks

out.writelnt (row);
out . writelnt (col) ;
for ( i = O; i < r ow; i++ ) {
for (j = O; j < col ; j++) {
out.writeDouble (data[i] [j] );
}
}
out.close();
} catch (!□Exception e) {
e.printStackTrace();
}
}
}
}

Chm ReadMatrix2
import java.io.*;
public class ReadMatrix2 {
static double[] [] data;
public static void main(String[J args) {
if (args.length > 0) {
try {
DatainputStream in=
nev DatainputStream(new FileinputStream(args[O]));
int row= in.readlnt();
System.out . println( 11 row = 11 + row);
int col= in.readint();
System.out.println( 11 col = 11 + col);
data= new double[row] [col];
for (inti= O; i < row; i++) {
for (int j = O; j < col; j++) {
data[i] [j] = in.readDouble();
System.out.println( 11 data[" + i + "] [ 11 + j + 11 ] = 11 +
data[i] [j]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

This example produces the same output as Example 8.14.


8.4 The Input/Output Framework • 37 5

Buffered Input and Output Streams

The Buff eredinputStream and BufferedOutputStream classes are filter


streams that support buffered 1/0. The relationships among the related classes are
shown in Figure 8.18. They take 1/0 streams and transform them into buffered I/0
streams. The constructors are shown in the following table:

Constructor Description

BufferedinputStream (in) Creates a buffered input stream that reads data


from the specified input stream in
Buff eredOutputStream (out) Creates a buffered output stream that writes data
to the specified output stream out

Figure 8.18
InputStream Datalnput
The buffered input ~
I
I
stream and the I
I
buffered output I

stream. FilelnputStream FilterlnputStream I


I
I
I

BufferedlnputStream DatalnputStream

OutputStream Data Output

FileOutputStream FilterOutputStream

DataOutputStream
BufferedOutputStream
376 • Object-Oriented Application Frameworks

EXAM P LE 8 . 1 6 Using Buffered 1/0 Streams

PURPOSE
This example demonstrates the use of buffered input and output streams.

DESCRIPTION
The Wri teMatrix3 and ReadMatrix3 classes perform the same tasks as in Exam-
ples 8.14 and 8.15. The main differences are the construction of the buffered input
and output streams, which are shown in boldface in the following programs.

SOLUTION
Caa Vrit•Matrix3
import java.io . •;
public class WriteMatrix3 implements MatrixData {
public static void main(String[] args) {
int row= data .length;
int col= data[O] . length;
inti, j;
for (i = O; i < row; i++) {
for (j = O; j < col; j++) {
System.out.println("data[" + i + "] [" + j + "] = 11 +
data[i] [j]);
}
}

if (args.length > 0) {
try {
DataOutputStream out~
nev DataOutputStream(
nev BufferedOutputStream(
nev FileOutputStream(args[O])));
out.vritelnt(row);
out . writelnt(col);
for (i = O; i < row; i++) {
for (j = O; j < col; j++) {
out.writeDouble(data[i] [j]);
}
}
out .close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-
8.4 The Input/Output Framework ■ 377

Class ReadMatrix3
import java.io.•;
public class ReadMatrix3 {
static double[)[) data ·
'
public static void main(String[) args) {
if (args.length > 0) {
try {
DatainputStream in.
nev DatainputStream(
nev BufferedinputStream(
. nev FileinputStream(args[O))));
int row= in . readint() ;
System . out .println ( "row ="+row);
int col= in.readint();
System . out .println (" col = " +col) ;
data= new double[row) [col];
for (inti= O; i < row; i++) {
for (int j = O; j < col; j++) {
data[i] [j] = in . readDouble ();
System . out .println ("data[" + i + " ] [" + j + "] = " +
data[i] [j]);
}
}
} catch (!□Exception e) {
e.printStackTrace();
}
}
}
}

This example produces the same output as Example 8.14.



Object Streams
Java supports object serialization. It involves two proce se :
1. Serializing: To write an object and all the objects that are directly or indirectly ,
referenced by the object3 to a stream (i.e., a sequence of byte ).
2. Deserializing: To restore an object and all the objects that are directly or indirectly .
referenced by the object from a stream that is the re ult a serialization.
Instances of classe that implement the Serializable interface can be erialized.
Many classes in the Java 2 Platform cla librarie can be erialized, including most

3. All the object that are directly or indirectly referenced by a given object o along with the reference
relation among the e objects fonn what is known as the object graph of o.

. ..
1";'

,..,,,., I I

378 ■
Object-Oriented Application Frameworks

Figure 8.19
InputStream Data Input
LS
The object input
stream and the ob-
ject output stream.
FilelnputStream ObjectlnputStream ------~ Objectlnput

OutputStream Data Output


2S

FileOutputStream ObjectOutputStream ---- ~ Objecioutput I

of the classes in j ava. lang and the collection classes. Arrays can also be serialized
as long as the elements are serializable.
The methods for serializing and deserializing are declared in the Obj ectinput
and Obj ectOutput interfaces, which extend Datainput and DataOutput, respec-
tively. The input method is readOb j ect () ; the output method is wri teOb j ect ().
The Obj ectinputStream and Obj ectOutputStream are two UO filter classes
that implement the Objectinput and ObjectOutput interfaces, respectively, as
shown in Figure 8.19. The constructors are described in the following table:

Constructor Description

Obj ectinputStream (in) Creates an object input stream that reads data from
the specified input stream in
Obj ectOutputStream (out) Creates an object output stream that writes data to
the specified output stream out

EXAM PLE 8 . 1 7 Using Object Serialization

PURPOSE
This example demonstrates reading and writing a two-dimensional matrix using
object serialization.

DESCRIPTION
The Wri teMatrix3 and Read.Matrix3 classes perform the same tasks as in Exam-
ples 8.1 4-8. 16.
8.4 The Input/Output Framework • 379

SOLUTION

Using object serialization, we can easily read and write the entire array with a single
method invocation.

Class WriteHatrix4
import java . io .•;

public class WriteMatrix4 implements MatrixData {


public static void main(String[] args) {
int row= data.length;
int col= data[0) .length;
inti, j;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
System .out.println( 11 data[ 11 + i + 11 ) [ 11 + j + 11 ) = II +
data[i] [j]);
}
}
if (args . length > 0) {
try {
0bject0utputStream out•
new 0bject0utputStream(new FileOutputStream(args[0]));
out.write0bject(data);
out. close() ;
} catch (I0Exception e) {
e.printStackTrace ();
}
}
}
}

Claa ReadMatrix4
import java.io .*;
public class ReadMatrix4 {
static double[][] data ;
public static void main(StringO args) {
if (args . length > 0) {
try {
0bjectinputStream in• [OJ)) ·
new 0bjectinputStream(nev FilelnputStream(args '
data. (double(](]) in.read0bject();
int row = data .length;
int col= data [0] . length;
for (int i= 0; i < row ; i++){
for (l· nt J. = 0; J. < col; j ++){ 11 +
System .out .println(11data(11 + i + "] (11 + j + 11) =
data (i](j ] );
}
}
380 ■
--
Object-Oriented Application Frameworks

} catch (Exception e ) {
e . printStackTrace() ;
}
}
}
}

8.4.2 Design Pattern: Decorator

The design of the Java input/output framework illustrates the Decorator design pattern.
The basic byte-based 1/0 capability is provided by the InputStream and Output-
Stream classes. Also, there are many add-on features that enhance the basic 1/0
capability, such as reading and writing data of primitive types, read and write objects,
buffered read and write, compression and decompression of data, encryption and de-
cryption of data, and so on. These add-on features can be combined in many different
ways, and in different contexts we may need to use different combinations of them.
One approach to satisfying all the potential needs is to extend the InputStream and
0utputStream classes to add these features. A large number of classes are necessary
to cover every possible combination of these add-on features , such as
■ BufferedDatainputStream,
■ EncryptedDatainputStream,
■ CompressedinputStream,
■ EncryptedCompressedDatainputStream,
■ and so on.
An alternative approach is to define each add-on feature as a separate class known as
a decorator, or filrer, 4 that adds a feature to the basic I/O capability through object
composition. A particular combination of the add-on features is composed by adding
the corresponding decorators of these add-on features to the basic I/O capability, as
m
new Decorator2(new Decorator} (inputStream))

This approach avoids an explosion of the number of classes and provides flexibility
through object composition.

Design Pattern Decorator


Category: Structural design pattern.
/111e111: Attach additional responsibilities or capabilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending
functionality.

4. Decorator seems 10 be a belier name than filter because decorate connotes addi ng, whereas filrer
connotes removing.
8.4 The: Input/Output Framework ■ 381

Applicability ·· The Decorator design pattern should be used


• to add responsibilities t · ct· · .
ing other obie t . h o m iv1dual obJects dynamically without affect-
J c s m t e same class.

• for responsibilities that can be withdrawn


• when extension by sub 1 . . . ·
of possible inde enden~ assm~ is impr~ctical owing to a large number
of subclasse t p extensions, which would produce an explosion
s O support every combination.

The structure of the Decorator des1gn


· pattern is shown in the following diagram:

Component "
operation()

I
t I o Component
ConcreteComponent Decorator
operation() operation() 0 · · · ----\ component .operation()

I
t I
ConcreteDecoratorA ConcreteDecoratorB
-
operation() operation() 0
added State addedOperation() \

'\
super.operation()
addedOperation()

The participants of the Decorator design pattern are as follow :

Component (e.g., I nput St re am), which define the interface for objects that
can have re ponsibilitie added to them dynamically.
Co11creteCompone11t (e.g., FileinputStream), which define an object to
which additional respon ibilitie can be added.
Decorator (e.g., FilterinputStream), which maintain a reference to a
component object and defines an interface that confonns to the component's
interface.
ConcreteDecorator (e.g., DatainputStream and Buff eredOutputStream),
which adds responsibilities to the component.

For a detailed di cussion of the Decorator design pattern, see Gamma et al.
[ 1995].
382 • Object-Oriented Application Frameworks

8.4.3 Character Streams

In Java programs, strings are repre ented internally in Unicode, in which all char-
acters are encoded in two bytes. Externally, text fil es are stored in various character
encodings supported by the platfonn on which the programs are running . The default
character encoding of a machine is often detennined by the machine's locale . A locale
is a geographic region that share the same language and customs. For example, in
the United States, the default character encoding is ISO-8859-1, commonly known as
the ASCII code, in which all characters are encoded in a single byte. ln the People's
Republic of China, the default character encoding is GB-2312, in which all Latin
characters are encoded in a single byte, and all Chinese ideographs are encoded in
two bytes.
The character-based 1/0 streams perfonn conversions between the Unicode and
the locale-sensitive character encodings when reading and writing strings. In other
words, character streams are locale sensitive, but byte streams are not.
Two abstract classes, Reader and Writer, are the roots of inheritance hierar-
chies of the character-based input and output streams, respectively. The relationships
among the key character 1/0 streams are shown in Figure 8.20. The Reader and
Writer classes provide the basic capabilities for reading and writing characters. Their
methods are summarized in the following table. The parameter c represents a char-
acter, ca represents a character array, and off and len are integers that specify a

Figure 8.20
Reader
The character-
based readers and
writers.
InputStream lnputStream Reader Buffered Reader

File Reader

Writer

t
OutputStream OutputStream Reader BufferedWriter

File Writer PrintWriter


8.4 The Input/Output Framework • 383

segment of an array; off represents the offset (i.e., the starting index of the segment),
and len represents the length (i.e., the number of elements in the segment).

Input Method Output Method Description


read () write(c) Reads/writes a single character
read (ca) write (ca) Reads/writes an entire character
array
read(ca,off,len) write(ca,off,len) Reads/writes a segment of the
character array
close() close () Closes the stream

The InputStreamReader and 0utputStreamWriter are two filters that serve


as bridges between byte-based I/O and character-based I/O. The constructors of these
two classes are summarized in the following table. The parameter encodi ng is a
string that denotes the character encoding used by the reader/writer. For example, the
IS0-8859-1 encoding for English is denoted "8859_1, " and the GB-2312 encoding
for simplified Chinese is denoted "GB2312. 11

Constructor Description

InputStreamReader ( in) Creates an input stream reader


that reads data from the pecified
input stream in and use the default
character encoding
InputStreamReader (in, encoding) Creates an input stream reader that
reads data from the specified input
tream in and uses the pecified
character encoding

OutputStreamWriter (out) Create an output tream writer that


writes data to the pecified output
tream out and use the default
character encoding

OutputStreamWriter(out, encoding) Create an output tream writer that


write data to the pecified output
tream out and u es the specified
character encoding
., ,I d

384 • Object-Oriented Application Frameworks

The FileReader and FileWri ter are two concrete character streams that
provide the basic character-ha ed VO capabilities. The constructors of these two
clas es are summarized in the following table:

Constructor Description

FileReader (filename) Creates a file reader readjng from the file


named filename. An I □ Exception is
thrown if the file does not exist.
FileWri ter (filename) Creates a file writer writing to the file named
filename . If the file does not exist, a new
file is created. If the file already exists, the
original file is overwritten.
FileWri ter(filename, append) Creates a file writer writing to the file
named filename . If the file does not exist,
a new file is created. If the file already
exists, the new data wiJl be appended to the
end of the original file if the boolean flag
append is true; otherwise, the original file
is overwritten.
--------------.e::i_____ i c = , , __ _ _ __,,,._ _ ~

The BufferedReader and Buff eredWri ter are two filters that support buff-
ered character-based 1/0. The constructors of these two classes are summarized in the
following table:

Constructor Description

BufferedReader(reader) Creates a buffered reader that reads data from the


specified reader
BufferedWriter(writer) Creates a buffered writer that writes data to the
specified writer

The Buff eredReader class adds the method readLine (), which reads a line
of text.
PrintWri ter is a useful filter class that supports writing values of various data
types by converting them to their string representations. The PrintWri ter class has
the following constructor:

PrintWriter(writer)
8.4 The Input/Output Framework • 385

which creates a print writer that writes data to the specified writer. The PrintWriter
class provides the following two methods, which are overloaded on all data types; i.e.,
the parameter v can be of any type.

Method Description

print ( v) Prints the string representation of v


println ( v) Prints the string representation of v followed by a new line

The string representations of values of primitive types are formatted according to


the usual and customary conventions. The string representations of objects are defined
by the to String () method of their classes.

Ex AM P LE 8 . 1 8 Using Character Streams

PURPOSE
This example demonstrates reading and writing a two-dimensional matrix using
character-based 1/0 streams.

DESCRIPTION
To read and write data using strings, all data must be converted to and from their string
representations. To write data, we must insert delimiters between consecutive values.

SOLUTION
The Wri teMatrix5 class writes the two-dimensional array to a text file. Each line
contains a single value. The first two lines contain the number of rows and columns
followed by the values in the matrix.

Class WriteMe.trix5
import java . io.•;
public class WriteMatrixS implements MatrixData {
public static void main(String(] args) {
int row = data .length;
int col= data(O] .length ;
inti, j;
for (i = O; i < row; i++) {
for (j = O; j < col ; j++) {
System . out .println ( ll data (11 + 1. + 11) [" + j + 11) : II +
data[i] [j]);
}
}
C
386 ■
..
Object-Oriented Application Frameworks

if (args . length > 0) {


try {
PrintWriter out=
new PrintWriter (
new BufferedWriter (
new FileWriter (args[O])) );
out .println(row);
out.println(col);
for (i = O; i < row; i++) {
for (j = O; j < col; j++) {
out.println(data(i] (j]);
}
}
out . close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

The ReadMatrix5 class reads the text file written by Wri teMatrix5 and re-
stores the array.

Class leadNatrix5
import java.io.•;
public class ReadMatrix5 {
stati c double[] 0 data;
public static void main(String(] args) {
if (args.length > 0) {
try {
BufferedReader in=
new BufferedReader(
new FileReader(args[O])) ;
String line;
line= in.read.Line();
int row= Integer.parselnt(line) ;
System . out .println( 11 row = 11 + row);
line= in.readLine();
int col= Integer . parselnt (line) ;
System.out . println( 11 col = 11 + col);
data z new double[row] [col];
for (inti= O; i < row; i++){
for (int j = O; j < col; j++){
line= i n .read.Line ();
data[ i ] (j] = Double .valueOf(line) . doubleValue();
System.out.println("data[ 11 + i + 11 ] ( 11 + j + 11 ] = 11 +
data(i] (j]);
}
}
8.4 The Input/Output Framework • 387

} catch (!□Exception e) {
e . printStackTrace() ;
}
}
}
}

Character streams are also able to use character encodings that are different from
the default character encoding.

EXAMPLE 8 . 1 9 A Universal Text Viewer

PURPOSE
This example demonstrates the use of character-based readers to view text files in any
character encoding.
DESCRIPTION
This GUI application makes use of Swing components. It consists of an instance of
JTextArea inside an instance of JScrollPane, which allows the text area to be
scrolled both vertically and horizontally. The program expects two arguments: the
text file name and the character encoding.
SOLUTION
Class UniveraalTextViewer
import java.avt.*;
import java.avt.event .*;
import java.io.*;
import javax.swing .*;
public class UniversalTextViewer extends JPanel {
public UniversalTextViewer(String filename, String enc) {
setLayout(new BorderLayout ()) ;
JTextArea textArea = new JTextArea (40, 80);
textArea . setEditable (false );
textArea . setFont (new Font ( "Monospaced", Font .BOLD, 16)) ;
add(new JScrollPane(textArea) , BorderLayout . CENTER) ;
try {
BufferedReader in=
new BufferedReader (
new InputStreamReader (new Filel nputStream(filename) ,
enc));
String l i ne ;
while ((line = in.readLi ne ()) ! = null ) {
textArea . append(line + 11 \n");
}
} catch (IOE.xcept i on e) {
e . printStackTrace ( ) ;
}
}
d

388 • Object-Oriented Application Frameworks

public static void main (String args[) ) {


if (args.length >= 2) {
JFrame frame= new JFrame ();
frame.setTitle("Universal Text Viewer: 11 +
args[0) + 11 [ 11 + args[l) + " ) " );
frame.getContentPane().setLayout(new BorderLayout ( ));
frame . getContentPane ( ) .add(new UniversalTextViewer(args[O],
args(l]), BorderLayout . CENTER);
frame.addWindowListener(new AppCloser());
frame . setSize(600, 400);
frame . show() ;
}
}

protected static final class AppCloser extends WindowAdapter {


public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
}

The universal text viewer can be invoked as


venusi. java UniversalTextViewer filename encoding

Figure 8.21 shows a screen shot of the universal text viewer presenting a text file
in simplilied Chinese (GB2312 encoding).5

5. For the Chinese cbaracLers Lo be displayed properly, Chinese fonLS must be installed and the
font . properties file of the JDK or JRE musl be configured to use the appropriate fonts . See the
on line supplemeol of the book for details of setting up the fonts.

r, - - - --. -- - - - - -- - -
Figure 8.21
!iS14if@iiiit!titi\Mhiili11t·l•i·IWM=fMtl--- -1 DIX I
The universal text
I •
viewer. I
I fi

~-IQJ

•±•~~~~~ "~~~~~~
8.4 The Input/Output Framework ■ 389

8 .4.4 Random Access Files

Random access files support reading and writing data at any position in the files
and can support reading and writing at the same time. The RandomAccessFile
cla s provides the basic capabilities of random-access 1/0. The constructor of the
RandomAccessFile class is the following:

Constructor Description

RandomAccessFile (filename, mode) Creates a random-access file that reads


from, and optionally writes to, the file
named filename . The mode argument
is a string whose value must be either
"r", for read-only. or "rw" , for read-
write.

The RandomAccessFile class implements both Datainput and DataOut-


put interfaces. Moreover, it supports the following methods for moving the current
read/write position. The read/write position is the location in the file where the next
read or write operation will occur. In the parameters of the following methods, i i
an integer, and 1 is a long integer.

Method Description

seek (l ) Moves the read/write po ition to the 1-th byte counting from the
beginning of the file
ski pBytes ( i) Move the read/write po ition i bytes relative to the current
po ition; move forward if i > 0 and move backward if i < 0

EXAMPLE 8 . 2 0 Store Serialized Objects in Random-Access Files

PURPOSE
Thi example demon trate the u e of a random-acce file.

DESCRIPTION
The RandomAc cessFile cla doe not implement the Obj ectinput and the Ob-
j ect0utput interface . So erialized object cannot be directly written to rando_m -
acce file . In thi example, we extend the RandomAccessFile to upport reading
and writing erialized object in random-acce file .
390 • Object-Oriented Application Frameworks

SOLUTION
Because serialized objects vary in size, for each object, we first store its size as
an integer (4-byte) and then store the serialized object itself. The wri t eObj ect 0
method first serializes the object in a byte array and copies the array to the fi le, and then
the total number of bytes written to the file (including the size count) is returned . The
readOb j ect O method reads a serialized object at the current read/write positio n.
It first reads the size count and then reads the serialized object into a byte array and
deserializes the object.

<lass Obj ectRandollAcceaaFil•


import java . io .• ;
public class 0bjectRandomAccessFile extends RandomAccessFile {
protected ByteArray□utputStream b0ut;
protected byte O buf;
public 0bjectRandomAccessFile (String name, String mode )
throws ! □Exception {
super(name , mode);
b0ut = new ByteArray□utputStream ( );
}

public int wri te0bject(0bject obj) throws r □Exception {


if (obj != null &&
obj instanceof Serializable) {
0bjectOutputStream obj0ut = new 0bjectOutputStream (bOut) ;
objOut.write0bject (obj) ;
i nt count= bOut . size () ;
byte D buf = bOut. toByteArray ();
write!nt(count);
write(buf , 0, count);
bOut . reset O ;
return count+ 4 ;
} else {
return O;
}
}

public Object readObject() throws I0Exception ,


ClassNotFoundException {
int count = readint () ;
if (buf == null I I
count> buf . length) {
buf = new byte[count];
}
read (buf, 0, count );
ObjectinputStream obj in =
new 0bjectinputStream (new ByteArrayinputStream (buf , 0, count) );
Object obj= objin .read0bject() ;
objin . closeO;
return obj;
}
}
8.4 The Input/Output FraJMWork • 3 91

The following simple test program wntes


. three senahzed
. . .
stnngs .
mto a random
acces file :

Test of the vriteObj ect O method of Obj ectRandomAccea■File


import java.io.•;
public class TestWrite {
public static void main(String[) args) {
if (args .length > O) {
try {
ObjectRandomAccessFile out=
new ObjectRandomAccessFile (args [O], "rv" );
Object[] obj = {"Tic", "Tac", "Toe" } ;
long offset= O;
int count;
for (int i = O; i < obj.length; i++) {
count= out .writeObject(obj(i]);
System.out . println(obj(i] +"written at offset"+
offset+" size= "+ count);
offset+= count;
}
} catch (IOException e) {
e .printStackTrace ();
}
}
}
}

The output is
venus¾ java TestWrite obj.out
Tic written at offset O size= 14
Tac written at offset 14 size= 14
Toe written at offset 28 size= 14

The following is a simple test program that read the random-acce s file created
by TestWrite. It expects the first argument to be the file name and the following
arguments to be integers specifying the offset at which the object are to be read.

Test or the readObj ect () method of ObjectRandollAcceasFile


import java.io.*;
public class TestRead {
public static void main (String(] args) {
if (args.length > 0) {
try {
ObjectRandomAccessFile in=
new ObjectRandomAccessFile (args (O] , "r") ;
Object obj;
long offset; .
for (inti= 1; i < args . length ; 1++) {
offset= Long . parseLong(args[i]) ;
in.seek(offset);
392 • Object-Oriented Application Frameworks

obj= i n.roadObjec t ( );
System. out . println (obj +"read at off set " + offset) ;
}
} catch (Exception e) {
e . printStackTrace() ;
}
}
}
}

The output is

venus¾ java TestRead obj . out 28 14 O


Toe read at offset 28
Tac read at offset 14
Tic read at offset 0

"CHAPTER SUMMARY

■ An object-oriented application framework is a set of cooperating classes that rep-


resent reusable designs of software systems in a particular application domain. It
typically consists of a set of abstract classes and interfaces that are parts of semi-
complete applications that can be specialized to produce custom applications.
The key characteristics of application frameworks are extendability, inversion of
control, and use of design patterns.
■ Although both design patterns and frameworks are mechanisms used to cap-
ture reusable designs, they are quite different. Design patterns are schematic
descriptions of reusable designs that are not concrete programs and are language
independent. In contrast, frameworks are semicomplete applications that are writ-
ten in a specific program.ming language.
■ A collection is an object that contains other objects, which are called the elements
of the collection. The collections framework is a set of interfaces and classe
that support storing and retrieving objects in collections of varying structures,
algorithms, and time-space complexities. The collections can be grouped in the
following abstract collection categories:
A bag is an unordered collection of elements that may contain duplicate
elements. Bags are also known as multisets.
A set is an unordered collection of elements with no duplicate elements.
A sorted set is a set whose elements are automatically sorted according to
a certain sequence.
Chapter Summary • 393

A list is an ordered collection of elements that allows duplicate elements.


Lists are also known as sequences.
A map is an unordered collection of key-value pairs. The keys in a map
must be unique. Maps are also known as functions, dictionaries, or
associative arrays.
Each of the abstract collections can be implemented by using different data
structures and algorithms.
■ The Iterator design pattern is used to provide a uniform way to iterate through
different concrete collections (i.e., polymorphic iteration). Two iterator interfaces
are defined: Iterator and Listiterator.
■ The graphical user interface (GUI) framework consists of the Abstract Windows
Toolkit (AWT) and the Swing portion of the Java Foundation Classes. It is a set
of interfaces and classes that support the construction of graphical user interfaces
with a great deal of versatility.
■ GUI components are the building blocks of the visual aspect of graphical user
interfaces. All Java GUI component classes are subclasses of the Component
class. These subclasses are divided into two groups: primitive components, which
do not contain other components, and containers, which may contain other
primitive components and containers.
■ Each container has a layout manager, which handles the layout of the compo-
nents contained in the container. Each layout manager defines a layout strategy.
Commonly used layout managers include FlowLayout, BorderLayout, and
GridLayout.
■ GUI components communicate with other applications through events, which
represent user input or actions. The source of an event is the component from
which the event originated. A listener of an event is an object that receives and
processes the event. Events are classified as different types, with each type of
event represented by an event class. Each type of event is also associated with
a listener interface, which the listeners of this type of event must implement.
Listeners must be registered to their respective sources before they can receive
events from their sources.
■ The input/output framework is a set of interfaces and clas es that support the
input and output of different types of objects to and from different media with
varying capabilitie .
■ Java supports two types of VO: trean1 VO and random-acce VO. A tream
is a sequence of byte . Stream-ha ed l/O support reading or writing data se-
quentially. A stream may be opened for reading or writing, but not reading and
writing. There are two kinds of stream : byte trean1 and character treams.
Byte streams support reading and writing data of any type, including strings, in
the binary fonnat. Character tream upport reading and writing of text using
locale-dependent character encoding . Random-acce l/O upports reading and
writing data at any po ition of a file . A random-acce file may be opened for
both reading and writing.
-
394 •
~r-.
Object-Oriented Application Frameworks

• Serialization is the proce of writing an object and all the objects that are directly
or indirectly referenced by the object to a stream. Deserialization is the process
of restoring an object and all the objects that are directly or indirectly referenced
by the object that have been crialized.
• The Composite design pattern compose objects into tree tructures to represent a
part-whole hierarchy. The Compo ite design pattern let client treat individual ,
objects and compositions of object uniformly. The Compo ite design pattern i
should be used to repre ent a part-whole hierarchy of objects or to allow clients I
to treat aJI objects in the compo ite structure uniformly.
■ The Decorator design pattern attaches additional responsibilities to an object dy-
namically. Decorators provide a flexible alternative to subcla sing for extending
functionality. The Decorator design pattern should be used to add responsibili-
ties to individual objects dynamically without affecting other objects in the same
class, or to avoid an explosion of subclasses caused by an extension by subclas -
ing.
■ The common aspects of related classes should be handled unifonnly. These com-
mon aspects are usually interfaces or abstract classes. The greater the uniformity,
the simpler and more useful is the design.

FURTHER READING

Walrath, K., and M. Campione (1999). The JFC Swing Tutorial: A Guide to Con-
structing GU/s. Addison-Wesley.

l EXERCISES

8.1 The Set interface defines only the most basic Define and implement a class that supports
operations on sets. Some of the common these operations on sets as algorithms that
operations on sets that are not included in can be applied to any concrete classes that
the Set interface and their definitions are the implement the Set interface.
following :
8.2 Design and implement a class Car that de-
• The union of two sets: scribes the key characteri. tics of a car, uch a
model, model year, manufacturer, color, hor e-
power, number of cylinders, and o on. The
Car class should implement the Comparable
• The intersection of two sets:
interface. The natural order of the car objects i
based on the ascending orders of the following
attributes of the cars in descending significance:
• The nonsymmcLric difference of two sets: • The manufacturer
• The model
• The model year
Projects • 39 5

Wri te a program SortCars that instantiates a


8.6 Enhance the digital clock applet so that it can
number o f in lances of the Car class, inserts
display the time of any time zone around the
them into a list, sorts the cars in the list, and world. Use a choice component for choosing
prints out the cars in their natural order. the time zone.
8.3 Extend the Car class in the previous exercise 8.7 Java supports compression and decompression
by defining a comparator for each attribute of of data in ZIP or GZlP format. Write a program
a car. Write a program SortCars2 similar to to read a text file, compress the contents, and
the program Sort Cars in the previous exercise, write a file with the compre sed contents. The
except that it can sort the cars based on different compression format is either ZIP or GZIP
attributes of the cars in either ascending or specified by a command-line argument. Write
descending order using different comparators. another program to read a compressed file ,
The program takes a command-line argument decompress the contents, and write a text file
indicating the attribute to be sorted on and with the decompressed text. (Hint: Find out
whether in ascending or descending order. the details of the compre sion/decompre sion
For example, the program can be invoked as classes by reading the J2SE API documentation
follows: on package java. util . zip.)
java SortCars2 model ascend

8.4 Hash tables are often used to build indices for


fast access to objects in a large collection. Use PROJECTS
the Customer and Address classes defined in
Exercise 6.1 to build a list of customers. Then 8.1 Enhance the applets developed in Projects 5.2
use hash tables to build indices on customer and 5.3. Use a button to dynamically switch
names, customer IDs, and the postal code of between the pie chart display and the bar chart
the shipping addresses. Note that the customer display. Flip the button labels as well.
ID of each customer is unique and that the name 8.2 Enhance the bouncing ball applet in Exam-
and the postal code of the shipping address of ple 8.12 so that there can be several balls. Use
each customer are not unique. a pair of buttons to increase or decrease the
number of balls. If two balls collide, change the
8.5 Write a simple calculator applet. It should
contain buttons for the digits, th~ operators direction of their movement.
(+, -, *, and/), and the equ~s sign(=). The
results can be displayed by using a text field.
Design Case Study:
A Drawing Pad

CHAPTER OVERVIEW
In this chapter, we present a case study in developing g@phical user interfaces (GUls),
using the Java Foundation Class (JFC). We also demonst@te the ite@tive development
process by designing and implementing a graphical d@wing pad in successive incre-
ments. We introduce two new design patterns: Factory Method and State.

9.1 PLANNING

In this chapter, we develop a complete GUI application u ing the lightweight compo-
nent in the Swing package. The GUI application to be developed i a drawing tool
that supports the following:

• Scribbling and drawing variou hape


■ Saving the drawing to file and loading the drawing from file
• Typing from the keyboard
• Choo ing colors and font
397
398 • Design Case Study: A Drawing Pad

nd
The aim of this case study is to illustrate the iterative development proces~ a ~ e
use of design patterns. The use of design patterns play a crucial role in the tt~rauve
development process because each increment must be designed to be extensible to
accommodate design changes and enhancements in subsequent iterations.
The plan is to divide the development process into six iterations:

Iteration I : Creating a simple scribble pad, which consists of only a canvas for
scribbling.
Iteration 2: Adding support for saving and loading drawings, a menu bar, various
dialogs.
Iteration 3: Refactoring to support various tools for drawing various shapes .
Iteration 4: Adding tools for drawing lines, rectangles, and ovals.
Iteratio11 5: Refactoring and adding tools for drawing filled rectang les and ovals.
Iteration 6: Adding a tool for typing text.

In order for students to study the changes between iterations, the code produced in
each iteration is placed in a separate package.

9.2 ITERATION 1: A SIMPLE SCRIBBLE PAD

The goal of the first iteration is to deliver an initial version of the application th
. . . b f" In
~~n_tams a _nurumum su s~t o 1eatures. order to keep it as simple as possible, the
at
1rut1aJ vers10n of the drawmg
. tool only supports scribbling · The applicat1·00 consists
·
of a frame that contams the canvas for scribbling only. A screen shot of th · · •
version . 1e sen"bbl e pad 1s
. of t be s1mp . shown in Figure 9 .1. e m1tial

Figure 9.1 fflff

The simple scribble


pad.

t
p
9.2 ltffation 1: A Simple Scn'bble Pad ■ 399

figure 9.2 JFrame JPanel


The design of the
scribble pad-
Iteration 1 • Scribble ScribbleCanvas ~>-----1 EventListener

MouseListener MouseMotionListener
' I
' I
' I
' I
' I

ScrlbbleCanvasUstener

The initial design of the simple scribble pad is shown in Figure 9.2. The UML
diagrams in this chapter contain shaded classes to indicate that these clas es are the
ones to be developed in the current iteration, and the rest are classes and interface
either in the Java 2 Class Library or developed in previous iterations. The classes
to be developed in the first iteration are in the package scribble1, and they are
summarized in the following table:

Class Description

Scribble The main application


ScribbleCanvas The canvas for cribble
ScribbleCanvasListener Theeventli tenerthatli ten tomou eevent
for scribbling

9.2.1 The Scribbling Canvas and Its Listener


Let us first take a look at the ScribbleCanvas clas . Thi i the GUI component on
which all the draw in o take place. It extend the j avax •swing• JP anel cla , which
is the Swing counte;art of the Panel in AWT. The field of the ScribbleCanvas
class are as follow :

Fields Description

Whether one of the mou c button i being pressed down


mouseButtonDown
The current mou e po ition in the canvas
x, y
Mou c c enl Li cener of the canvas
listener
400 • Design Case Study: A Drawing Pad

C~ acribble1. ScribbleCanvaa
-
package scribble! ;
import java .awt .* ;
import java.awt . event . *;
import java .util.EventListener;
import j avax . swing . *;
public class ScribbleCanvas extends JPanel {
public ScribbleCanvas () {
listener= new ScribbleCanvasListener(this);
add.MouseListener ((MouseListener ) listener) ;
add.MouseMotionListener((MouseMoti onListener) listener);
}

protected EventListener listener ;


protected bool ean mouseButtonDown = false;
protected i nt x, y;

The ScribbleCanvasListener class handle the mouse events that occur in


the canvas. The desired behavior of scribbling involves the following actions:

■ A stroke begins when any mouse button is pressed.


■ The stroke continues when the mouse is dragged, that is, when the user moves
the mouse while pressing one of the buttons.
■ The stroke finishes when the mouse button is released.

There are two event listener interfaces that deal with mouse events:

1. j ava . awt. event. MouseListener, for events related to the action on the
buttons of the mouse, such as button press and release.
2. j ava. awt. event. MouseMotionListener, for events related to the move-
ment of the mouse, such as mouse move or drag.

The ScribbleCanvasListener class must handle button press, button relea e,


and mou e drag actions, so it must implement both the MouseListener and Mouse-
MotionListener interfaces. The relevant methods of the ScribbleCanvasLis-
tener cla s are summarized in the following table:

Methods Description

mousePressed O Notified when a mouse button is pressed


mouseReleased O Notified when a mouse button is released
mouseDragged O Notified whi le the mouse is being dragged
9.2 lteration1:ASimpleScribblePad • 401

Th~ other methods declared in the MouseListener and MouseMot ionLi s-


tener interfaces are of no interest here, so empty 1IDp
. 1ementallons
. are provided
. for
t hem.

ChJss acribble1 .ScribbleCanvuLiatener


package scribble! ;
import java.awt . • ;
import java .awt .event .• ;
public class ScribbleCanvasListener
implements MouseListener , MouseMotionLi stener {
public ScribbleCanvasListener (Scri bbleCanvas canvas) {
this.canvas= canvas ;
}

public void mousePressed (MouseEvent e) {


Point p = e .getPoi nt () ;
canvas.mouseButtonDow:n = true ;
canvas . x = p .x;
canvas.y = p. y;
}

public void mouseReleased (MouseEvent e) {


canvas .mouseButtonDown = false ;
}

public void mouseDragged (MouseEvent e) {


Point p = e.getPoint ();
if (canvas.mouseButtonDown) {
canvas.getGraphics (). drawLine (canvas.x, canvas.y, p .x, p.y);
canvas .x = p .x;
canvas. y = p. y;
}
}

publi c voi d mouseCl icked (MouseEvent e) {}


public void mouseEntered (MouseEvent e) {}
publ ic void mouseExited(MouseEvent e) {}
publi c voi d mouseMoved(MouseEvent e) {}
protected Scr i bbl eCanvas canvas ;
}

The mousePressed () method handle the beginning of a troke. It et the


boolean flag mouseButtonDown of the Scr i bbl eCanvas to true and tores
the current mou e po it ion. The current mouse po ·ition i obtained by invoking
e . get Poi nt () . The po ition is relative to the upper-left comer of the source com-
ponent (i.e., the canva ).
While the mouse i, being dragged. a eries of mouse drag events are generated
by the Java run time. Each event indicate · the current mou e position. The mouse-
Dragged () method handles the continuation of a troke. It draw a line from the
402 • Design Case Study: A Drawing Pad

previous mouse position to the current position and updates the current mouse position
in ScribbleCanvas.
The mouseReleased () method handles the end of a drawi ng troke. It sets the
boolean flag mouseButtonDown of the canvas to false.
The constructor of ScribbleCanvas class [p. 400] creates an instance of
ScribbleCanvasListener and registers the listener as both MouseListener and
MouseMotionListener of the canvas.

9.2.2 The Application

The Scribble class is the main application class of the initial version of the scribble
pad. It extends the j avax. swing. JFrame class. A frame is created in the main ()
method, and an instance of the Seri bbleCanvas is placed at the center of the frame.

Class scribble1. Scribble


package scr i bble! ;
import java.alolt.*;
import java.alolt.event .*;
import j avax.swing .*;
public class Scribble extends JFrame {
public Scribble() {
setTi tle ("Scribble Pad");
canvas= new ScribbleCanvas();
canvas.setBackground(Color.white);
getContentPane().setLayout (new BorderLayout ()) ;
getContentPane().add(canvas, BorderLayout.CENTER);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}

public static void main(String[] args) {


int width= 600;
int height= 400;
JFrame frame= new Scribble();
frame . setSize(width, height);
Dimension screenSize = java.awt .Toolkit .getDefaultToolkit()
.getScreenSize () ;
II place the application frame at the center of the screen
frame . setLocation(screenSize .width/2 - width/2,
scr eenSize. height/2 - height/2);
frame . show () ;
}

prote cted ScribbleCanvas canvas· I

}
9.3 Iteration 2: Menus, Options, and Files ■ 403

This completes the first iteration of the drawing pad. The result is a simple but
completely functional GUI application.

9.3 ITERATION 2: MENUS, OPTIONS, AND FILES

The aim of this iteration is to enhance the scribble in a number of aspects:


■ Storing the drawings internally so that they can be redrawn
■ Saving the drawings into files and loading drawings from files
■ Building a menu bar
■ Using the file dialogs
■ Creating a dialog box for selecting colors
A screen shot of the scribble pad built in this iteration is shown in Figure 9.3. The
design of this iteration of the simple scribble pad is shown in Figure 9.4. The cla e
of this iteration are in package scribble2.

9.3.1 Strokes

One of the deficiencies of the initial version is that the drawings are directly painted
onto the canvas by the ScribbleCanvasListener object, and they are not tored
internally. As a result, if the application frame is moved, covered, or minimized.
the drawings disappear. To retain the drawings after the application frame i moved,

Figure 9.3 . ~, ~ '' -: ~ ' .- ~


- . .

Ale Option
The scribble pad-
iteration 2.
-
404 ■ Design Case Study: A Drawing Pad

Figure 9.4 JFrame JPanel

The design of the


scribble pad-
iteration 2. Scribble ScribbleCanvas EventListener
1

Serializable MouseListener MouseMotionListener


'' '' I
I

'' \ /

'' .. \
\ I
I

ScribbleCanvasUstener
1

*
Point

covered, or minimized, the drawings must be stored internally and repainted whenever
necessary.
The drawings are stored in the following structure:
■ Each drawing consists of a list of strokes.
■ Each stroke consists of a list of points and the color of the stroke.
The scribble2. Stroke class represents a single stroke in a drawing. Since we
want to be able to save the drawings in files, we make Stroke class serializable so
that we can use the serialization mechanism to save and load drawings. The fields of
the Stroke class are as follows:

Fields Description

points The list of points that form the stroke


color The color of the stroke

The methods of the Stroke class are these:

Methods Description

s et Color () Set the color of the stroke.


getColor O Get the color of the stroke.
addPoint O Append a point to the stroke.
getPoints O Return the list of points.
9.3 Iteration 2: ~nus, Options, and Files • 405

Classscribble2 . Stroke
package scribble2;
import j ava . ut i l.• ;
import java . io.Seri alizable;
i mport j ava . awt . Point ;
i mport j ava . awt . Color ;
public class Stroke implements Seriali zable {
public Stroke() {}
public Stroke (Color color) {
this . color= color ;
}
public void setColor (Color color) {
this . color= color;
}
public Color getColor () {
return color ;
}
public void addPoint (Point p) {
if (p != null) {
points . add (p) ;
}
}
public List getPoints () {
return points ;
}
protected List points= new ArrayList ( ) ;
protected Color color= Color . black ;
}

9.3.2 The Scribble Canvas

The scribble2. ScribbleCanvas class i an enhancement to the scri bbl el


. Scribbl e Canva s clas . It store the drawing a a list of trokes and repaint the
drawings onto the canvas whenever necessary. In addition to the fields declared in
s cribble!. ScribbleCanvas, the scribble2 . Scri bbleCanvas cla contain
the following fields:

Fields Description

strokes The Ii t of troke


curStroke The current troke, while the troke is being drawn
cur Color The color of the current troke
406 ■ Design Case Study: A Drawing Pad

The methods of the scribble2. ScribbleCanvas class are summarized in the


following table:

Methods Description

setCurColor() Set the current color.


getCurColor () Get the current color.
startStroke () Invoked by the listener to start a new stroke. A new
current stroke is created.
addPointToStroke () Invoked by the listener to append a point to the
current stroke. A new point is appended to the
current stroke.
endStroke () Invoked by the listener to end a new stroke. The
current stroke is added to the list of strokes.
paint() Invoked by the Java run time whenever the canvas
needs to be painted. The drawing is repainted onto
the canvas by retracing the strokes stored internally.
newFile () Create a new drawing and a new file.
saveFile () Save the current drawing to a file using object
serialization.
openFile () Load a drawing from a file by deserializing the
object stored in the file and repaint the canva .

Class1cribble2.ScribbleCanvas
package scribble2;
import java.awt.Color ;
import java .awt .Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java .util.•;
import java. io.•;
import java.awt . event . •;
import java .util.EventListener;
import javax.swing.•;
public class ScribbleCanvas extends JPanel {
public ScribbleCanvas() {
listener= new ScribbleCanvasListener(this);
addMouseListener((MouseListener) listener);
addMouseMotionListener((MouseMotionListener) listener);
}
9 .3 Iteration 2: Menus, Options, and Files • 407

public void setCurColor(Color curColor) {


this . curColor = curColor;
}

public Color getCurColor ( ) {


return curColor;
}

public void startStroke(Point p ) {


curStroke = new Stroke(curColor);
curStroke . addPoint(p);
}

public void addPointToStroke(Point p) {


if (curStroke != null) {
curStroke . addPoint(p);
}
}

public void endStroke(Point p) {


if (curStroke != null) {
curStroke . addPoint (p);
strokes.add(curStroke);
curStroke = null;
}
}

public void paint (Graphics g) {


Dimension dim= getSize () ;
g.setColor (Color.white) ;
g.fillRect (O, 0, dim.width, dim.height ) ;
g . setColor (Color .black);
if (strokes != null) {
Iterator iterl = strokes.iterator () ;
while ( iterl . hasNext ()) {
Stroke stroke= (Stroke) iterl . next ();
if (stroke ! = null) {
g.setColor (stroke . getColor ()) ;
Point prev - null ;
List points= stroke.getPoints ();
Iterator iter2 = poi nts .iterator ();
while ( iter2 . hasNext ()) {
Point cur= (Point) i ter2. next ();
if (prev ! = null) {
g . drawLi ne (prev .x , prev.y, cur .x , cur .y);
}
prev = cur ;
}
}
}
}
}
408 ■ Design Case Study: A Drawing Pad

public void newFile() {


strokes.clear();
repaint();
}

public void saveFile(String filename) {


try {
ObjectOutputStream out=
new ObjectOutputStream(new FileOutputStream(filename));
out.writeObject(strokes);
out. close O ;
System. out. println ( 11 Save drawing to 11 + filename) ;
} catch (IOException e) {
System.out . println( 11 Unable to write file: 11 + filename);
}
}

public void openFile(String filename) {


try {
ObjectinputStream in=
new ObjectinputStream(new FileinputStream(filename));
strokes= (List) in . readObject();
in. close O ;
repaint();
} catch (IOException el) {
System.out.println( 11 Unable to open file: 11 + filename);
} catch (ClassNotFoundException e2) {
System .out.println(e2);
}
}

protected List strokes= new ArrayList();


protected Stroke curStroke = null;
protected Color curColor = Color.black;
protected EventListener listener;
protected boolean mouseButtonDown = false;
protected int x, y;
}

9.3.3 The Canvas Listener

The scribble2. ScribbleCanvasListener class is very similar to the scrib-


ble 1. ScribbleCanvas class in the previous iteration. The only difference is
that in addition to drawing the points of a stroke onto the canvas, the points must
also be stored in strokes. Method startStroke O, addPointToStroke () , or
endStroke O of the canvas is invoked by method mousePressed (), mouseRe-
leased O, or mouseDragged () of the listener, respectively.
9.3 Iteration 2: Menus, Options, and Files • 409

Class scri bbl•~ . ScribbleCanvaaListener


package scribble2 ;
import java .awt . • ;
import java . awt . event .•;
public class ScribbleCanvasListener
implements MouseListener, MouseMotionListener {
public ScribbleCanvasListener (ScribbleCanvas canvas) {
thi s . canvas= canvas;
}

public void mousePressed(MouseEvent e) {


Point p = e .getPoint ();
canvas . mouseButtonDown = true;
canvas . startStroke(p);
canvas . x = p . x ;
canvas.y = p . y ;
}

public void mouseReleased(MouseEvent e) {


Point p = e.getPoi nt() ;
canvas . endStroke(p);
canvas.mouseButtonDown = false ;
}

public void mouseDragged (MouseEvent e ) {


Point p = e . getPoint() ;
if (canvas.mouseButtonDown) {
canvas.addPointToStroke(p) ;
Graphics g = canvas . getGraphics ();
g . setColor (canvas . getCurColor ());
g . drawLine(canvas . x, canvas . y , p . x , p. y);
canvas . x = p.x;
canvas . y = p . y ;
}
}

public void mouseClicked(MouseEvent e ) {}


public void mouseEntered (MouseEvent e ) {}
public void mouseExi ted(MouseEvent e) {}
public void mouseMoved (MouseEvent e ) {}
protected ScribbleCanvas canvas ;

9.3.4 The Application


The scribble2 . Scribble class is the main application class. This version of the
scribble pad ha a menu bar. The scribble2. Scribble class contains a number of
41 0 ■ ~ign Cas~ Study: A Drawing Pad

e the listeners of the menu items in the menu bar. The methods
nested cIasses, Which ar . . .
of the scribble2. Scribble class are summarized m the followmg table:

Methods Description

createMenuBarO Create the menu bar.


newFile () Start a new drawing and a new file.
openFile () Load a drawing from the specified file.
saveFile () Save the current drawing to the current file.
saveFileAs () Save the current drawing to the specified file.

The nested classes of the scribble2. Scribble class are summarized in the
foIJowing table:

Nested Classes Description

NewFileListener Action for the "New" menu item


0penfileListener Action for the "Open" menu item
SaveFileListener Action for the "Save" menu item
SaveAsFileListener Action for the "Save As" menu item
Exi tListener Action for the "Exit" menu item
ColorListener Action for the "Color" menu item
AboutListener Action for the "About" menu item

C.. •cribble2. Scribble


package scribble2;
import java.allt.•;
import java. awt.event .• ;
import java.io.•;
import javax.swing.•;
public class Scribble extends JFrame {
public Scribble() {
setTitle("Scribble Pad");
canvas= new ScribbleCanvas();
getContentPane().setLayout(new BorderLayout());
getContentPane() . add(createMenuBar(), BorderLayout . N0RTH);
getContentPane() . add (canvas, BorderLayout.CENTER);
r- 9.3 Iteration 2: Menus, Options, and Files • 411

addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (exitAction ! • null) {
exitAction . actionPerformed(new ActionEvent
(Scribble.this, 0, null));
}
}
});
}

(createMenuBar O method on page 411 )


(newFile O, openFile () , saveFile () , and saveFileAs () methods on page 416)
(Nested class NewFileListener on page 413)
(Nested class OpenFileListener on page 413}
(Nested class SaveFileListener on page 414)
(Nested class SaveAsFileListener on page 415)
(Nested class Exi tListener on page 415}
(Nested class Color Listener on page 417)
(Nested class AboutListener on page 413)
public static void main(String [] args) {
int width= 600;
int height= 400;
JFrame frame= new Scribble();
frame . setSize(width, height);
Dimension screenSize =
j ava. awt . Toolkit . getDef aul tToolki t () . getScreenSize O ;
II place the application frame at the center of the screen
frame . setLocation (screenSize.width/2 - width/ 2 ,
screenSize . height/2 - height/2);
frame . show O ;
}

protected String currentFilename = null ;


protected ScribbleCanvas canvas;
protected ActionListener exitAction;
protected JFileChooser chooser= new JFileChooser (" . ");
}

In the constructor of the Scribble class, the menu bar is created and placed
above the canvas. The menu bar is created by the following createMenuBar 0
method:

Method of ecribble2. Scribble das.1: createNauBar ()


protected JMenuBar createMenuBar() {
JMenuBar menuBar = new JMe nuBar () ;
JMenu menu;
JMenuitem mi;
// FIie menu
menu= new JMenu ("File 11 ) ;

menuBar .add (menu);


412 • Design Case Study: A Drawing Pad

mi= new JMenu I tem ( "New ").,


menu .add (mi);
· ddActionListener(new NewFileListener());
mi.a
mi= new JMenuitem("Open");
menu.add(mi);
mi.addActionListener(new OpenFileListener());
mi = new JMenuitem("Save");
menu.add(mi);
mi.addActionListener(new SaveFileListener());
mi= new JMenuitem("Save As");
menu.add(mi);
mi.addActionListener(new SaveAsFileListener()) ;
menu.add(new JSeparator());
exitAction = new ExitListener();
mi = new JMenuitem("Exit") ;
menu.add(mi);
mi .addActionListener(exitAction);
II option menu
menu= new JMenu("Option");
menuBar . add(menu);
mi= new JMenuitem("Color");
menu . add(mi);
mi.addActionListener(new ColorListener());
II horizontal space
menuBar.add(Box.createHorizontalGlue());
II Help menu
menu= new JMenu("Help");
menuBar.add(menu);
mi = new JMenultem("About ");
menu.add(mi);
mi.addActionListener(new AboutListener ());
return menuBar;
}

Note that each menu item has an associated action listener to handle the action of
the menu item. The action listeners are all defined as nested classes of the Scribble
class.
The simplest action listener is the AboutListener, which handles the action
of the About menu item in the Help menu. It displays a simple dialog box using the
javax. swing . JOptionPane class as shown in Figure 9.5. The JOptionPane cla
provides various static methods to display the most commonly used dialog boxes with
a single method invocation.1

I. See API documentations on Lhe j avax . swing . JOpt ionPane class for details.
9 .3 Iteration 2: Menus, Options, and Files • 413

Figure 9 .5
D

The "About" dialog DRWlngPad version LO


of the scribble pad. Copyright (C) Xiaoping Jla 2002

Nested class of acribble2. Scribble class; AboutLiatener


class AboutListener i mplement s Act i onLi st ener {
public void actionPerformed (ActionEvent e) {
JOpti onPane . showMessageDi alog(null,
"DrawingPad version 1 .0\nCopyr ight (c) Xi aopi ng J i a 2002",
"About",
JOpti onPane .INFORMATION_MESSAGE);
}

The NewFileListener class handles the action of the New menu item in the
File menu. The action is delegated to the newFile () method, as follows:

Nested class of acribble2. Scribble class: lfevFileLiatener


class NewFi leLi s t ener i mplements ActionListener {
publ i c void a ct ionPerformed (ActionEvent e) {
newFile ();
}

The OpenFileLi stener class handle the action of the Open menu item in the
File menu. It uses the j a v ax. swi ng . J Fi leCh ooser clas to di play a file dialog
as shown in Figure 9.6. After a fi le nan1e is elected using the fi le dialog, the action
is delegated to the openFi le () method with the elected file name, as follow

Nested class of acribble2. Seribbl• claa:~ uFileLiatener


class OpenFileListener implements ActionListener {
public void actionPerformed( ActionEvent e ) {
int retval = chooser.showDialog(null, "Open");
if (retval == JFileChooser . APPROVE_OPTION) {
File theFile = chooser.getSelectedFile();
if (theFile != null) {
if (theFile.isFile()) {
414 • ~lgn Case Study: A Drawing Pad

String filename=
chooser.getSelectedFile ().getAbsolutePath();
openFile(filename) ;
}
}
}
}

The SaveFileListener class handles the action of the Save menu item in the
File menu. The action is delegated to the saveFile () method, as follows :

of1cribble2.Scribbledass:SaveF1leLiatener
class SaveFileListener implements ActionListener {

public void actionPerformed(ActionEvent e ) {


saveFile O ;
}
}

The SaveAsFileListener class handles the action of the Save As menu item
in the File menu. It also uses the j avax. swing. JFileChooser class to display a
tile dialog similar to the one shown in Figure 9.6. After a file name is selected using

Figure 9.6

The fiJe dialog of Look !n: fi!j Drawings


the scribb~ pad.
DJanJsColortuLsalbble

FIie Name: jJavaJsColorful. sulbble /


I
files of :[Ype: ~I Flies
1-----===-- - -----:::----;==,,---,
• I
Open Cancel I
9 .3 Iteration 2: Menus, Options, and Files • 415

the file dialog, the action is delegated to the saveFileAs () method with the selected
file name, as follows :

Nested class of scribble2. Scribble clau: SaveA■FileLiatener


class SaveAsFileListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int retval = chooser.showDialog (null, "Save As") ;
if (retval == JFileChooser . APPROVE_OPTION) {
File theFile = chooser .getSelectedFile();
i f (theFile != null) {
if (!theFile.isDirectory ()) {
String filename=
chooser.getSelectedFile().getAbsolutePath();
saveFileAs (filename );
}
}
}
}

The Exi tListener class handles the action of the E.xit menu item in the File
menu. It displays a simple dialog box using the j avax . s-wing. JOptionPane class
as shown in Figure 9.7 to prompt the user to save the current drawing.

Nested daa of ■cribble2. Scribble clam: ExitLiatener


class ExitListener implements ActionListener {
public void actionPerformed (ActionEvent e) {
int result= JOptionPane.showConfirmDialog(null,
"Do you want to exit Scribble Pad?",
"Exit Scribble Pad?" ,
JOptionPane . YES_NO_OPTION);
if (result== JOptionPane.YES_OPTION) {
saveFile () ;
System.exit (O);
}
}
}

Figure 9.7

The "Exit" dialog of Do you wint to exit Salbble Pad?


the scribble pad.
416 • Design Case Study: A Drawing Pad

The methods dealing with files maintain the current file name and delegate most
of the work to the corresponding method in the scribble2. ScribbleCanvas class
[p. 406].

Metbom of 1cribblt2. Scribble·class: JlevFile (), ~penFile (),


aav-eFil• 0, and savefileA1 () .~ _
protected void newFile() {
currentFilename = null ;
canvas.newFile();
setTitle( "Scribble Pad");
}

protected void openFile(String filename) {


currentFilename = filename;
canvas.openFile(filename);
setTitle ("Scribble Pad [" + currentFilename + "]");
}

protected void saveFile() {


if (currentFilename == null) {
currentFilename = "Untitled";
}
canvas . saveFile(currentFilename);
setTitle("Scribble Pad [" + currentFilename + "]");
}

protected void saveFileAs(String filename) {


currentFilename = filename;
canvas.saveFile(filename);
setTitle("Scribble Pad["+ currentFilename + "]");
}

9.3.5 Choosing Colors

The nested ColorListener class handles the action of the Color menu item in the
Option menu. It displays a custom dialog box as shown in Figure 9.8. The custom
color dialog is defined in the scribble2. ColorDialog class.

Agure 9.8

The color dialog of □□■■


the scribble pad.
■■□□
□□□■
■ □■□
More colors I
9.3 ltaation 2: Menus, Options, and Fila • 417

class ColorListener implements ActionListener {


public void actionPerformed(ActionEvent e) {
Color result • dialog . showDialog();
if (result ! s null) {
canvas.setCurColor(result);
}
}

protected ColorDialog dialog=


new ColorDialog(Scribble .this, "Choose color" ,
canvas .getCurColor());
}

package scribble2 ;
import java . avt.•;
import java. avt.event . •;
import javax. swing . • ;
public class ColorDialog extends JDialog implements
ActionListener {
public ColorDialog(JFrame owner, String title) {
this(owner, title , Color.black) ;
}

public ColorDialog(JFrame owner, String title,


Color color) {
super(owner, title, true);
this.color= color;
getContentPane() . setLayout(new BorderLayout());

JPanel topPanel = new JPanel () ;


topPanel.setLayout(new BorderLayout()) ;
colorPanel = new ColorPanel(20 , 20, 8, 8);
topPanel . add(colorPanel, BorderLayout.CENTER);
moreColorButton = new JButton ( 11 More colors") ;
moreColorButton .addActionListener (this);
topPanel . add (moreColorButton , BorderLayout .SOUTH);
getContentPane () .add(topPanel , Border Layout . CENTER);

JPanel bottomPanel = new JPanel();


bottomPanel . s etLayout (new FlowLayout(FlowLayout .RIGHT));
okButton = new JButton ( 11 0k 11 ) ;
okButton.addAct i onLi stener ( thi s );
bottomPanel. add (okButton);
cancelButton = new JButton ("Cancel");
cancelButton . addActionListener (this );
418 • Design Case Study: A Drawing Pad

bottom.Panel . add(cancelButton);
getContentPane().add(bottomPanel, BorderLayout.SOUTH);
pack();
}

public Color showDialog() {


result,. null;
colorPanel.setColor(color);
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
Dimension dialogSize,. getSize();
setLocation(screenSize.width / 2 - dialogSize.width / 2,
screenSize.height / 2 - dialogSize . height / 2);
show();
if (result != null) {
color= result;
}
return result;
}

public void actionPerformed(ActionEvent e) {


Object source= e.getSource();
if (source== okButton) {
result= colorPanel.getColor();
} else if (source== moreColorButton) {
Color selectedColor = chooser.showDialog(ColorDialog.this,
"Choose color",
color);
if (selectedColor != null) {
colorPanel.setColor(selectedColor);
colorPanel.repaint();
}
return;
}
setVisible(false);
}

protected JButton okButton;


protected JButton cancelButton;
protected JButton moreColorButton;
protected ColorPanel colorPanel;
protected JColorChooser chooser= new JColorChooser();
protected Color color= null;
protected Color result= null;
class ColorPanel extends JPanel {
ColorPanel(int cellWidth, int cellHeight, int xpad, int ypad) {
if (cellWidth < 5) {
cellWidth • 5;
}
9.3 Iteration 2: Menus, Options, and Fks • 419

if (cellHeight < 5) {
cellHeight = 5;
}
i f (xpad < 2) {
xpad • 2;
}
if (ypad < 2) {
ypad a 2;
}
this.cellWidth = cellWidth ;
this . cellHeight • cellHeight ;
this.xpad • xpad ;
this . ypad = ypad;
rowCount = colorGrid.length ;
columnCount • colorGrid[O].length;
dimension= new Dimension((cellWidth + xpad) •
columnCount + xpad,
(cellHeight + ypad) •
(rowCount + 1) + ypad);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent event) {
Point p = event . getPoint();
inti= (p .y / (ColorPanel.this.cellHeight +
ColorPanel . this . ypad));
int j = (p.x / (ColorPanel . this.cellWidth +
ColorPanel . this.xpad));
if (i < rowCount tt
j < columnCount) {
color= colorGrid[i][j] ;
repaint();
}
}
});
}

public void setColor(Color color) {


this.color= color;
}

public Color getColor() {


return color;
}

public Dimension getMinimumSize () {


return dimension ;
}

public Dimension getPreferredSize () {


return dimension;
}
420 • Design Case Study: A Drawing Pad

public void paint(Graphics g) {


Dimension dim= getSize();
g.setColor(Color.lightGray);
g.fillRect(0, 0, dim.width, dim.height);
int x, y;
for (inti= 0; i < rowCount; i++) {
for (int j = 0; j < columnCount; j++) {
x = (cellWidth + xpad) * j + xpad;
y = (cellHeight + ypad) * i + ypad;
g.setColor(colorGrid(i] [j));
g.fillRect(x, y, cellWidth, cellHeight);
g.setColor(Color.black);
g.drawRect(x, y, cellWidth, cellHeight);
}
}
x = xpad;
y = (cellHeight + ypad) * rowCount + ypad;
int width= (cellWidth + xpad) * colwnnCount - xpad;
g.setColor(color);
g .fillRect(x, y, width, cellHeight);
g.setColor(Color.black);
g.drawRect(x, y, width, cellHeight);
}

protected Color color;


protected int cellWidth;
protected int cellHeight;
protected int rowCount;
protected int colwnnCount;
protected int xpad;
protected int ypad;
protected Dimension dimension;
protected Color[][] colorGrid = {
{ Color.white, Color.lightGray, Color.darkGray,
Color.black},
{ Color.gray, Color.blue, Color . cyan,
Color.green},
{ Color . yellow, Color.orange, Color.pink,
Color.red},
{ Color . magenta, new Color(230, 230, 250),
new Color(0, 0, 128),
new Color(64, 224, 208) } };
}

It uses the j avax. swing. ColorChooser class to display a color chooser dialog
as shown in Figure 9.9.
This completes the second iteration of the drawing pad .
9..4 ttaation 3: Refactoring • 421

Figure 9.9

The Swing color


chooser.

Preview

~ -1 C~cel 11 Beset I

9.4 ITERATION 3: REFACTORING

The ultimate goal of the drawing pad i to upport the drawing of variou type of
shapes, such a lines, ovals, and rectangles. The current version of the drawing pad
supports only scribbling. The design of the current version of the drawing pad is
also rather simpleminded to support scribbling only. ln order to upport the drawing
of variou types of shape , the de ign of the drawing pad mu t be improved to
accommodate such extension . The aim of thi iteration i to refactor the design
to improve its exten ibility. The functionality of the re ulting application is identical
to that of the previou iteration.

9.4.1 The Shapes


The first step in refactoring the design of the drawing pad i to generalize the notion
of hape that can be drawn in the drawing pad. Currently, the only . hape that ~an
be drawn i a troke a defined in the previou. iteration. To generalize the nouon
422 ■ Design Case Study: A Drawing Pad

Figure 9.10 Serializable

Refactoring the
scribble pad-the •
shapes. ScrlbbleCanvas

Point

of shapes, we introduce an abstract class Shape to represent all shapes that can be
drawn, with stroke being one of such shapes. The refactored design concerning the
shapes is shown in Figure 9.10. Other shapes include lines, ovals, and rectangles. The
subclasses representing these other shapes are introduced in the next iteration (see
Figure 9.13).
The reason for Shape being an abstract class instead of an interface is that there
are some features, that is, fields and methods, that are common to all shapes, for ex-
ample, the color of each shape. It is more appropriate to define these common features
in a common superclass. The field color and the associated methods previously de-
fined in the Stroke class are moved to the superclass Shape. The abstract method
draw() is intended for each subclass to define how each shape should be drawn.

Abltnctdmscribble3.Sh
package scribble3;
import java.awt.•;
import java.io.Serializable;
public abstract class Shape implements Serializable {
public Shape(){}
public Shape(Color color) {
this . color= color;
}

public void setColor(Color color) {


this.color= color;
}

public Color getColor() {


return color;
}

public abstract void draw(Graphics g);


protected Col or color= Color . black;
}

~---
9.4 Iteration 3: Refactoring • 423

The scribble3. Stroke class in this iteration is identical to the version in


the previous iteration, except the field color and related methods are moved to the
superclass Shap e.

Cla,s scribble8.
package scribble3 ;
import java.util . •;
import java.awt.Point;
import java.awt .Color;
import java.awt.Graphics;
public class Stroke extends Shape {
public Stroke(){}
public Stroke(Color color) {
super(color);
}

public void addPoint(Point p) {


if (p != null) {
points . add(p);
}
}

public List getPoints() {


return points;
}

public void draw(Graphics g) {


if (color != null) {
g.setColor(color);
}
Point prev = null;
Iterator iter = points . iterator() ;
while (iter.hasNext()) {
Point cur= (Point) iter .next();
if (prev != null) {
g.drawLine(prev . x, prev .y, cur . x, cur . y) ;
}
prev = cur;
}
}

// The list of points on the stroke


// elements are instances of java.awt.Polnt
protected List points= new ArrayList () ;
}

The refactoring of the design concerning the shapes is an application of the


Strategy design pattern. The Shape class is the abstract strategy, and the Stroke
class is a concrete strategy.
424 • Onlgn CaK Study: A Drawing Pad

9.4.2 The Tools

In order to draw different shapes, such as lines, ovals, and rectangles, the behavior
of the canvas listener must be different when drawing different shapes. One possible
approach is outlined in the following code fragments. First, we define a set of constants
to represent possible shapes that need to be drawn and use a field current Shape to
indicate the currently selected shape:
public class DrawingPad {
II constants representing shapes
public static final int SCRIBBLE= O;
public static final int LINE= 1;
public static final int RECTANGLE= 2;
public static final int OVAL= 3;
// the currently selected shape
protected int currentShape = SCRIBBLE;
public void setCurrentShape(int shape) {
currentShape = shape;
}
public int getCurrentShape() {
return currentShape;
}
II other fields and methods
}

Second, the canvas listener will be extended as follows:


public class DrawingCanvasListener
implements MouseListener, MouseMotionListener {
public voi d mousePressed(MouseEvent e) {
switch (drawingPad .getCurrentShape()) {
case Drawi ngPad .SCRIBBLE :
II handle mouse pressed event for scribbling break;
case DrawingPad.LINE :
II handle mouse pressed event for drawing a line break;
case DravingPad .RECTANGLE:
II handle mouse pressed event for drawing a rectangle break;
case DrawingPad .OVAL :
II handle mouse pressed event for drawing an oval break;
}
}

public void mouseDragged (MouseEvent e) {


switch (drawingPad.getCurrentShape ()) {
case DrawingPad . SCRIBBLE :
II handle mouse dragged event for scribbling break;
case Drawi ngPad .LINE :
II handle mouse dragged event fo r drawing a line break;
case DrawingPad .RECTANGLE :
II handle mouse dragged event for drawing a rectangle break;
case Drawi ngPad .OYAL :
II handle mouse dragged event for drawing an oval break;
}
}

• I
111, • •• .
9 .4 Iteration 3: Refactoring • 425

publ ic void mouseReleased(HouseEvent e) {


switch (drawingPad .getCurrentShape()) {
case DrawingPad.SCRIBBLE:
// handle mouse released event for scribbling break;
case DrawingPad.LINE:
// handle mouse released event for drawing a line break;
case DrawingPad.RECTANGLE:
II handle mouse released event for drawing a rectangle break;
case DrawingPad.OVAL:
II handle mouse released event for drawing an oval break;
}
}
II . . .
}

This design works. However, it is rather cumbersome, inflexible, and inelegant,


for the following reasons. First, the use of the constants to represent shapes causes the
coupling between the DrawingPad and DrawingPadListener classes to be very
high. The labels of the switch statements in the DrawingPadListener class must
exactly match the constants defined in the DrawingPad class. Adding new shapes
or removing existing shapes requires carefully coordinated changes in both classes.
Second, the behavior of drawing each shape is defined in three separate methods-
mousePres sed O, mouseReleased (), and mouseDragged ()-intermixed with
the behaviors of dealing with other shapes. Thus, changing the behavior of handling
a particular shape or adding or removing a shape requires coordinated changes in all
three methods. This requirement is a severe hindrance to the readability of the code
and makes changes to the code very difficult and error-prone.
A better design is to encapsulate the behavior of handling the drawing of a
particular shape in a class, which we call a tool. The behavior of each tool is defined by
its response to the following events: mouse button pressed, mouse button released, and
mouse dragged. The following interface Tool represents an abstraction of the tools.
The behavior of drawing a particular shape is defined in a class that implements the
Tool interface, such as the ScribbleTool class. The refactored design concerning
the tools is shown in Figure 9 .11. It becomes clear in the next iteration that this design
allows much easier extensions of additional shapes and tools (see Figure 9.14).

Figure 9.11
I ScrlbbleCanvasUstener p--~71__'ool___
Rdactoring the
scribble pad-the
*
tools. AbstraotTool

ScribbleTool
426 ■ Design Case Study: A Drawing Pad

...,... -.cribbl.S. Tool


package scribble3;
import java.avt.•;
public interface Tool {
public String getName();
public void startShape(Point p);
public void addPointToShape(Point p);
public void endShape(Point p);
}

The AbstractTool class implements the Tool interface and provides default
implementation to features that are shared by all tools.

iKribbl.S.OnractTool
package scribble3;

public abstract class AbstractTool implements Tool {


public String getName() {
return name;
}

protected AbstractTool(ScribbleCanvas canvas, String name) {


this.canvas= canvas;
this .name= name ;
}

protected ScribbleCanvas canvas;


protected String name;
}

The ScribbleTool class is a concrete tool that defines the behavior for scrib-
bling. Note that the implementation of the ScribbleTool class is identical to por-
tions of the implementation that were previously in the scribble2. Scribble-
CanvasListener class.

package scribble3;
import java.avt.•;
public class ScribbleTool extends AbstractTool {
public ScribbleTool(ScribbleCanvas canvas , String name) {
super(canvas, name);
}

public void startShape(Point p) {


curStroke = new Stroke(canvas .getCurColor());
curStroke.addPoint(p);
}
9.4 Iteration 3: Refactoring • 427

public void addPointToShape(Point p) {


if (curStroke != null) {
curStroke . addPoint(p) ;
Graphics g = canvas.getGraphics();
g.setColor(canvas.getCurColor());
g . drawLine(canvas.x, canvas.y, p.x, p . y);
}
}

public void endShape(Point p) {


if (curStroke != null) {
curStroke.addPoint(p);
canvas . addShape(curStroke);
curStroke = null;
}
}

protected Stroke curStroke = null;


}

The behavior of drawing each shape is now encapsulated in a tool for that
shape. The scribble3. ScribbleCanvasListener class contains a reference to
the current tool being used for drawing, that is, the scribble tool in this iteration,
since it is the only tool supported. It is to this tool that responses to events of the
scribble3. ScribbleCanvasListener class are delegated.

Clills acribbl.S. S c r l b ~
package scribble3;
import java.awt . • ;
import java.awt.event.•;
public class ScribbleCanvasListener
implements MouseListener, MouseMotionListener {
public ScribbleCanvasListener(ScribbleCanvas canvas) {
this . canvas= canvas;
tool= new ScribbleTool(canvas, "Scribble");
}

public void mousePressed(MouseEvent e) {


Point p = e . getPoint( );
tool.startShape(p);
canvas .mouseButtonDown = true;
canvas.x = p.x;
canvas.y = p . y ;
}

public void mouseDragged(MouseEvent e) {


Point p = e.getPoint();
if (canvas.mouseButtonDown) {
tool .addPointToShape(p);
canvas . x = p .x;
canvas . y = p . y;
}
}
428 ■ Design Cas~ Study: A Drawing Pad

public void mouseReleased(MouseEvent e) {


Point p = e .getPoint();
tool .endShape(p);
canvas .mouseButtonDown = false ;
}

public void mouseClicked(MouseEvent e) {}


public void mouseEntered(MouseEvent e) {}
publi c void mouseExited(MouseEvent e) {}
publi c void mouseMoved(MouseEvent e) {}
protected ScribbleCanvasListener(ScribbleCanvas canvas, Tool tool) {
this .canvas= canvas ;
this . tool= tool ;
}

protected ScribbleCanvas canvas;


protected Tool tool;
}

The refactoring concerning the tools is also an application of the Strategy design
pattern. In the next iteration, the design will be further extended to support multiple
tools, in which case the Strategy pattemconcerning the tools here will become a part
of a new design pattern-State (see Section 9.5.3).

9.4.3 Extending Components

The various components of the drawing·pad will be enhanced and extended in several
iterations. Enhancements can usually be accomplished in two ways: :
I. Modifying the original class
2. Building a new class that extends the original class
Usually the same enhancement can be accomplished in either way. However, enhance-
I
ment by extending the original class has several advantages:
• It is nondestructive. The original implementation is untouched, and the enhance-
ments or changes can be easily re.versed.
• It is more suited for iterative development, in which systems are built in small
increments. The separate classes fonn the natural boundaries of the increments.
• It is the only option when the source code of the original class is not available
or the original class should not be modified for some reason; e.g., it is used by
other programs that should not be affected by the enhancements or changes to
the class.

In iterations 2 and 3, we modified the original code in classes Seri bble, Scribble-
Canvas, and Seri bbleCanvasListener to implement enhancements and changes.
In all the subsequent iterations, we will use the extension technique to implement ,
enhancements and changes. Specifically, new versions of the canvas and canvas
9.4 Iteration 3: Refactoring • 429

)jstener classes will extend the previous version of the canvas and canvas listener
classes, respective]y.
The scribble3 . Scribble class is nearly identical to the scribble2 . Scrib-
ble class. The only difference is that in scribble3. Scribble the canvas is not
created directly by using the new operator; rather it is created indirectly by using a
factory method-makeCanvas () . Using a factory method instead of the new oper-
ator allows subclasses of the Seri bble class to instantiate instances of subclasses of
ScribbleCanvas, which represent enhanced versions of the canvas. To replace the
canvas with an enhanced canvas, a subclass of the Scribble class can override the
factory methods to create instances of the enhanced canvas. No change in the Serib-
ble class would be necessary. The benefits of using factory methods will become clear
in iteration 4 [p. 445).

package scribble3 ;
import java.awt . •;
import java.awt.event.•;
import java.io.•;
import javax.swing.•;
public class Scribble extends JFrame {
public Scribble() {
super("Scribble Pad");
// calling the factory method
canvas• makeCanvas() ;
getContentPane().setLayout(new BorderLayout());
menuBar = createMenuBar();
getContentPane() . add(menuBar, BorderLayout .N0RTH);
getContentPane().add(canvas, BorderLayout.CENTER);
addWindowListener (new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (exitAction ! null) {
3

exitAction . actionPerformed(new ActionEvent


(Scribble.this, 0, null));
}
}
});
}

// the factory method


protected ScribbleCanvas makeCanvas() {
return new ScribbleCanvas() ;
}

(createMenuBarO method on page 411}


(newFile () , openFile (), saveFile (), and saveFileAs () methods on page 416}
(Nested class NewFileListener on page 413}
(Nested class OpenFileListener on page 413}
(Nested class SaveFileListener on page 414}
430 • Design Case Study: A Drawing Pad

{Nested class SaveAsFileListener on page 415)


{Nested class Exi tListener on page 415)
{Nested class ColorLis tener on page 417)
{Nested class AboutListener on page 413)
}

The scribble3.ScribbleCanvas class is also very similar to scribble2


. ScribbleCanvas. The main differences in the scribble3. ScribbleCanvas
class are these:

■ The canvas listener is also created using a factory rnethod-makeCanvas-


Listener O.
• The drawings are stored as a list of shapes instead of a list of strokes. The
strokes field in scribble2. ScribbleCanvas is replaced by the shapes
field in scribble3. ScribbleCanvas. Corresponding changes are also made
in methods newFile O, openFile O, saveFile O, and paint().

package scribble3;
import java.avt.Color;
import java.avt.Dimension;
import java.avt .Graphics;
import java. avt.Point;
import java.util.•;
import java.io.•;
import java.avt.event . •;
import java.util . EventListener;
import javax.swing. •;
public class ScribbleCanvas extends JPanel {
public ScribbleCanvas() {
II calling the factory method
listener= makeCanvasListener();
addMouseListener((MouseListener) listener);
addMouseMotionListener((MouseMotionListener) listener);
}

public void setCurColor(Color curColor) {


this.curColor • curColor;
}

public Color getCurColor() {


return curColor;
}

public void addShape(Shape shape) {


if (shape !• null) {
shapes . add(shape) ;
}
}

...... se--z
9.4 Iteration 3: Refactoring • 431

public void paint(Graphics g) {


Dimension dim= getSize();
g.setColor(Color.white) ;
g .f i llRect(O, 0, dim .width, dim.height);
g.setColor(Color.black);
if (shapes != null) {
Iterator iter = shapes . iterator();
while (iter.hasNext()) {
Shape shape= (Shape) iter.next();
if (shape!= null) {
shape.draw(g);
}
}
}
}

public void newFile() {


shapes . clear() ;
repaint();
}

public void openFile(String filename) {


try {
ObjectinputStream in=
new ObjectinputStream(new FileinputStream(filename));
shapes= (List) in . readObject() ;
in. close() ;
repaint();
} catch (IOException el) {
System . out.println( 11 Unable to open file: 11 + filename);
} catch (ClassNotFoundException e2) {
System . out .println(e2);
}
}

public void saveFile(String filename) {


try {
ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream(filename));
out.writeObject(shapes);
out . close() ;
System . out .println( 11 Save drawing to 11 + filename);
} catch (IOException e) {
System. out .println( 11 Unable to write file: 11 + fil ename) ;
}
}

// the factory method


protected EventListener makeCanvasListener() {
return new ScribbleCanvasListener(this);
}
432 • Design Case Study: A Drawing Pad

protected List shapes= new ArrayList();


protected Color curColor = Color.black;
protected EventListener listener;
public boolean mouseButtonDow = false;
public int x, y;
}

This completes the third iteration of the drawing pad, which is concerned with
refactoring only to improve extensibility without enhancement in functionality.

ITERATION 4: ADDING SHAPES AND TOOLS

In this iteration, we enhance the functionality of the drawing pad. The refactoring
of the design in the last iteration allows these enhancements to be added easily and
incremental]y. A screen shot of this version of the drawing pad is shown in Figure 9.12.
The drawing pad has a tool bar at the left, with each tool button representing a
different tool. Clicking on the tool button selects the tool for the drawing. The tools
supported by this drawing pad are the scribbling tool, the line-drawing tool, the
rectangle-drawing tool, and the oval-drawing tool. This version also adds a new
menu-Tools-on the menu bar. A drawing tool can also be selected from the menu
item corresponding to the tool.

Figure 9.12
flle Tools Opdan Help
The drawing pad-
Iteration 4.
Satbllle

une I
j_ ( ) /7 /

JQJa is vers0:wv ,
Remn1111

________
-....,;;;;;;.;;;.;,., ,,
..,
Iteration 4: Adding Shapes and Tools • 433

The key issues addressed in this iteration are the following:

■ Use of the State design pattern to support the different behaviors associated with
different tools and to switch among different tools dynamically.
■ Use of the Factory Method design pattern to allow flexibility in creating instances
of different subclasses.

9.5.1 The Shapes

In this iteration, the drawing of the following types of shapes will be supported: line,
oval, and rectangle. These three types of shapes share a common characteristic-
each of these shapes can be completely defined by two points: the two end points for
lines and the two diagonally opposite comers for rectangles and ovals. We call these
two points the end points. An abstract class named TwoEndsShape is introduced to
capture this common feature. The design concerning the shapes in this iteration is
shown in Figure 9.13.
The methods of the TwoEndsShape class are summarized in the following table:

Methods Description

setEnds() Set both end points.


setEndl () Set the first end point.
setEnd2() Set the second end point.
getXl () Return the x coordinate of the first end point
getYl () Return the y coordinate of the first end point.
getX2() Return the x coordinate of the second end point.
getY2() Return the y coordinate of the second end point.
drawOutline () Draw a temporary frame of the shape.

Figure 9.13 Serializable

The design of the


drawing pad-the
1
shapes. ScribbleCanvas

OrawlngCanvas Stroke

UneShape RectangleShape OvalShape


434 • Design Cas~ Study: A Drawing Pad

Abstract class drav1. TvolndaSh~


package drawl;
import java.awt .Graphics;
import java.awt.Color;
public abstract class TwoEndsShape extends scribble3.Shape implements
Cloneable {
public TwoEndsShape() {}
public TwoEndsShape(Color color) {
super(color);
}

public Object clone() throws CloneNotSupportedException {


return super.clone() ;
}

public void setEnds(int xl, int yl, int x2, int y2) {
this .xl = xl;
this.yl = yl;
this.x2 = x2;
this.y2 = y2;
}

public void setEndl(int xl, int yl) {


this.xl = xl;
this .yl = yl;
}

public void setEnd2(int x2, int y2) {


this.x2 = x2;
this . y2 = y2;
}

public int getXl() {


return xl;
}

public int getYl() {


return yl;
}

public int getX2() {


return x2;
}

public int getY2() {


return y2;
}

abstract public void drawOutline(Graphics g, int x1, int yl,


int x2, int y2);
protected int xl ;
protected int yl;
9.5 Iteration 4: Adding Shapes and Tools • 435

protected int x2;


protected int y2;
}

The three types of concrete shapes are defined as concrete subclasses of the
TwoEndsShape class.

Qaaj drav1 .Liu


package drawl;
import java .awt.•;
public class LineShape extends TwoEndsSbape {
public void draw(Graphics g) {
if (color != null) {
g.setColor(color);
}
g.drawLine(xl, yl, x2, y2);
}

public void drawOutline(Grapbics g, int xl, int yl,


int x2, int y2) {
g.drawLine(xl, yl, x2, y2);
}
}

drav1. Oval.SM~
package drawl;
import java.avt .• ;
public class DvalShape extends TwoEndsShape {
public void draw(Graphics g) {
int x = Math.min(xl, x2);
int y = Math .min(yl, y2);
int w = Math . abs(x1 - x2) + 1;
int h = Math .abs(yl - y2) + 1;
if (color != null) {
g.setColor(color) ;
}
g . draw□val(x, y, w, h);
}

public void draw□utline(Graphics g, int x1, int y1,


int x2 , int y2) {
int x = Math .min (xl, x2);
int y = Math . min (yl, y2);
int w = Math.abs(xl - x2) + 1;
int h = Math .abs(yl - y2) + 1;
g .drawOval(x, y, w, h) ;
}
}
436 • Onlgn Cas~ Study: A Drawing Pad

package drawl;
import java.awt.•;
public class RectangleShape extends TwoEndsShape {
public void draw(Graphics g) {
int x • Math.min(x1, x2);
int y = Math.min(y1, y2);
int w = Math.abs(x1 - x2) + 1;
int h = Math.abs(y1 - y2) + 1;
if (color!= null) {
g. setColor(color);
}
g.drawRect(x, y, w, h);
}

public void drawOutline(Graphics g, int xl, int yl,


int x2, int y2) {
int x = Math .min(xl, x2);
int y = Math .min(yl, y2);
int w = Math .abs(xl - x2) + 1;
int h = Math.abs(yl - y2) + 1;
g. drawRect(x, y, w, h);
}
}

9.5.2 The Toolkit

The design concerning the tools in this iteration is shown in Figure 9.14. The drawing
pad provides a set of different drawing tools. Each tool extends the abstract class
AbstractTool in the previous iteration. The Toolkit class represents a set of tools
supported by the drawing pad and keeps track of the currently selected tool. The
responses to a mouse button press or release and mouse dragging on the drawing

Figure 9.14
Scribble
The design of the
drawing pad-the
tools. 1 •
DrawfngPad ic> - -- i Toolkit Tool

AbstractTool

TwoEndsTool ScribbleTool
....,.
9.5 Iteration 4: Adding Shapes and Tools ■ 437

canvas depend on which tool is currently selected. In the Toolkit class, each tool
can be identified either by its name or by its position in the toolkit. In the DrawingPad
class, an integer field is used to indicate the position of the tool to be selected. The
methods of the Toolkit class are summarized in the following table:

Methods Description

addTool () Add a new tool to the toolkit.


getToolCount () Return the number of tools in the toolkit.
getTool (i) Return the i-th tool in the toolkit.
findTool (name) Return the tool with the given name.
setSelectedTool (i) Set the i-th tool to be the current tool.
setSelectedTool (name) Set the tool with the given name to be the current
tool.
setSelectedTool () Set the specified tool to be the current tool.
getSelectedTool () Return the selected tool.

a.
package dra'Wl;
import java .util . *;
import scribble3.Tool;
public class ToolKit {
public ToolKit() {}
public int addTool(Tool tool) {
if (tool != null) {
tools . add(tool);
return (tools . size() - 1) ;
}
return -1 ;
}

public int getToolCount ( ) {


return tools.size() ;
}

public Tool getTool(int i) {


if (i >= 0 U
i < tools.size () ) {
return (Tool) tools .get (i) ;
}
retur n null ;
}
438 • Design Case Study: A Drawing Pad

public Tool findTool (String name) {


if (name != null) {
for (inti= O; i < tools . size(); i++) {
Tool tool= (Tool) tools.get(i);
if (name . equals(tool . getName())) {
return tool ;
}
}
}
return null;
}

public void setSelectedTool(int i) {


Tool tool= getTool(i) ;
if (tool != null) {
selectedTool = tool;
}
}

public Tool setSelectedTool(String name) {


Tool tool= findTool (name);
if (tool != null) {
selectedTool = tool;
}
return tool;
}

public void setSelectedTool(Tool tool) {


selectedTool = tool;
}

public Tool getSelectedTool() {


return selectedTool;
}

I
protected Li st tools = new ArrayList(16);
protected Tool selectedTool = null;
}

9.5.3 Design Pattern: State

The refactoring concerning the toolkit illustrates the State design pattern, which
allows encapsulation of the behavior of each tool in a separate class and decouples
the tools from the DrawingPad class.

I
Design Pattern State
Catego,y: Behavioral design pattern.
Intent: Allow an object to alter its behavior when its internal state changes.
Also Known As: Objects for states.

J
9.5 Iteration 4: Adding Shapes and Tools ■ 439

Applicability: Use the State design pattern


• when an object's behavior depends on its state and it must change its
behavior at run time, depending on that state (e.g., selecting among
several tools).
• when methods have large, multipart conditional statements that depend
on the object's state (e.g., the switch statements in the DrawingPadLis-
tener class [p. 424]).

The structure of the State design pattern is shown in the following diagram.

Context State
,~ State
request()Q Iv handle()
t;,
I
,. - - - - - - - _ ,_ - - - - - - - '"I
I I

state.handle()
ConcreteStateA ConcreteStateB
handle() handle()

The participants of the State design pattern are as follows:

■ Context (e.g., DrawingPad), which maintains an instance of a ConcreteState


that defines the current state (e.g., the currentTool field in the DrawingPad
class).
■ State (e.g., Tool), which defines an interface for encapsulating the behavior
associated with a particular state of the Context.
■ ConcreteState (e.g., ScribbleTool), in which each subclass implements a
behavior associated with a state of Context.

9.5.4 A Concrete Tool-TwoEndsTool

When we select the line-drawing, rectangle-drawing, or oval-drawing tool, we can


use the mouse to draw a line, a rectangle, or a circle. Although these shapes have
little in common, the ways in which they are drawn are quite similar-by defining the
two end points. These shapes are drawn as follows:

• When the mouse button is pressed, the current mouse position defines the first
end point of the selected shape.
• When the mouse is dragged, temporary frames of the selected shape are drawn .
These temporary frames follow the mouse as it is being dragged. This process
is commonly known as rubber banding because dragging the mouse is like
stretching a rubber band.
• When the mouse button is released, the current mouse position defines the second
end point of the selected shape. The selected shape defined by the two end points
is drawn.
440 • Design Case Study: A Drawing Pad

Because of the similarities in drawing Jines, rectangles, and ovals, we design a


single tool, the TwoEndsTool, to handle all three shape . Each shape is identified
by an integer constant: LINE, OVAL, or RECT. The fields of the TwoEndsTool are
summarized in the following table:

Field Description

shape Shape to be drawn


xStart, yStart Coordinates of the first end point

Ciass drav1. TvoEndsTool


package drawl;
import java .awt.*;
import scribble3 .* ;
public class TwoEndsTool extends AbstractTool {

public static final int LINE= O;


public static final int OVAL= 1;
public static final int RECT = 2;
public TwoEndsTool(ScribbleCanvas canvas, String name, int shape) {
super (canvas, name) ;
this.shape= shape;
}
public void startShape(Point p) {
canvas.mouseButtonDown = true;
xStart = canvas.x = p . x;
yStart = canvas .y = p .y;
Graphics g = canvas.getGraphics();
g. setXORMode(Color.darkGray);
g. setColor(Color . lightGray);
switch (shape) {
case LINE :
drawLine(g, xStart, yStart, xStart, yStart);
break;
case OVAL :
drawOval (g, xStart, yStart, 1 , 1);
break;
case RECT:
drawRect(g, xStart, yStart , 1, 1);
break ;
}
}
1

9 .5 Iteration 4: Adding Shapes and Tools • 441

public void addPointToShape(Point p) {


if (canvas.mouseButtonDown) {
Graphics g = canvas.getGraphics();
g.setXORMode(Color.darkGray);
g . setColor(Color . lightGray);
swit ch (shape) {
case LINE:
drawLine(g, xStart, yStart, canvas.x, canvas .y);
drawLine(g, xStart, yStart , p . x, p . y);
break ;
case OVAL:
drawOval(g, xStart, yStart, canvas.x - xStart + 1,
canvas . y - yStart + l);
drawOval(g, xStart, yStart, p.x - xStart + 1,
p . y - yStart + l);
break;
case RECT:
drawRect(g, xStart, yStart, canvas . x - xStart + 1,
canvas.y - yStart + 1);
drawRect(g, xStart, yStart, p.x - xStart + 1,
p.y - yStart + 1);
break;
}
canvas.x p.x;
canvas.y p.y;
}
}

public void endShape(Point p) {


canvas .mouseButtonDown = false;
TwoEndsShape newShape = null;
switch (shape) {
case LINE:
newShape = new LineShape();
break;
case OVAL:
newShape new OvalShape() ;
break;
case RECT:
newShape = new RectangleShape();
}
if (newShape != null) {
newShape.setColor(canvas . getCurColor());
newShape.setEnds(xStart, yStart, p . x, p.y);
canvas.addShape(newShape);
}
Graphics g = canvas.getGraphics();
g.setPaintMode();
canvas.repaint();
}

protected int shape= LINE;


protected int xStart, yStart;

I ,
442 • Design Case Study: A Drawing Pad

// helper methods
public static void drawLine(Graphics g,
int x1, int y1, int x2, int y2) {
g.drawLine(x1, y1, x2, y2);
}

public static void drawRect(Graphics g,


int x, int y, int w, int h) {
if (w < 0) {
X = X + W;
w = -w;
}
if (h < 0) {
y = y + h;
h = -h;
}
g.drawRect(x, y, w, h);
}

public static void drawOval(Graphics g, int x, int y, int w,


int h) {
if (w < 0) {
X = X + w;
w = -w;
}
if (h < 0) {
y = y + h;
h = -h;
}
g.drawOval(x, y, w, h);
}

The mousePressed O method records the coordinates of the first end point and
prepares for rubber banding. Rubber banding is accomplished by using the exclusive-
or (XOR) mode of the graphics context. When the XOR mode is used in a graphics
context and a figure is drawn once, the figure is visible. However, the color of the
figure depends not only on the current pen color, but also on the colors of the pixels
that the figure covers. When the same figure is drawn twice in the same position, the
figure disappears. The mouseDragged() method performs the rubber banding. The
values canvas . x and canvas. y are the coordinates of the intermediate preceding
location of the mouse. The mouseReleased () method records the second end point
of the shape. It sets the mode of the graphics context to the default paint mode. A new
instance of the selected shape is created, and the selected shape is drawn on the canvas.

9.5.5 Extending Components

In this iteration, the main application, the canvas, and the canvas listener classes a11
extend their counterparts in the previous iteration. Factory methods are used in the
ex tended classes to create instances of the extended classes. The design is shown in
Figure 9.15.

______..._..__.
9.5 Iteration 4: Adding Shapes and Tools • 443

Figure 9.15
Scribble ,-1. DrawingPad
The overall design
scribble() Q makeCanvas()
of the drawing 0
pad-iteration 4. I
'\
...
makeCanvas()
...

ScribbleCanvas (} DrawlngCanvas

ScribbleCanvas () 9 makeCanvaslistener()
I '\
...
makeCanvaslistener()
...

ScribbleCanvaslistener f"
1.... I
I DrawlngCanvasllstener I

The drawl . DrawingCanvasListener class extends the scri bble3


. ScribbleCanvasListener class in the previous iteration. In the scribble3
. ScribbleCanvasListener class, only one tool, the scribble tool, is supported. In
the drawl. DrawingCanvasListener class, the tool can be set to any tool supported
by the drawing pad.

Chm drav1. Drav~~pvuLiatener


package drawl;
import java.awt . •;
import java.awt . event . •;
import scribble3 . •;
public class DrawingCanvasListener extends ScribbleCanvasListener {
public DrawingCanvasLi stener (DrawingCanvas canvas) {
super(canvas , null) ;
}

publ ic Tool getTool() {


return tool;
}

public void setTool(Tool tool ) {


this.tool= tool;
}

}
444 • Design Case Study: A Drawing Pad

The draw1.DrawingCanvas class extends the scribble3 . Scribble class.


The factory method rnakeCanvasListener O is used to create an instance of the
enhanced canvas listener.

Class dravl. Dravi~anvas


package drawl;
import java . awt . Color ;
import java .awt.Dimension;
import java . awt . Graphics;
import java.awt.Point;
import java.util.•;
import java . io .•;
import java .awt .event.•;
import java .util.EventListener;
import javax .swing. •;
import scribble3 . •;
public class DrawingCanvas extends ScribbleCanvas { I

I
public DrawingCanvas() {
}
I
public void setTool (Tool tool) {
drawingCanvasListener . setTool(tool);
}

public Tool getTool() {


return drawingCanvasListener.getTool();
}

// the factory method


protected EventListener makeCanvasListener() {
return (drawingCanvasListener =
new DrawingCanvasListener(this)) ;
}

protected DrawingCanvasListener drawingCanvasListener;


}

The drawl . DrawingPad class is the main application class. It manages the
toolkit and is responsible for creating the tool bar on the left and the tool menu on
the menu bar for selecting the current drawing tool. The methods of the DrawingPad
class are summarized in the following table:

Methods Description

ini tTools O Create the tools and initialize the toolkit.


getSelectedTool O Return the currently selected drawing tool.
9 .5 Iteration 4: Adding Shapes and Tools • 445

makeCanvas 0 Create the canvas (the factory method).


createToolBar () Create the tool bar on the left side.
createToolMenu() Create the tool menu on the menu bar.

The factory method makeCanvas () is used to create an instance of the enhanced


canvas. An anonymous nested class is used as the action listener for both the tool
bar and the tool menu for selecting the drawing tool. The menu bar is constructed
incrementally. The menu bar is first constructed in the constructor of the superclass
without the tool menu. The tool rne~u is created by the createToolMenu O method
and then inserted into the menu bar.

Class draw1. Drawin~ad


package drawl;
import java.awt . * ;
import java.awt . event.•;
import java.io.•;
import javax.swing.*;
import scribble3.•;
public class DrawingPad extends Scribble {
public DrawingPad(String title) {
super(title);
ini tTools () ;
II an anonymous nested class to select the current tool
ActionListener toolListener = new ActionListener() {
public void actionPerformed(ActionEvent event) {
Object source= event.getSource ( );
if (source instanceof AbstractButton) {
AbstractButton button= (AbstractButton) source;
Tool tool=
toolkit . setSelectedTool(button.getText());
drawingCanvas . setTool(tool);
}
}
};
II create the tool bar
JComponent toolbar = createToolBar(toolListener) ;
getContentPane () .add(toolbar, BorderLayout.WEST);

II create the tool menu


JMenu menu= createToolMenu(toolListener);
II insert the tool menu into the menu bar
menuBar . add (menu, 1) ; II insert at index position 1
}

public Tool getSelectedTool() {


return toolkit . getSelectedTool();
}
446 • Design Case Study: A Drawing Pad

JI create the drawing tools and initialize the toolkit


protected voi d i ni tTools () {
t oolkit= new ToolKit ();
toolkit . addTool (new ScribbleTool (canvas, "Scr i bble ")) ;
toolkit . addTool (new TwoEndsTool (canvas, "Line" , TwoEndsTool . LINE)) .
toolki t. addTool (new TwoEndsTool (canvas, "Oval ", TwoEndsTool. OVAL))'.
toolkit . addTool (new TwoEndsTool (canvas, "Re ctangle" , '
TwoEndsTool .RECT));
drawingCanvas.setTool(toolkit .getTool (O) );
}

JI the factory method


protected ScribbleCanvas makeCanvas () {
return (drawingCanvas = new DrawingCanvas( ) );
}
protected ]Component createToolBar(ActionListener toolListener) {
JPanel toolbar = new JPanel(new GridLayout(O, 1)) ;
JI create a button for each tool
int n = toolkit.getToolCount () ;
for (i nti= O; i < n; i++) {
Tool tool = toolkit.getTool(i);
if (tool != null) {
]Button button= new JButton (tool .getName( ) );
button .addActionListener(toolListener) ;
toolbar . add(button) ;
}
}
return toolbar ;
}
protected JMenu createToolMenu(ActionListener toolListener) {
JMenu menu= new JMenu ("Tools");
II create a menu item for each tool
int n = toolkit.getToolCount ();
for (int i = O; i < n ; i++) {
Tool tool = toolkit .getTool(i);
if (tool != null) {
JMenuitem menuitem = new JMenuitem (tool . getName ( ) );
menui tem.addActionListener(toolList ener) ;
menu . add(menuitem) ;
}
}
return menu ;
}

protect ed ToolKi t t oolkit;


protected DrawingCanvas draw ingCanvas;
public stati c void mai n(St ring[] args) {
JFrame frame= new Dr awi ngPad("Drawing Pad ");
frame.setSize( wi dth , he ight );
Dimension s creenSize =
java.awt.Toolkit .get DefaultTool ki t ( ) .getScreenSize( );
9.5 Iteration 4: Adding Shapes and Tools • 44 7

// place the application frame at the center of the screen


frame.set Location(screenSize . width/2 - width/2,
screenSize.height/2 - height/2);
frame . show() ;
}

This completes the fourth iteration of the drawing pad.

9.5.6 Design Pattern: Factory Method

Design Pattern Factory Method


Category: Creational design pattern.
Intent: To define an interface for creating an object but defer instantiation to the
subclasses.
Also Known As: Virtual constructor.
Applicability: Use the Factory Method design pattern
• when a class cannot anticipate the class of objects it must create.
• when a class defers to its subclasses to specify the objects it creates .

The structure of the Factory Method design pattern is shown in the following
diagram:

Product Creator
factoryMethod()
anOperation() o ..
"
.~ Product = factoryMethod(}

Concrete Product ConcreteCreator


factoryMethod() 0

Return new ConcreteProduct()

The participants of the Factory Method design pattern are the following:

■ Product (e.g., EventListener), which defines the interface of the objects to be


created.
■ ConcreteProduct (e.g., ScribbleCanvasListener and ToolListener),
which implements the Product interface, and may provide default implemen-
tation.
- 448 • Design Case Study: A Drawing Pad

■ Creator (e.g., Scribble), which defines one or more fac tory methods (e.g.
makeCanvasListener ()) that create abstract products, that is, objects of type
Product. The Creator may provide a default implementation (e.g., the implemen-
tation of the makeCanvasListener O method in the Scribble class) and may
call factory methods to create Product objects (e.g., invocation of the makeCan-
vasListenerO method in the constructor of the Scribble class).
■ ConcreteCreator (e.g., DrawingPad), which overrides the factory method to
return an instance of a ConcreteProduct (e.g., implementation of the make-
CanvasListener () method in the DrawingPad class).

Factory Method and Factory (see Section 7.4.3 [p. 296]) are different design patterns.
The Factory design pattern involves a factory class whose sole responsibility is to
create objects. The Factory Method is for a class to defer the creation of certain objects
to its subclasses.

9.6 ITERATION 5: MORE DRAWING TOOLS

The go_al of this iteration _is rather simple. We want to further enhance the drawing pad
by addmg tools for drawmg filled ovals and rectangles. An ad hoc extension is rather
straightforward, but it is inelegant and difficult to maintain. We refactor the design of
the TwoEndsTool class to make it more extensible.

9.6.1 Filled Shapes

'
First, we define two new shapes, Filled0valShape and FilledRectangleShape,
by extending OvalShape and RectangleShape, respectively:

Class draw2. FilledOvalShaJ>e


package dra.r2;
import java.avt.•;
import dra.rl.•;
public class FilledOvalShape extends OvalShape {

public void dra.r(Graphics g) {


int x = Math.min(xl, x2);
int y = Math.min(yl, y2);
int .r = Math .abs(xl - x2) + 1;
int h = Math . abs(yl - y2) + 1;
if (color != null) {
g.setColor(color);
}
g.fillOval(x, y, .r, h);
}
9.6 Iteration 5: More Drawing Tools • 449

Classdraw2.FilledRectangleShape
package draw2 ;
import j ava.awt.*;
import drawl . *;
public class FilledRectangleShape extends RectangleShape {
public void draw(Graphics g) {
int x = Math.min(xl, x2);
int y = Math.min(yl, y2);
int w = Math . abs(xl - x2) + 1 ;
int h = Math . abs(yl - y2) + 1;
if (color != null) {
g . setColor (color);
}
g . fillRect(x, y, w, h) ;
}
}

9.6.2 Drawing Filled Shapes

Drawing filled rectangles and ovals is very similar to drawing unfilled rectangles and
ovals. A simple ad hoc approach to support the drawing of filled rectangles and ovals
is to modify the TwoEndsTool as follows :

public class TwoEndsTool implements Tool {


public static final int LINE=
public static final int OVAL = . . .
public static final int RECT = . . .
public static final int FILLED_OVAL • .
public static final int FILLED_RECT • .
public void mousePressed(Point p, ScribbleCanvas canvas) {
II . ..
switch (shape) {
case LINE: II . . .
case OVAL: II . . .
case RECT : II . . .
case FILLED_OVAL : II . . .
case FILLED_RECT : II . . .
}
}

public void mouseDragged(Point p, Scr i bbleCanvas canvas ) {


II . . .
switch (shape) {
case LINE : II .. .
case OVAL : // . . .
case RECT : II . . .
450 • Design Case Study: A Drawing Pad

case FILLED_OVAL: II . . .
case FILLED_RECT : II .. .
}
}

public void mouseReleased(Point p, ScribbleCanvas canvas) {


II . ..
switch (shape) {
case LINE: II .. .
case OVAL: II .. .
case RECT: II . . .
case FILLED_OVAL: II .. .
case FILLED_RECT : II . . .
}
}
}

In this approach, modification is required for each new shape. The relevant code
segments for each shape are scattered in three different methods: mousePressed (),
mouseDragged (), and mouseReleased O. Modifications to these methods must
be carefully coordinated because the shapes to be drawn are tightly coupled with the
two-ends tool. It is rather cumbersome and error-prone to add new shapes using this
approach. Adding new shapes would be much simpler if the shapes to be drawn could
be decoupled from the two-ends tool.

Figure 9.16
draw1 .DrawingPad
The design of the
drawing pad-
iteration 5. draw2.DrawingPad Toolkit *
Tool

AbstractToaf

LineShape FilledRectangleShape
I
OvalShape FllledOvalShape

RectangleShape
9.6 Iteration 5: More Drawing Tools • 451

A better design is to separate the shape from the tool for drawing that shape
using the Strategy design pattern. The TwoEndsShapeTool class, instead of us-
ing an integer value to indicate the shape to be drawn, contains a reference to
TwoEndsShape, which represents the abstract strategy; the classes that implement
the TwoEndsShape interface represent the concrete strategies. Using the Strategy
pattern, the switch statements that are tightly coupled with the shapes are eliminated.
The TwoEndsShapeTool class does not contain code that is specific to any particular
shape. The design is shown in Figure 9.16
The prototype object serves as a representative of the shape to be drawn. The
mousePressed () , mouseDragged () , and mouseReleased () methods delegate
actions specific to each shape to the prototype object. The new shape object to be
added to the drawing is created by cloning the prototype object.

Class drav2. TvoEndsSha eTool


package draw2;
import java . awt .•;
import scribble3.•;
import drawl . •;
public class TwoEndsShapeTool extends AbstractTool {
public TwoEndsShapeTool(ScribbleCanvas canvas, String name,
TwoEndsShape prototype) {
super(canvas, name) ;
this.prototype= prototype;
}

public void startShape(Point p) {


if (prototype != null) {
canvas .mouseButtonDown = true;
xStart = canvas.x = p.x;
yStart = canvas .y = p .y;
Graphics g = canvas .getGraphics() ;
g.setXORMode(Color.darkGray);
g.setColor(Color.lightGray);
prototype .drawOutline(g, xStart, yStart, xStart, yStart);
}
}

public void addPointToShape(Point p) {


if (prototype != null &&
canvas.mouseButtonDown) {
Graphics g = canvas . getGraphics();
g.setXORMode(Color.darkGray);
g . setColor(Color.lightGray);
prototype.drawOutline(g, xStart, yStart, canvas.x, canvas.y);
prototype.drawOutline(g, xStart, yStart, p.x , p.y);
}
}
452 • Design Case Study: A Drawing Pad

public void endShape(Point p) {


canvas .mouseButtonDown false;
if (prototype != null) {
try {
TwoEndsShape newShape = (TwoEndsShape) prototype.clone ();
newShape . setColor(canvas.getCurColor ());
newShape.setEnds(xStart, yStart, p . x , p.y) ;
canvas . addShape (newShape) ;
} catch (CloneNotSupportedException e) {}
Graphics g = canvas.getGraphics();
g . setPaintMode();
canvas. repaint();
}
}

protected int xStart, yStart ;


protected TwoEndsShape prototype;
}

9.6.3 The Application

The main application class draw2. DrawingPad overrides the ini tTools () method
to include two additional drawing tools for filled ovals and filled rectangles. No other
changes are necessary.

, Chm draw2. Dravi~Pad


package draw2;
import java.awt.*;
import java.awt.event.*;
import java . io.*;
import javax.swing.*;
import scribble3 . *;
import drawl.*;
public class DrawingPad extends drawl .DrawingPad {
public DrawingPad(String title) {
super(title);
}

II create the drawing tools and initialize the toolkit


protected void initTools() {
toolkit= new ToolKit();
toolkit . addTool(new ScribbleTool (canvas, "Scribble ") );
toolkit. addTool (new TwoEndsShapeTool (canvas, "Line",
new LineShape ( ) ) ) ;
toolkit . add Tool (new TwoEndsShapeTool (canvas, "Oval " ,
new OvalShape () ) ) ;
toolkit . addTool(new TwoEndsShapeTool (c anvas , "Rect" ,
new Rectangl eShape ()));
toolkit . addTool (new TwoEndsShapeTool (canvas , "Filled Oval",
new Fill edOvalShape ()));
9. 7 Iteration 6: The Text Tool • 453

toolkit . addTool(new TwoEndsShapeTool(canvas, "Filled Rect",


new FilledRectangleShape()));
drawi ngCanvas . setTool(toolkit.getTool(O)) ;
}

public static void main(String[] args) {


JFrame frame= new draw2 . DrawingPad("Drawing Pad");
frame . setSize(width, height);
Dimension screenSize =
java.awt . Toolkit . getDefaultToolkit() . getScreenSize();
II place the application frame at the center of the screen
frame.setLocation(screenSize . width/2 - width/2,
screenSize . height/2 - height/2);
frame . show();
}

This completes the fifth iteration of the drawing pad.

ITERATION 6: THE TEXT TOOL

In this iteration, we further enhance the drawing pad by adding a different type of
tool: a tool for typing text using the keyboard. A screen shot of the new drawing pad
is shown in Figure 9.17. The design is shown in Figure 9.18 The key issues addressed
in this iteration are handling keyboard input and keyboard focus.

Figure 9.17
, Ale Tools Option
The drawing pad-
iteration 6. Salbble
Java is easy!
Line Java is easy!
Java 1 s easy !

AIied Oval

AIied Rect
454 • Design Case Study: A Drawing Pad

Figure 9.18 draw2. Drawing Pad

The design of the


drawing pad-
iteration 6. draw3.DrawlngPad K>-----1 Toolkit
'----~

Text 1-----< ScribbleTool

TwoEndsShapeTool

LineShape FilledRectangleShape

OvalShape FilledOvalShape

RectangleShape

9. 7.1 The Text Shape

The draw3. Text class is a subclass of the Shape class representing a text string.
The field text represents the keys typed. The field font indicates the font to be used
to draw the text. The fields x and y represent the location of the text.

Class drav3. Text


package draw3 ;
import java . awt.•;
public class Text extends scribble3.Shape {
public Text O {}
public Text(Color color) {
super (color);
}

public void setLocation(int x, int y) {


this.x = x;
this.y = y;
}
9.7 Iteration 6: The Text Tool • 455

public int getX() {


return x;
}

public int getY() {


return y;
}

public void setText(String text) {


this.text= text;
}

public String getText() {


return text;
}

public Font getFont() {


return font;
}

public void setFont(Font font) {


this . font= font;
}

public void draw(Graphics g) {


if (text != null) {
if (color != null) {
g.setColor(color);
}
if (font != null) {
g . setFont(font);
} else {
g.setFont(defaultFont);
}
g . drawString(text, x, y);
}
}

protected int x;
protected int y;
protected String text;
protected Font font;

protected static Font defaultFont =


new Font("Serif", Font . BOLD, 24);

9.7.2 The Keyboard Input Tool

The Tool interface defined earlier is intended to handle the input from a mouse. It is
inadequate to handle input from a keyboard. To define tools that handle key presse
from a keyboard, we introduce an extended interface KeyboardTool.
-

456 ■ Design Case Study: A Drawing Pad

Interface drav3. Ke boardTool


package draw3;
import scribble3 .Tool ;
public interface KeyboardTool extends Tool {
public void addCharToShape(char c);
}

The TextTool class represents a concrete tool that handles input from the
keyboard. The text field stores the characters that have been typed. Because the
text field needs to be updated whenever a key is pressed, we use StringBuffer
instead of String for the sake of efficiency. The behavior of the text tool is as follows:

■ A mouse press indicates the position in the canvas where the text will be dis-
played.
■ Whenever a key is pressed, the corresponding character is appended to text,
and it is drawn in the canvas.

This way of handling keyboard input is rather primitive. It does not support editing
capabilities, such as deleting characters and moving the cursor.

Class drav3. TextTool


package draw3;
import java.awt . •;
import scribble3.•;
public class TextTool extends AbstractTool implements KeyboardTool {
public TextTool(ScribbleCanvas canvas, String name) {
super(canvas, name);
text= new StringBuffer();
}

public void startShape(Point p) {


text.delete(O, text.length());
curShape = new Text();
curShape.setColor(canvas.getCurColor());
curShape.setLocation(p . x, p.y);
if (canvas instanceof KeyboardDrawingCanvas) {
curShape .setFont(((KeyboardDrawingCanvas) canvas) .getFont()) ;
}
canvas.addShape(curShape);
}

public void addCharToShape(char c) {


text . append(c);
curShape . setText(text.toString());
canvas .repaint();
}
9.7 Iteration 6: The Text Tool • 45 7

public void addPointToShape(Point p) {}


public void endShape(Point p) {}
protected StringBuffer text;
protected Text curShape;
}

The event listener of the canvas must also be extended to receive and handle the
events originating from the keyboard. The listener interface for keyboard events is
KeyListener. The method keyPressed () of a key listener is invoked when one or
more keys on the keyboard are pressed. In the following implementation, if the current
tool is an instance of KeyEventTool , the keyPressed () method of the current tool
is invoked; otherwise, the key press is ignored:

Class draw3. Ke:rDrawi~anvaaLiatener


package draw3;
import java.awt.•;
import java . awt.event . •;
import drawl.•;
public class KeyboardDrawingCanvasListener
extends DrawingCanvasListener implements KeyListener {
public KeyboardDrawingCanvasListener(DrawingCanvas canvas) {
super(canvas);
}

public void keyPressed(KeyEvent e) {


if (tool instanceof KeyboardTool) {
KeyboardTool keyboardTool = (KeyboardTool) tool;
keyboardTool.addCharToShape((char) e .getKeyChar());
}
}

public void keyReleased(KeyEvent e) {}


public void keyTyped(KeyEvent e) {}
public void mouseClicked(MouseEvent e) {
canvas.requestFocus();
}

When dealing with input from the keyboard, we face another complication: key-
board focus. Often several windows are open on the screen, and multiple components
could accept input from the keyboard. However, only one component will receive
keyboard input at any moment: the component that currently has the keyboard focus.
The component having the keyboard focus is determined by a focus manager, which
provides a set of conventions for navigating the focus . In our drawing pad, we specify
that whenever the mouse is clicked on a component, that component will have the
458 • Design Case Study: A Drawing Pad

keyboard focus. Therefore, clicking the mouse on the canvas ensures that the canvas
has the keyboard focus. The implementation of the method mouse Cl i eked () of the
event listener of the canvas accomplishes this purpose by requesting the keyboard
focus when a mouse click event occurs on the canvas.
In the canvas class, in order to receive the keyboard input, it is necessary to define
the isFocusable () method to return true.

Chm drav3.K•~•v~anvaa
package draw3;
import java.awt.•;
i mport java.avt . event . *;
import java. util . EventListener;
import drawl . •;
public class KeyboardDrawingCanvas extends DrawingCanvas {
public KeyboardDrawingCanvas() {
addKeyListener((KeyListener) listener);
font= new Font(fontFamily, fontStyle, fontSize) ;
}

public Font getFont() {


return font;
}

public String getFontFamily() {


return fontfamily;
}

public void setFontfamily(String fontfamily) {


if (fontfamily!= null kk
!fontFamily.equals(this . fontfamily)) {
this.fontfamily =fontfamily;
font= new font(fontfamily, fontStyle, fontSize);
}
}

public int getfontSize() {


return fontSize;
}

public void setfontSize(int fontSize) {


if (fontSize > 0 kk
fontSize != this . fontSize) {
this.fontSize = fontSize ·
'
font = new font(fontfamily, fontStyle , fontSize);
}
}

public int getFontStyle() {


return fontStyle;
}
9.7 Iteration 6: The Text Tool • 4-59

public void setFontStyle(int fontStyle) {


if (fontStyle !• this.fontStyle) {
this.fontStyle = fontStyle;
font= new Font(fontFamily, fontStyle, fontSize);
}
}

II necessary for keyboard input


public boolean isFocusable() {
return true;
}

II factory method
protected EventListener makeCanvasListener() {
return (drawingCanvasListener =
new KeyboardDrawingCanvasListener(this));
}

protected String fontFamily = "Serif";


protected int fontSize = 24;
protected int fontStyle = Font.PLAIN;
protected Font font;
}

9. 7.3 The Font Option Menu

The main application class draw3. DrawingPad adds the new text tool to the toolkit
and adds a cascading menu to the menu bar for selecting the font family, font size,
and font style:

Class.drav3. Dravi Pad


package draw3;
import java.awt.•;
import java.awt.event.*;
import java . io.*;
import javax.swing.*;
import draw2.*;
import scribble3.*;
public class DrawingPad extends draw2.DrawingPad {
public DrawingPad(String title) {
super(title);
JMenu optionMenu = menuBar . getMenu(2);
addFontOptions(optionMenu);
}

II factory method
protected ScribbleCanvas makeCanvas() {
return (drawingCanvas = keyboardDrawingCanvas =
new KeyboardDrawingCanvas());
}
460 • Design Case Study: A Drawing Pad

protected void initTools() {


super.initTools();
toolkit.addTool(new TextTool(canvas, "Text"));
}
protected void addFontOptions(JMenu optionMenu) {
String[) fontFamilyNames = {
"Serif",
"Sans-serif",
"Monospaced",
"Dialog",
"Dialoginput"
};

int[] fontSizes = {
8, 10, 12, 16, 20, 24, 28, 32, 40, 48, 64
};

String[) fontStyleNames = {
"plain",
"bold",
"italic",
"bold italic"
};

inti;
ActionListener fontFamilyAction = new ActionListener() {
public void actionPerformed(ActionEvent event) {
Object source= event .getSource();
if (source instanceof JCheck.BoxMenuitem) {
JCheckBoxMenuitem mi= (JCheckBoxMenuitem) source;
String name= mi.getText();
keyboardDrawingCanvas.setFontFamily(name) ;
}
}
};
JMenu fontFamilyMenu = new JMenu("Font family");
ButtonGroup group= new ButtonGroup();
for (i = O; i < fontFamilyNames.length; i++) {
JCheckBoxMenultem mi=
new JCheck.BoxMenultem(fontFamilyNames[i]);
fontFamilyMenu.add(mi);
mi . addActionListener(fontFamilyAction);
group . add(mi);
}
optionMenu . add(fontFamilyMenu);
ActionListener fontSizeAction = new ActionListener() {
0

public void actionPerformed(ActionEvent ev ent) {


Object source= event .getSource();
if (source instanceof JCheckBoxMenuitem) {
JCheckBoxMenuitem mi= (JCheckBoxMenuitem) source;
String size= mi . getText();
9.7 Iteration 6: The Tm Tool • 461

try {
keyboard.DrawingCanvas . setFontSize(
Integer . parseint(size));
} catch (NumberFormatException e) {
e.printStackTrace() ;
}
}
}
};
JMenu fontSizeMenu = new JMenu("Font size") ;
group= new ButtonGroup();
for (i = O; i < fontSizes.length; i++) {
JCheckBoxMenuitem mi=
new JCheckBoxMenuitem(Integer.toString(fontSizes[i]));
fontSizeMenu.add(mi) ;
mi . addActionListener(fontSizeAction);
group.add(mi) ;
}
optionMenu . add(fontSizeMenu);
ActionListener fontStyleAction = new ActionListener() {
public void actionPerformed(ActionEvent event) {
Object source= event.getSource() ;
if (source instanceof JCheckBoxMenuitem) {
JCheckBoxMenuitem mi= (JCheckBoxMenuitem) source;
String styleName = mi .getText();
int style= Font.PLAIN;
if (styleName .equals("bold")) {
style= Font . BOLD;
} else if (styleName.equals ("italic")) {
style= Font.ITALIC;
} else if (styleName . equals ( "bold itali c") ) {
style= Font.BOLD I Font.ITALIC ;
}
keyboardDrawingCanvas.setFontStyle (style);
}
}
};
JMenu fontStyleMenu = new JMenu("Font styl e" ) ;
group= new ButtonGroup();
for (i = O; i < fontStyleNames.length; i++) {
JCheckBoxMenuitem mi=
new JCheckBoxMenuitem(fontStyleNames[i]);
fontStyleMenu.add(mi );
mi . addActionListener(fontStyleAction);
group.add(mi) ;
}
optionMenu.add(fontStyleMenu) ;
}

protected KeyboardDrawingCanvas keyboardDrawingCanvas ;


-
462 • Design Case Study: A Drawing Pad

public static void main(String[) args) {


JFrame frame= new draw3 .DrawingPad("Drawing Pad");
frame . setSize(width, height);
Dimension screenSize =
java.awt.Toolkit . getDefaultToolkit() . getScreenSize();
// place the application frame at the center of the screen
frame . setLocation(screenSize . width/2 - width/2,
screenSize . height/2 - height/2);
frame. show();
}

This completes the last iteration of this case study.

CHAPTER SUMMARY

■ The iterative process is used to develop large-scale software systems in a succes-


sion of iterations. Each iteration builds on the result of the preceding iteration and
enhances functionality in small increments. Each iteration involves a complete
development cycle, including conceptualization, analysis and modelinoo, <lesion t, '
I
and implementation. Each iteration results in a completely functional intermedi-
ate product.
■ Design patterns, such as Strategy, Template Method, Factory Method, and State,
are often used in the iterative development process to allow the functionality of
systems to be enhanced incrementally and changed dynamically.
■ The State design pattern allows an object to alter its behavior when its internal
state changes. The State design pattern should be used when an object's behavior
depends on its state and it must change its behavior at run time, depending on that
state, or when methods have large, multipart conditional statements that depend
on the object's state.
■ The Factory Method design pattern defines an interface for creating an object
but lets subclasses decide which class to instantiate. The Factory Method design
pattern should be used when a class cannot anticipate the class of objects it must
create or when a class wants its subclass to specify the objects it creates.

FURTHER READINGS

Bloch, J. (2001). Effective Java Programming Language Guide. Addison-Wesley.


Gamma, E., et al. (1995). Design Patterns- Elements of Reusable Object-Oriented
Software. Addison-Wesley.
Metsker, S. J. (2002). Design Patterns Java Workbook. Addison-Wesley.
Shalloway, A., and J. R. Trott (2001 ). Design Patterns Explained: A New Perspective
011 Object-Oriented Design. Addison-Wesley.
Project • 463

PROJECT

9.1 Use lhe iterative development approach lo add (c) Support selecting image files as the back-
ome of the following enhancements to the ground.
drawing pad program: (d) Support a zoom-in and zoom-out function.
(a) Add new drawing tools to draw different (e) Support moving and deleting shapes.
shapes, such as diamonds, polygons,
splines, and so on.
(b) Support selecting different background
colors. Refactor the code to avoid code
duplication with the selecting-pen-colors
function.
CHAPTER-10

More Design Patterns

CHAPTER OVERVIEW
In this chapter, we first present an idiom for type-safe enumeration types. We then discuss
a number of important design patterns, including Abstract Factory, Prototype, Builder,
Command, and Adapter. These design patterns will be illustrated in the context of several
complete programs.

rl 0.1 TYPE-SAFE ENUMERATION TYPES

10.1.1 A Simple Maze Game

In this chapter, we use a simple maze game as shown in Figure 10. l to illust_rate_an
implementation idiom and a number of important design patterns. The following is a
very brief description of the maze game:
th
The board of the maze game is an n x m grid. Figure 10.1 shows two instances of e
maze game: a small, 1 x 2 maze game, and a large, 3 x 3 maze game. Each square on
the board is a room. Adjacent rooms are eparated by either a wall or a door between
them. The doors can be either open or closed. Each maze game contains a player
through open doors.
represented by a dot. The player can move from room to room
Variou aspects of the game will be further elaborated later on.
465
466 • More Design Patterns

Figure 10.1

A simple maze
game.

10.1.2 Enumeration Types

Before we discuss the design of the maze game, we first present a useful implemen-
tation idiom for enumeration types in Java. An enumeration type consists of a finite
set of distinct values. To implement the maze game, we need to deal with a couple of
simple enumeration types:

■ Orientation, with values of horizontal and vertical. A wall or a door on the maze
board is in either horizontal or vertical position.
■ Direction, with values of north, south, east, and west. Each room has four sides
one in each direction. '

Some languages, such as CIC++, provide language support for enumeration


types. In these languages, enumeration types are distinct from all other types, and
their values are all distinct and noninterchangeable. Proper use of enumeration type
variables and values can be type checked at compile time.
Java does not support enumeration types. One simple and common approach to
implement enumeration types in Java is to treat enumeration types as integer types
and define integer constants for the values of enumeration types. For example, the
orientation and direction type can be defined as follows:

publi c interface Orientation {


public static final int HORIZONTAL= O;
public static final int VERTICAL = 1;
}

public interface Direction {


public static final int NORTH= O;
public static final int EAST = 1;
publi c static final int SOUTH= 2;
public static final int WEST = 3;
}
10.1 l'ype-Safe Enumeration Types • 467

Thi is a workable solution. However, it has a number of shortcomings. First, it is


not type-safe, and second, it is quite error-prone. For example, consider the following
variables of these enumeration types:

int orientation ; // an enumeration type variable for orientation


int direction; // an enumeration type variable for direction
orientation= Orientation.VERTICAL; llokay
direction = Direction.EAST; // okay

However, the following statements clearly make no sense, but are legal:

orientation= Direction.WEST;
direction= Orientation .HORIZONTAL;
orientation= 6;
direction= -1;

In this approach, variables and values of different enumeration types are interchange-
able, and the values may not even be meaningful. Second, the values of enumeration
types are numbers. When directly printing these values, they are cryptic. For example,

direction= Direction . EAST;


System.out.println("The current direction is: "+ direction);

This prints out the following message, which is cryptic and unreadable:

The current direction is: 1

To make the printout more readable, the printing of values must be handled as follows :

public interface Orientation {


public static final int HORIZONTAL= O;
public static final int VERTICAL = 1;
public static String name(int v) {
if (v == HORIZONTAL)
return "Horizontal";
else if (v == VERTICAL)
return 11 Vertical";
else
return null;
}
}

II . . .
direction= Direction . EAST;
System . out.println("The current direction is : 11 +
Orientation.name(direction)) ;
468 • More Design Patterns

10.1.3 Unordered Type-Safe Enumeration Idiom

In Java there is a better approach to implementing enumeration types using the type-
safe e11 ;,meration idiom, as illustrated in the following definition of the enumeration
type Orientation:
Enumeration~ aaze. Orientation
package maze;
public class Orientation {
public static final Orientation VERTICAL=
new Orientation ("Vertical");
public static final Orientation HORIZONTAL=
new Orientation ("Horizontal") ;
public String toString() {
return name;
}

private Orientation(String name) {


this.name= name;
}

private final String name;


}

Using this idiom, each enumeration type is defined as a class. Each value of the
enumeration type is associated with a descriptive name rather than an integer value.
Each value of the enumeration type is defined as a constant that references an instance
of the class. The toStringO method returns the descriptive name of each value.
The constructor of the class is private. It prevents any other instances of the class
from being created outside the class, since an enumeration type consists of only a
fixed finite number of distinct values. In other words, the instances referenced by the
constants defined in this class are the only instances of this class in existence. This
idiom ensures that
• each enumeration type may only have a fixed number of distinct values.
• the enumeration types are type-safe; that is, variables and values of different

'
enumeration types are not interchangeable.
• the values can be printed with descriptive names.
For example,
Orientation orientation= Orientation . VERTICAL;
System.out.println("The current orientation is: " + orientation);

Thi prints out the following message:


The current orientation is : Vertical

Furthermore, since the enumeration type has only a fixed number of distinct
values, equality of two values (in lances) i the ame as the identity of the two value
10.1 Type-Safe Enumeration Types • 469

(instances). So, identity comparison, that is, equality of references, can be used for
comparing the equality of values:
Orientation orientationl =
Orientation orientation2 =
if (orientationl == orientation2) {
// both have the same orientation
}

Comparison of the identities is usually more efficient than comparison of the


contents.

10.1.4 Ordered Type-Safe Enumeration Idiom

An extension of the type-safe enumeration idiom deals with an ordered enumeration


type; that is, there is an order among the values of an enumeration type. The following
is a definition of the ordered enumeration type Direction using the extended idiom:

Orciered·enumeration·tll)! maze .Direction


package maze;
public class Direction implements Comparable {
public static final Direction NORTH = new Direction("North");
public static final Direction EAST = new Direction ("East");
public static final Direction SOUTH = new Direction ( "South") ;
public static final Direction WEST = new Direction("West");
public String toStringO {
return name;
}

public int get0rdinal() {


return ordinal;
}

public int compareTo(Object o) {


if (o instanceof Direction) {
return ordinal - ((Direction) o).get0rdinal();
}
return 0;
}

public static Direction first() {


return values[0];
}

public Direction next() {


if (ordinal< values.length - 1) {
return values[ordinal + 1);
} else {
return null;
}
}

4 70 • More Design Patterns

publi c Direction opposite () {


return values[(ordinal + 2) %4) i
}

private Direction(String name) {


this .name= name;
}

private static int nextDrdinal = O;


private final String name;
private final int ordinal= nextDrdinal++;
private static final Direction[] values=
{NORTH, EAST, SOUTH , WEST};
}

In this ordered enumeration type, each value is also associated with an ordinal
number, which is automatically assigned when the instances are created. This class
implements the Comparable interface and defines the compareTo () method to
compare with other instances of this class based on the ordinal numbers.
This class also provides a number of methods that are specific to handling the
directions: the opposite () method returns the opposite direction, and the first ()
and next() methods support iteration through the directions, as illustrated here:

for (Di rection dir = Direction .first (); dir != null; dir = dir .next ()) {
// process each direction
}

10.2 CREATIONAL DESIGN PATTERNS

10.2.1 A Simple Design of the Maze Game

In this section, we present a simple design of the maze game that supports only a
default style of the maze game. The design is shown in Figure I 0.2. Later, we present
several improved designs using various design patterns to support different styles of
the maze game.

Figure 10.2
rooms 1
Maze
The design of the I
I
simple maze game. 1 .. 2 : create
I

Wall Room * _____ _ create _j SimpleMazeGame I


: create :
---- ------------------------------- ~ I

, create :
----------------------- ----- ---------~
10.2 Creational Design Patterns ■ 4 71

The Map Sites


The interface MapSi te defines the common behavior of the map sites, that is, the fixed
objects that comprise the maze board: rooms, walls, and doors. The draw () method
draws the visual appearance of each map site, and the enter O method defines the
reaction of each map site when the player attempts to enter the map site. The specific
behaviors are defined in the classes implementing the MapSi te interface, that is,
c1asses representing specific types of map sites: Room, Door, and Wall. 1

package maze;
import java . awt . • ;
public interface MapSite extends Cloneable {
public Object clone O throws CloneNotSupportedException;
public void enter(Maze maze);
public void draw(Graphics g, int x, int y, int w, int h);
}

The Room class represents rooms on the maze board. Each room has a room
number. Each room also has four sides, which must be either walls or doors. When a
player enters a room, a sound will be played.

Class maze. Room


package maze;
import java.awt.•;
import java . applet.AudioClip;
public class Room implements MapSite {
public static final Color ROOM_COLOR = new Color(152, 251, 152);
public static final Color PLAYER_COLOR = Color.red;
public Room(int roomNumber) {
this . roomNumber = roomNumber;
}

public Object clone() throws CloneNotSupportedException {


Room room= (Room) super.clone() ;
room.sides= new MapSite[4] ;
for (inti = O; i < 4; i++) {
if (s i des [i] ! = null) {
room . sides[i] = (MapSite) s i des[i] .clone() ;
}
}
return room ;
}

l. Some of Lhe details of drawing 1he maze board are not essen1ial in illustrating Lhe de ign pattern and
are thus omitted in the ource code Ii tings that follow. The complete source code is available in the
supplement package.
472 • More Design Patterns

public MapSite getSide(Direction dir) {


if (dir != null) {
return sides[dir.getOrdinal()];
}
return null;
}

public void setSide(Direction dir, MapSite site) {


if (dir != null) {
sides[dir.getOrdinal( ) ] = site;
if (site instanceof Door) {
Door door= (Door) site;
if (dir == Direction.NORTH I I
dir == Direction.SOUTH) {
door . setOrientation(Orientation . HORIZONTAL);
} else {
door .setOrientation(Orientation . VERTICAL);
}
}
}
}

public void setRoomNumber(int roomNumber) {


this.roomNumber = roomNumber;
}

public int getRoomNumber() {


return roomNumber;
}

public Point getLocation() {


return location;
}

public void setLocation(Point location) {


this.location= location;
}

public void enter(Maze maze) {


maze . setCurrentRoom(this);
gong.play();
}

public boolean isinRoom() {


return inroom;
}

public void setinRoom(boolean inroom) {


this . inroom = inroom;
}

public void draw(Graphics g, int x, int y, int w, i nt h) {


g . setColor(ROOM_COLOR);
g . fillRect(x, y, w, h);
10.2 Creational Design Patterns • 4 73

if ( i nroom) {
g . setColor(PLAYER_COLOR) ;
g. f ill□val(x + w / 2 - 5, y + h / 2 - 5, 10, 10);
}
}

protected int roomNumber = O;


protected boolean inroom = false ; // whether the play is in this room
protected MapSite[] sides= new MapSite[4];
protected Poi nt location= null;
protected stati c AudioClip gong=
uti1 . AudioUtility .getAudioClip ( "audi o/gong . au 11 ) ;

The Door class represents doors on the maze board. Each door connects two
rooms, one on each side. When the player tries to enter a door that is open, the player
will enter the room on the other side of the door. When a player tries to enter a door
that is closed, the player will remain in the current room and a smashing sound will
be played.

Class maze. Door


package maze;
import java.awt.* ;
import java . applet . AudioClip;
public class Door implements MapSite {
publi c Door (Room rooml, Room room2 ) {
this.room!= rooml;
this . room2 = room2;
}

public Object clone( ) throws CloneNotSupportedException {


return super.clone( );
}

public boolean isOpen() {


return open;
}

public void setOpen(boolean open) {


this.open= open;
}

public void setRooms(Room rooml , Room room2) {


this . room!= r ooml;
this . room2 = room2;
}

publi c Ori entati on getOrient at i on () {


return orientat ion;
}
·- I
474 • More Design Patterns

public void setOrientation(Orientation orientation) {


this.orientation= orientation;
}

I
public Room otherSideFrom(Room room) {
if (room!= null) {
if (room== rooml) {
return room2;
} else if (room== room2) {
return rooml;
}
}
return null;
}
public void enter(Maze maze) {
if (open) {
Room otherRoom = otherSideFrom(maze.getCurrentRoom());
if (otherRoom != null) {
otherRoom.enter(maze);
}
} else {
ding .play();
}
}
public void draw(Graphics g, int x, int y, int w, int h) {
g.setColor(Wall.WALL_COLOR);
g.fillRect(x, y, w, h);
if (orientation== Orientation.VERTICAL) {
y += 2 * w; h -= 4 * w;
} else {
x += 2 * h; w -= 4 * h;
}
if (open) {
g.setColor(Room.ROOM_COLDR);
g.fillRect(x, y, w, h);
} else {
g.setColor(Color.red);
g.fillRect(x, y, w, h);
g.setColor(Color .black);
g.drawRect(x, y, w, h);
}
}
protected Room rooml ;
protected Room room2;
protected boolean open;
protected Orientation orientation ;
protected static AudioClip ding=
util .AudioUtility .getAudioClip (" audio/ding . au");

-
10.2 Cn~atlonal Design Patterns • 4 75

The Wall class represents walls on the maze board. A wall could be between two
rooms or on the edge of the maze board. Walls are impassable. So when a player tries
to enter a waJl, the player will remain in the current room and a smashing sound will
be played.

package maze;

import java . awt.•;


import java.applet . AudioClip;
public class Wall implements MapSite {
public static final Color WALL_COLOR = Color.orange;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}

public void enter(Maze maze) {


hurts. play() ;
}

public void draw(Graphics g, int x, int y, int w, int h) {


g . setColor(WALL_COLOR) ;
g . fillRect(x, y, w, h);
}

protected static AudioClip hurts=


util.AudioUtility.getAudioClip("audio/that.hurts.au");
}

The Maze Board


The Maze class represents the maze board. The maze board consists of a list of
rooms, represented by the field rooms. The current room, which is represented by
the field curRoom, is the room where the player is. The Maze class has two major
responsibilities: (1) to draw the maze board, and (2) to control the movement of the
player. We first use this maze game to illustrate various creational design patterns,
so we focus only on the first responsibility here. The code concerning the second
responsibility is omitted here and will be discussed in Section 10.3. l to illustrate a
behavioral design pattern Command.

package maze;
import java .util.•;
import java.awt .•;
import java . awt .event . • ;
import javax .swi ng . •;
476 • More Design Patterns

public class Maze implements Cloneable {


public Object clone() throws CloneNotSupportedException {
return super.clone();
}

public void addRoom(Room room) {


if (room!= null) {
rooms.add (room);
}
}

public Room findRoom(int roomNumber) {


for (inti= O; i < rooms .size(); i++) {
Room room= (Room) rooms.get(i);
if (roomNumber == room.getRoomNumber()) {
return room;
}
}
return null;
}

public void setCurrentRoom(int roomNumber) {


Room room= findRoom(roomNumber);

}
setCurrentRoom(room);

public void setCurrentRoom(Room room) {


if (room!= curRoom) {
'
if (curRoom != null) {
curRoom .setlnRoom(false);
}
if (room!= null) {
room.setlnRoom(true);
curRoom = room;
}
if (view != null) {

}
view. repaint() ;
I
}
}

public Room getCurrentRoom() {


return curRoom;
}

public void draw(Graphics g) {


(draw the maze board on the view component}
}

protected List rooms = new ArrayList (); // the list of rooms that comprise
// the board
protected Room curRoom = null; // the room in which the player is
10.2 Cn~ational Design Patterns • 4 77

protect ed Component view; // the GUI component representing the board


(Methods, fields, and nested classes for handling the movement of the player.
See Section 10.3. 1 [p. 507])

The SimpleMazeGame class is the main driver of the maze program. It is respon-
sible for creating the layout of maze games. The createMaze () method creates a
small maze board with two rooms, and the createLargeMaze O method creates a
large maze board with nine rooms. Note that the code to create a large maze game is
lengthy, ad hoc, and hard to change.

Cl~ maze. SimpleMazeGame


package maze ;
import java . awt.•;
import javax.swing.*;
public class SimpleMazeGame {

• Creates a 1 x 2 small maze board with 2 rooms.
·1
public static Maze createMaze() {
Maze maze= new Maze() ;
Room rooml = new Room(l) ;
Room room2 = new Room(2);
Door door= new Door(rooml, room2);
rooml . setSide(Direction.NORTH, new Wall());
rooml.setSide(Direction.EAST, door);
rooml.setSide(Direction.SOUTH, new Wall()) ;
rooml .setSide(Direction.WEST, new Wall ());
room2.setSide(Direction . NORTH, new Wall ()) ;
room2 . setSide(Direction . EAST , new Wall()) ;
room2.setSide(Direction . SOUTH, new Wall( ) ) ;
room2 . setSide(Direction.WEST, door);
maze . addRoom(room1);
maze.addRoom(room2);
return maze ;
}


• Creates a 3 x 3 large maze board with 9 rooms.
·1
public static Maze createLargeMaze () {
Maze maze= new Maze( ) ;
Room rooml = new Room(1 );
Room room2 = new Room(2) ;
Room room3 = new Room (3);
4 78 • More Design Patterns

Room room4 = new Room(4);


Room rooms = Room (S);
new
Room room6 = Room (6);
new
Room room7 = Room(7);
new
Room room8 = Room(B);
new
Room room9 = Room(9);
new
Door doorl = Door(rooml,
new room2);
Door door2 = Door(room2,
new room3);
Door door3 = Door(room4 ,
new room5);
Door door4 = Door(room5,
new room6);
Door door5 = new Door(room5, roomB);
Door door6 = new Door(room6, room9);
Door door7 = new Door(room7, room8);
Door door8 = new Door(rooml, room4) ;
doorl .setOpen(true);
door2.set0pen(false);
door3.set0pen(true);
door4 .set0pen(true);
door5 .set0pen(false);
door6 . set0pen(true);
door7 .set0pen(true);
door8 . set0pen(true);
rooml.setSide(Direction . NORTH, door8);
rooml .setSide(Direction .EAST , new Wall());
room1 .setSide(Direction . SOUTH, new Wall());
rooml .setSide(Direction.WEST, doorl);
room2 .setSide(Direction . NORTH, new Wall());
room2.setSide(Direction .EAST, door1);
room2 . setSide(Direction . SOUTH, new Wall());
room2 .setSide(Direction.WEST, door2);
room3.setSide(Direction.NORTH, new Wall());
room3 .setSide(Direction.EAST, door2) ;
room3.setSide(Direction.SOUTH, new Wall());
room3.setSide(Direction.WEST, new Wall()) ;
room4 . setSide(Direction.NORTH, new Wall());
room4.setSide(Direction.EAST, new Wall());
room4 . setSide(Direction.SOUTH, door8);
room4 .setSide(Direction.WEST, door3);
room5 . setSide (Direction.NORTH, door5);
room5 . setSide(Direction.EAST, door3);
room5 . setSide(Direction.SOUTH, new Wall());
room5 . setSide(Direction.WEST, door4);
r oom6 . setSide (Direction .NORTH, door6);
room6 .setSide (Direction.EAST, door4);
room6 . setSide (Direction.SOUTH, new Wall () );
room6 . setSide(Direction .WEST , new Wall ( ) );
10.2 Creational Design Patterns • 4 79

room7.setSide(Direction . NORTH, new Wall());


room7.setSide(Direction.EAST, new Wall());
room7 . setSide(Direction.SOUTH, new Wall());
room7.setSide(Direction .WEST, door7);
room8.setSide(Direction.NORTH, new Wall());
room8.setSide(Direction.EAST, door7);
room8.setSide(Direction . SDUTH, door5);
room8.setSide(Direction.WEST, new Wall());
room9.setSide(Direction.NORTH, new Wall());
room9 . setSide(Direction . EAST, new Wall());
room9.setSide(Direction.SOUTH, door6);
room9.setSide(Direction.WEST, new Wall());
maze.addRoom(rooml);
maze.addRoom(room2);
maze.addRoom(room3);
maze.addRoom(room4);
maze . addRoom(room5);
maze.addRoom(room6);
maze . addRoom(room7);
maze.addRoom(room8);
maze.addRoom(room9);
return maze;
}

public static void main(String[] args) {


Maze maze;
// choose the size of the maze board
if (args.length > 0 &&
"Large".equals(args[O])) {
maze= createLargeMaze();
} else {
maze= createMaze();
}
maze.setCurrentRoom(l);
maze. showFrame ("Maze") ; // build and create frame
}

The SimpleMazeGame class can be invoked with an optional argument Large.


The main () method creates a small or large maze board depending on the command-
line argument. If the command argument Large is present, the large maze board will
be created; otherwise, the small maze board will be created (see Figure 10.1 ).

The Themes
To make the maze game more interesting, we want to support different looks or
themes of the game. In different themes, the rooms, walls, and doors could be drawn
in different shapes or colors, and the reactions to entering rooms, walls, and door
480 ■ More Design Patterns

Figure 10.3 maze

Two themes of the


maze.harry maze.snow
maze game.

HarryPotterDoor Door SnowWhiteDoor

HarryPotterWall Wall SnowWhiteWall

HarryPotterRoom .---.---, __
..__Room _,,:;.~-;---, SnowWhiteRoom

could also have different sound or animation effects. Each theme consists of a set of
rooms, walls, and doors. For the purpose of illustration, we build two new themes of
the maze game in addition to the default theme: a Harry Potter theme and a Snow
White theme. For each theme, a set of subclasses of room, wall, and door are defined.
The subclasses of different themes are placed in separate packages; see Figure 10.3.
The classes for the Harry Potter theme are in a subpackage named maze. harry.
The rooms, doors, and walls in the Harry Potter theme are represented by classes
HarryPotterRoom, HarryPotterDoor, and HarryPotterWall, respectively.

Clatsaaze.h!!!I.HarryPotterRoom
package maze .harry;
import java.awt.•;
import java . applet . AudioClip;
import maze .•;
class HarryPotterRoom extends Room {
public static final Color ROOM_COLOR = new Color(85, 107, 47);
public static final Color PLAYER_COLOR = Color . black;
public HarryPotterRoom(int roomNumber) {
super(roomNumber);
}

public void enter(Maze maze) {


maze.setCurrentRoom(this);
adapt . play() ;
}

public void draw (Graphics g, int x, int y, int w, int h) {


g . setColor(ROOM_COLOR);
g.fillRect(x, y, w, h);
if (inroom) {
g.setColor(PLAYER_COLOR);

. 7
10.2 Creational Design Patterns • 481

g . fill □val(x + w / 2 - 5, y + h / 2 - 5, 10 , 10) ;


}
}

protected static AudioClip adapt=


util.AudioUtility.getAudioClip("audio/adapt-or-die . au");
}

Class maze . harry. Harr1-PotterDoor


package maze.harry;
import java.awt . *;
import maze . *;
class HarryPotterDoor extends Door {
public HarryPotterDoor(Room rooml, Room room2) {
super(rooml, room2);
}

public void draw(Graphics g, int x, int y, int w, int h) {


g.setColor(HarryPotterWall.WALL_COLOR) ;
g.fillRect(x , y, w, h);
if (orientation== Orientation.VERTICAL) {
y += 2 * w; h -= 4 * w;
} else {
x += 2 * h; w -= 4 * h;
}
if (open) {
g.setColor(HarryPotterRoom . ROOM_COLOR) ;
g.fillRect(x, y, w, h);
} else {
g . setColor(new Color(139, 69, 0));
g.fillRect(x, y, w, h);
g.setColor(Color .black);
g.drawRect(x, y, w, h);
}
}

Class mu;e. harry. Har~PotterWall


package maze . harry;
import java.awt . •;
import maze . •;
class HarryPotterWall extends Wall {
public static final Color WALL_COLOR = new Color (178, 34 , 34) ;
public void draw(Graphics g, int x, int y , int w, int h) {
g.setColor(WALL_COLOR) ;
g . fillRect(x, y, w, h);
}

}
482 • More Design Patterns

The classes for the Snow White theme are in a subpackage named maze. snow.
The rooms, doors, and walls in the Snow White theme are represented by classes
SnowWhiteRoom, SnowWhi teDoor, and SnowWhi teWall, respectively.

Class maze. ■now. SnowWhiteRoom


package maze.snow;
import java.awt.•;
import java.applet . AudioClip ;
import maze.•;
class SnowWhiteRoom extends Room {
public static final Color R00M_C0L0R = new Color(255, 218, 185);
public static final Color PLAYER_C0L0R = Color.white;
public SnowWhiteRoom(int roomNurnber) {
super(roomNumber);
}

public void enter(Maze maze) {


maze.setCurrentRoom(this);
tiptoe . play();
}

public void draw(Graphics g, int x, int y, int w, int h) {


g.setColor(R00M_C0L0R);
g.fillRect(x, y, w, h);
if (inroom) {
g .setColor(PLAYER_C0L0R);
g.fill0val(x + w / 2 - 5, y + h / 2 - 5, 10, 10);
}
}

protected static AudioClip tiptoe=


util.AudioUtility .getAudioClip("audio/tiptoe. thru . the . tulips . au");
}

Clals111.Ze.nov.SnovWhiteDoor
package maze.snow;
import java.awt .•;
import maze.•;
class SnowWhiteDoor extends Door {
public SnowWhiteDoor(Room rooml I Room room2) {
super(rooml, room2);
}

public void draw(Graphics g int x ·


' • int Y, int w, int h) {
g.setColor(SnowWhiteWall.WALL_COLOR);
g.fillRect(x, y, w, h);
10.2 Cn~ational Design Patterns • 483

if (orientation== Orientation .VERTICAL) {


y += 2 • w; h -= 4 • w;
} else {
x += 2 • h ; w -= 4 • h;
}
if (open) {
g . setColor(SnowWhiteRoom . ROOM_COLOR);
g.fillRect(x, y, w, h);
} else {
g . setColor(Color . orange);
g.fillRect (x, y, w, h);
g . setColor (Color.black);
g . drawRect(x, y, w, h) ;
}
}

Cius maze . snow. SnowWhiteWall


package maze.snow;
import java . awt . •;
import maze.•;
class SnowWhiteWall extends Wall {
public static final Color WALL_COLOR = new Color (255, 20, 147);
public void draw (Graphics g, int x, int y, int w, int h) {
g.setColor (WALL_COLOR);
g . fillRect (x , y, w, h);
}

Now we have defined the classes in the new themes. The question is how to
make the themes easily interchangeable without affecting other aspects of the maze
game, and to make it easy to add additional themes. Switching between themes means
replacing one set of rooms, walls, and doors from one theme with another set of
rooms, walls, and doors from another theme. A straightforward solution is to create
the maze boards of different themes in a way similar to the createMaze () and
createLargeMaze () in SimpleMazeGame [p. 477). Using the small maze board
as an example, the maze boards in the two new themes can be created as follows:

public static Maze createHarryPotterMaze() {


Maze maze= new Maze ();
Room room!= new HarryPotterRoom (l);
Room room2 = new HarryPotterRoom (2);
Door door = new HarryPotterDoor (rooml, room2);
rooml.setSide (Direction.NORTH, new HarryPotterWall ());
rooml .setSide (Direction.EAST, door) ;
484 ■ More Design Patterns

room1.setSide(Direction.SOUTH, new HarryPotterWall());


room1.setSide(Direction.WEST, new HarryPotterWall());
room2 . setSide(Direction.NORTH, new HarryPotterWall());
room2.setSide(Direction.EAST, new HarryPotterWall());
room2.setSide(Direction.SOUTH, new HarryPotterWall());
room2 . setSide(Direction . WEST, door);
maze.addRoom(room1);
maze.addRoom(room2);
return maze;
}

public static Maze createSnowWhiteMaze() {


Maze maze= new Maze();
Room room1 = new SnowWhiteRoom(l);
Room room2 = new SnowWhiteRoom(2);
Door door= new SnowWhiteDoor(rooml, room2);
rooml.setSide(Direction.NORTH, new SnowWhiteWall());
rooml.setSide(Direction.EAST, door);
room1.setSide(Direction . SOUTH, new SnowWhiteWall());
rooml.setSide(Direction.WEST, new SnowWhiteWall());
room2.setSide(Direction.NORTH, new SnowWhiteWall());
room2 . setSide(Direction.EAST, new SnowWhiteWall());
room2.setSide(Direction.SOUTH, new SnowWhiteWall()) ;
room2.setSide(Direction.WEST, door);
maze.addRoom(rooml);
maze . addRoom(room2);
return maze;
}

This is obviously an undesirable solution, since it involves duplication of largely


identical code segments. The duplicated code segments perform the identical task
with merely different parts. If we were to create the large maze board and to handle
more themes, the code duplication would be much more substantial. This is a serious
maintenance hazard, since it is tedious and error-prone to modify the code and to
ensure that the same changes are made to all the duplicated code. There are a number
of better solutions to create the objects in different themes and to switch easily between
themes using various creational design patterns, including Abstract Factory, Factory
Method, Prototype, and Builder.

10.2.2 Design Pattern: Abstract Factory

Abstract Factory is a creational design pattern for creating a set of related and compati-
ble products from several interchangeable product frunilies . The product instances are
not created using the new operator. They are created using a set of methods defined in
a factory class, which represents a product family. The different factories representing
10.2 Cn~ational Design Patterns • 485

different product families share a common interface-the abstract factory interface.


Therefore, a client can easily switch from one factory, or product family, to another.

Design Pattern Abstract Factory

Categ01y: Creational design pattern.


Intent: To provide an interface for creating a family of related or dependent
objects without specifying their concrete classes.
Also Known As: Kit.
Applicability: Use the Abstract Factory design pattern
• when a system should be independent of how its components or products
are created.
• when a system should be configurable with one of multiple interchange-
able families of products.
• when a family of related products should not be mixed with similar
products from different families.
• when only the interfaces of the products are exposed, while the
implementation of the products is not revealed.

The structure of the Abstract Factory design pattern is shown in the following
diagram:

AbstractFactor Client

makeProductA ()
makeProductB()
AbstractProduct

ConcreteFactory1 ConcreteFactory2 ConcreteProductA1 ConcreteProductA2


1----------1
.
makeProductA() makeProductA()
makeProductB() makeProductB()
AbstractProductB

: ... -. ---. ---•· --· -· · · · ·: · · · · · · · · · · ··· · · ConcreteProductB1 ConcreteProductB2


................................... . ..... . ........ . ....... . .. .. ' ........ ..... . .
~
' '
486 ■ More Design Patterns

The participants of the Abstract Factory design pattern are the following:


AbstractFactory (e.g., MazeFactory), which defines methods that create ab-
stract products (e.g. makeRoom () , make Wall O , makeDoor O) and may pro-
vide default implementation.

ConcreteFacto,y (e.g., HarryPotterMazeFactory, SnowWhiteMazeFac-
tory), which implements the methods to create c~ncrete instances of Con-
creteProduct. Each ConcreteFactory creates products m the same product family.
■ AbstractProduct (e.g., Room, Wall, Door), which defines an interface for a type
of product and may provide default implementation.
■ ConcreteProduct (e.g., HarryPotterRoom, HarryPotterWall, HarryPot-
terDoor, SnowWhi teRoom, SnowWhi teWall, SnowWhi teDoor), which de-
fines a product to be created by the corresponding concrete factory and imple-
ments the AbstractProduct interface.
■ Client (e.g., Maze), which uses only AbstractFactory and AbstractProduct.
In the SimpleMazeGame class [p. 477] , map site objects are created using the
new operator. So it is inflexible and inextensible. Using the Abstract Factory design
pattern, map site objects in different themes can be created using interchangeable
factories for the themes. The design of the maze game using the Abstract Factory
design pattern is shown in Figure 10.4.
The abstract products in this design of the maze game are the map site classes
Room, Wall, and Door. More precisely, these are abstract products with default im-
plementations. The concrete products are the map site classes defined in the different
themes, that is, HarryPotterRoom, HarryPotterWall, and HarryPotterDoor in
the Harry Potter theme, and SnowWhi teRoom, SnowWhi teWall, and SnowWhi te-
Door in the Snow White theme. Each theme represents a product family. The Maze-
Factory class defines the abstract factory. It also provides a default implementation
that returns the map site objects in the default theme.

Abstract facto maze. MazeFacto~


package maze;
public class MazeFactory {
public Maze makeMaze () {
return new Maze();
}

public Wall makeWall() {


return new Wall()·
} '

public Room makeRoom(int roomNumber) {


return new Room(roomNumber) ·
} '

public Door makeDoor(Room rooml, Room room2) {


return new Door (rooml, room2 );
}

.....
10.2 Creatlonal Design Patterns • 487

Figure 10.4
sides
A design of the
maze game using
the Abstract Fac-
tory pattern.

HarryPotterDoor HarryPotterRoom rooms


HarryPotterWall

SnowWhiteDoor SnowWhiteWall SnowWhiteRoom

I I
I.
Maze Factory ~· ~ · - · - · - · - · - · i . _ __ __ _
create create
Maze makeMaze() ---- - -- - _ 1

Wall makeWall()
Room makeRoom()
Door makeDoor()
fl,.
Create :
I
'r I
: create
'
I
HarryPotterMaze Factory SnowWhiteMazeFactory
I

I
Maze makeMaze() Maze makeMaze()
I
Wall makeWall() Wall makeWall()
I
Room makeRoom() Room makeRoom()
I
Door makeDoor() Door makeDoor()
I
A 7:-.
~ use ~ use I use
MazeGameAbstractFactory
1 - - - - - - - - - -- - - - - - 1 build
Maze createMaze(MazeFactory factory) 1 - - - - - - - - - '

The HarryPotterMazeFactory and SnowWhi teMazeFactory are the two


concrete factories, which return the map site objects in the two respective theme .

Concrete factory maze . Bar!J.PotterMazeFactory


package maze;
public class HarryPotterMazeFactory extends MazeFactory {

public Wall makeWall() {


return new HarryPotterWall();
}

public Room makeRoom(int roomNumber) {


return new HarryPotterRoom(roomNumber);
}
488 • More Design Patterns

public Door makeDoor(Room room1, Room room2) {


return new HarryPotterDoor(room1, room2);
}

Concrete facto!')' maze. SnowWhiteHazeFactory


package maze;
public class SnowWhiteMazeFactory extends MazeFactory {

public Wall makeWall() {


return new SnowWhiteWall();
}

public Room makeRoom(int roomNumber) {


return new SnowWhiteRoom(roomNumber);
}

public Door makeDoor(Room room1, Room room2) {


return new SnowWhiteDoor(room1, room2);
}

The MazeGameAbstractFactory class is the main class of this implementation


of the maze game using the Abstract Factory pattern, and it is also the client role in the
Abstract Factory pattern. The createMaze () method creates a 3 x 3 maze board. It
takes a parameter of MazeFactory object-the abstract factory. The map site objects
are created using the abstract factory interface. This method can be invoked with one
of the several different concrete factories. Switching between different themes means
using different concrete factories, which causes no code duplication. It is also easy
to introduce new themes, a task that involves defining a new set of concrete products
and a new concrete factory. -

Mue e(cllent maze.HazeGameAbstractFactorJ


package maze;
public class MazeGameAbstractFactory {
// build a 3 x 3 maze board using abstract factory
public static Maze createMaze(MazeFactory factory) {
Maze maze= factory . makeMaze();
Room room1 = factory.makeRoom(1);
Room room2 = factory . makeRoom(2);
Room room3 = factory . makeRoom(3);
Room room4 = factory.makeRoom(4);
Room room5 = factory.makeRoom(5);
Room room6 = factory.makeRoom(G);
Room room7 = factory.makeRoom(7);
Room room8 = factory .makeRoom(S);
Room room9 = factory.makeRoom(g);
Door door1 = factory . makeDoor(room1, room2);
Door door2 = factory . makeDoor(room2, room3);
10.2 Cn~ational Design Patterns ■ 489

Door door3 = factory.makeDoor(room4, room5);


Door door4 = factory.makeDoor(room5, room6);
Door door5 = factory.makeDoor(room5, room8);
Door door6 = factory.makeDoor(room6, room9);
Door door7 = factory.makeDoor(room7, room8);
Door door8 = factory.makeDoor(rooml, room4);
doorl.setOpen(true);
door2.set0pen(false);
door3 . set0pen(true);
door4 . set0pen(true);
door5.set0pen(false);
door6 . set0pen(true);
door7.set0pen(true);
door8.set0pen(true);
rooml.setSide(Direction.NORTH, door8);
rooml . setSide(Direction.EAST, factory.makeWall( ));
rooml.setSide(Direction.SOUTH, factory .makeWall () );
rooml . setSide(Direction.WEST, doorl);
room2.setSide(Direction . NORTH, factory.makeWall( ) );
room2 . setSide(Direction . EAST, doorl);
room2 . setSide (Direction. SOUTH, factory .makeWa11 ()) ;
room2.setSide(Direction.WEST, door2);

room3.setSide(Direction.NORTH, factory.makeWall());
room3 . setSide(Direction.EAST, door2);
room3.setSide(Direction . SOUTH, factory.makeWall());
room3.setSide(Direction .WEST , factory .makeWall() ) ;

room4.setSide(Direction.NORTH, factory .makeWall( )) ;


room4 . setSide(Direction.EAST, factory.makeWall());
room4.setSide(Direction . SOUTH, door8);
room4.setSide(Direction.WEST, door3);

room5.setSide(Direction.NORTH, door5) ;
room5.setSide(Direction.EAST, door3);
room5.setSide(Direction.SOUTH, factory .makeWall ());
room5.setSide(Direction.WEST, door4);

room6.setSide(Direction.NORTH, door6) ;
room6.setSide(Direction.EAST, door4) ;
room6.setSide(Direction . SOUTH, factory.makeWall());
room6.setSide(Direction .WEST, factory .makeWall() ) ;

room7.setSide(Direction.NORTH, factory . makeWall() ) ;


room7.setSide(Direction.EAST, factory.makeWall());
room7 . setSide(Direction.SOUTH, factory.makeWall());
room7.setSide(Direction . WEST, door7);

room8 . setSide(Direction .NORTH, factory.makeWall());


room8.setSide(Direction . EAST, door7);
room8.setSide(Direction . SOUTH, door5);
room8. setSide (Direction. WEST, factory .makeWall O);
--
490 ■ More Design Patterns

room9.setSide(Direction.NORTH, factory .makeWall ());


room9 .setSide(Direction . EAST, factory.makeWall());
room9.setSide(Direction.SOUTH, door6);
room9 . setSide(Direction . WEST, factory.makeWall());
maze.addRoom(room1);
maze.addRoom(room2);
maze.addRoom(room3);
maze.addRoom(room4);
maze.addRoom(room5);
maze.addRoom(room6);
maze.addRoom(room7);
maze.addRoom(room8);
maze.addRoom(room9);
return maze;
}

public static void main(String[] args) {


Maze maze;
MazeFactory factory= null;
II choose a theme by creating an appropriate concrete factory
if (args.length > 0) {
if ("Harry".equals(args[O])) {
factory= new maze.harry . HarryPotterMazeFactory ();
} else if ("Snow". equals (args [OJ)) {
factory= new maze.snow.SnowWhiteMazeFactory();
}
}
if (factory== null) {
factory= new MazeFactory();
}
maze= createMaze(factory);
maze.setCurrentRoom(1);
maze.showFrame("Maze -- Abstract Factory");
}

The MazeGameAbstractFactory class can be invoked with an optional argu-


ment: Harry for the Harry Potter theme, or Snow for the Snow White theme. The
main O method first creates a concrete factory for the specified theme, or the default
theme if the command-line argument is absent. The maze board is then created by th e
createMaze () method using the appropriate concrete factory.
Another benefit of the Abstract Factory pattern is that it allows the implementa-
tion of the concrete products to be hidden from the client. Note that in this design the
classe representing concrete products are in separate packages, maze . harry ao d
~aze. snow, ~espectively, and these classes are nonpublic. Only the concrete factory
m the respecllve package needs to be public, since the client only accesses the fac-
tories directly. This is an important application of the encapsulation principle al Lhe
package level.

....._ -
1 O. 2 Cn~ational Design Patterns • 4 91

10.2.3 Design Pattern: Factory Method

An alternative solution, which is similar to the Abstract Factory pattern, is to use the
Factory Method pattern discussed in Section 9.5 .6 [p. 447]. The two solutions are
similar in the sense that both define methods (factory methods) for creating instances
of products, and different themes are represented by subclasses that override the
factory methods to create concrete products in respective themes. There are also
differences between the two patterns. The sole responsibility of the factories in the
Abstract Factory pattern is to create products. However, creating products is only one
of the responsibilities of the creators in the Factory Method pattern. The creators in the
Factory Method pattern are also responsible for building a structure using the products
created by the factory methods. For example, in the maze game, the Abstract Factory
pattern separates the responsibilities for creating the map sites, that is, the parts, and
building the maze board using the parts, and assigns them to two classes. The Factory
Method pattern, however, combines these two responsibilities into one class- the
MazeGameCreator class.
The design of the maze game using the Factory Method design pattern is shown
in Figure I 0.5. The MazeGameCreator class is the creator in the Factory Method

Figure 10.5
MapSite ~--------,sides
A design of the
maze game using 1..2
the Factory Method
- - - ·- ·- ·- ·- -- -- -
pattern. 1

rooms
HarryPotterDoor HarryPotterWall HarryPotterRoom

SnowWhiteDoor SnowWhiteWall ~ SnowWhiteRoom

............. ·:· ..i·....... ~-.... ............... --~-...... ·j· . ... -·-- ....... -~- ............... :
' '
~
I I . I

1
' • I I
, I ~ - - ------, , I
I i - MazeGameCreator ·'- · - · - -- -- · - · 7·-·-·-·-·

Maze createMaze()
Maze makeMaze() build
Wall makeWall()
Room makeRoom()
Door makeDoor()

create : create

HarryPotterMazeGameCreator SnowWhiteMazeGameCreator

Wall makeWall() Wall makeWall()


Room makeRoom() Room makeRoom()
Door makeDoor() Door makeDoor()
492 • More Design Patterns

pattern. The makeMaze () , make Wall () , makeRoom () , and makeDoor () methods


are the factory methods. The createMaze () method builds the maze board u sing
the parts created by the factory methods.

Class maze. NazeGame · eator,


package maze ;

import java . awt.• ;


import javax.swing.•;

public class MazeGameCreator {

public Maze createMaze() {


Maze maze = makeMaze () ;
Room rooml = makeRoom(l);
Room room2 = makeRoom(2);
Room room3 = makeRoom(3);
Room room4 = makeRoom(4);
Room rooms= makeRoom(5) ;
Room room6 = makeRoom(6);
Room room7 = makeRoom(7);
Room room8 = makeRoom(8);
Room room9 = makeRoom(9) ;
Door doorl = makeDoor(rooml, room2);
Door door2 = makeDoor(room2, room3);
Door door3 = makeDoor(room4, room5);
Door door4 = makeDoor(room5, room6) ;
Door door5 = makeDoor(room5, room8) ;
Door door6 = makeDoor(room6, room9);
Door door7 = makeDoor(room7, room8);
Door door8 = makeDoor(rooml, room4);

doorl.setOpen(true);
door2.set0pen(false);
door3 . set0pen(true);
door4 . set0pen(true);
door5 . set0pen(false);
door6 .set0pen(true);
door7.set0pen(true);
door8.set0pen(true);

rooml . setSide(Direction . NORTH, door8);


rooml .setSide(Direction . EAST, makeWall());
rooml.setSide(Direction.SOUTH, makeWall());
rooml.setSide(Direction.WEST, doorl);

room2.setSide(Direction . NORTH, makeWall());


room2 . setSide(Direction.EAST, doorl);
room2.setSide(Direction .SOUTH, makeWall()) ;
room2 . setSide(Direction .WEST, door2);

... ..
10.2 Creational Design Patterns ■ 493

room3.setSide (Direction. NORTH , makeWall ( ) );


room3.setSide(Direction.EAST , door2 );
room3.setSide (Direction . SOUTH , makeWall ());
room3 . setSide(Direction .WEST, makeWall( )) ;
room4 . setSide(Direction . NORTH, makeWall ());
room4.setSide(Direction.EAST, makeWall ());
room4.setSide(Direction.SOUTH, door8 ) ;
room4 . setSide(Direction .WEST, door3 );
room5.setSide(Direction .NORTH, door5 );
room5 . setSide(Direction . EAST, door3 );
room5.setSide(Direction.SOUTH, makeWall( )) ;
room5 . setSide(Direction .WEST, door4 );
room6 . setSide(Direction . NORTH , door6 ) ;
room6.setSide (Direction.EAST, door4) ;
room6.setSide(Direction.SOUTH , makeWall ());
room6 . setSide (Direction.WEST, makeWall ()) ;
room7.setSide(Direction.NORTH, makeWall() ) ;
room7.setSide (Direction.EAST, makeWall ());
room7.setSide (Direction .SOUTH, makeWall ());
room7 .setSide (Direction.WEST , door7 ) ;
room8.setSide (Direction .NORTH, makeWall ());
room8.setSide (Direction.EAST, door7) ;
room8.setSide(Direction . SOUTH, door5);
room8 . setSide(Direction.WEST, makeWall ()) ;
room9.setSide (Direction. NORTH, makeWall ()) ;
room9 . setSide(Direction.EAST, makeWall ());
room9.setSi de(Direction.SDUTH , door6);
room9 . setSide (Direction.WEST, makeWall ( ) );
maze . addRoom (room1);
maze.addRoom (room2);
maze.addRoom(room3) ;
maze.addRoom(room4) ;
maze.addRoom(room5) ;
maze.addRoom(room6);
maze . addRoom(room7) ;
maze.addRoom (room8) ;
maze . addRoom (room9);

return maze;
}

public Maze makeMaze O {


return new Maze ( );
}

public Wall makeWall () {


return new Wall ();
}
494 ■ More Design Patterns

public Room makeRoom ( int roomNumber) {


return new Room(roomNumber);
}

public Door makeDoor(Room rooml, Room room2) {


return new Door(rooml, room2);
}

public static void main(String[] args) {


Maze maze ;
MazeGameCreator creator= null;
II choose a theme by creating an appropriate concrete creator
if (args .length > 0) {
if ("Harry". equals (args [OJ)) {
creator= new HarryPotterMazeGameCreator( ) ;
} else if ("Snow".equals(args[O])) {
creator= new SnowWhiteMazeGameCreator();
}
}
if (creator== null) {
creator= new MazeGameCreator();
}
maze= creator . createMaze();
maze.setCurrentRoom(l);
maze.showFrame("Maze -- Factory Method");
}

The HarryPotterMazeGameCreator and SnowWhiteMazeGameCreator


classes are the concrete creators. They extend the creator class, MazeGameCreator,
and override the factory methods to create concrete map site objects in the respective
themes.

Class uze .~PotterHazaGuaCre•tor


package maze;
public class HarryPotterMazeGameCreator extends MazeGameCreator {
public Wall makeWall() {
return new HarryPotterWall();
}

public Room makeRoom(int roomNumber) {


return new HarryPotterRoom(roomNumber);
}

public Door makeDoor(Room room1, Room room2) {


return new HarryPotterDoor(rooml, room2);
}

}
10.2 Creational Design Patterns • 495

Class maze. SnovWhiteMazeGameCreator


package maze;
public class SnowWhiteMazeGameCreator extends MazeGameCreator {
public Wall makeWall() {
return new SnowWhiteWall();
}

public Room makeRoom(int roomNumber) {


return new SnowWhiteRoom(roomNumber);
}

public Door makeDoor(Room rooml, Room room2) {


return new SnowWhiteDoor(rooml, room2);
}

The MazeGameCreator class can be also invoked with an optional argument:


Harry for the Harry Potter theme, or Snow for the Snow White theme. The main ()
method first creates a concrete creator for the specified theme, or the default theme
if the command-line argument is absent. The maze board is then created by the
createMaze O method using the polymorphic factory methods provided by the
respective concrete creator.

10.2.4 Design Pattern: Prototype

One of the shortcomings of the Abstract Factory and Factory Method patterns is that
each product family is represented by a subclass. Therefore, if there are many product
families, there will also be many subclasses. Furthermore, if it is possible to mix
products from different product families, then the potential number of combinations
and the number of subclasses could explode. An alternative solution that would
prevent the explosion of the number of subclasses is to use the Prototype pattern.
Using the Prototype pattern, objects are created by cloning a set of prototypes. Objects
in different product families can be created by using a different set of prototypes.
Using the Prototype pattern, product families can be defined or altered at run time by
choosing the desired prototype. While using the Abstract Factory or Factory Method
patterns, product families are defined in the form of a class and thus cannot be altered
or configured at run time.

Design Pattern Prototype


Categ01y: Creational design pattern.
Intent: To specify the kinds of objects to create using a prototypical instance and
create new instances by cloning this prototype.
496 • More Design Patterns

Applicability: Use the Prototype design pattern


• when a system should be independent of how its components or products
are created.
• when the classes to instantiate are specified at run time.
• to avoid building a class hierarchy of factories that parallels the class
hierarchy of products.

The structure of the Prototype design pattern is shown in the following diagram:

\ Cloneable
ZSI
I
I
Client
I
I prototype
Prototype -- aMethod() 9

clone()

/ ~
p = prototype.clone()

Concrete Prototype 1 ConcretePrototype2

clone() clone()

The participants of the Prototype design pattern are the following:

■ Prototype (e.g., Room, Wall, Door), which defines interfaces of objects to be


created, implements the j ava. lang. Clone able interface and defines a public
clone() method.
■ ConcretePrototype (e.g., HarryPotterRoom, HarryPotterWall, HarryPot-
terDoor, SnowWhiteRoom, SnowWhiteWall, SnowWhi teDoor ), which im-
plements the Prototype interface and implements the clone O method.
■ Client (e.g., MazePrototypeFactory), which creates new instances by cloning
the prototype.

The design of the maze game using the Prototype design pattern is shown in Fig-
ure l 0.6. This design is also an extension of the design using the Abstract Factory
panern. The MazePrototypeFactory is a concrete factory in the Abstract Factory
pattern, and it creates the map site objects using the Prototype pattern. It contains a set
of prototypes of the concrete map sites to be created. The concrete map site objects
are created by cloning the corresponding prototypes. To allow the cloning of the pro-
totypes, the concrete product classes must implement the j ava . lang. Cloneable
interface and define a public clone O method (see Section 6.3.4 [p. 231 ]).
10 .2 creational Design Patterns • 497

Figure 10.6
C/oneable
A design of the
maze game using
MapSite
the Prototype pat- 4
tern. clone() -- sides
~

I 1..2
I 0 *
Door Wall Room -

clone() clone() clone()


ro oms
1 1 'r
IHarryPotterDoor I IHarryPotterWall l IHarryPotterRoom

ISnowWhiteDoor I ISnowWhiteWall I ISnowWhiteRoom I 1 <)

I Maze I f:..
MazeFactory

Maze makeMaze()
Wall makeWall()
Room makeRoom()
Door makeDoor()

Maze Prototype Factory MazeGameAbstractFactory


Maze mazePrototype '. build
use r - - -- - -- - -- - -- ------1 I
Wall wallPrototype Maze createMaze(MazeFactory factory)
Room roomPrototype
Door doorPrototype
Maze makeMaze()
Wall makeWall()
Room makeRoom()
Door makeDoor()

Clam maze. Maz.PrototypFact~


package maze;
import java . awt.* ;
import javax . swing . • ;
public class MazePrototypeFactory extends MazeFactory {
498 • More Design Patterns

public HazePrototypeFactory(Haze mazePrototype,


Wall wallPrototype,
Room roomPrototype,
Door doorPrototype) {
this .mazePrototype • mazePrototype;
this .wallPrototype • wallPrototype;
this.roomPrototype • roomPrototype;
this.doorPrototype • doorPrototype;
}

public Haze makeHaze () {


try {
return (Haze) mazePrototype.clone();
} catch (CloneNotSupportedException e) {
System. err. println( "CloneNotSupportedException: ti + e. getMessage ()) ;
}
return null;
}

public Wall makeWall () {


try {
return (Wall) wallPrototype.clone();
} catch (CloneNotSupportedException e) {
System . err .println( "CloneNotSupportedException: ti + e. getMessage ()) ;
}
return null;
}

public Room makeRoom(int roomNumber) {


try {
Room room • (Room) roomPrototype.clone();
room .setRoomNumber(roomNumber);
return room;
} catch (CloneNotSupportedException e) {
System. err . println ("CloneNotSupportedException: t1 + e . getMessage O);
}
return null ;
}

public Door makeDoor(Room room1, Room room2) {


try {
Door door• (Door) doorPrototype.clone();
door .setRooms(room1, room2);
return door;
} catch (CloneNotSupportedException e) {
System. err. println ("CloneNotSupportedException: 11 + e. get Message ()) ;
}
return null;
}

protected Maze mazePrototype ;


protected Wall wallProt otype ;
protected Room r oomPrototype ;
protected Door doorProt otype ;
10.2 Creational Design Patterns • 4 99

public static void main(StringO args) {


Maze maze;
MazePrototypeFactory factory• null;
MazeFactory prototypeFactory • null;
if (args.length > 0) {
if ("Harry". equals (args [OJ)) {
prototypeFactory • new maze.harry.HarryPotterMazeFactory();
} else if ("Snow". equals (args [OJ)) {
prototypeFactory • new maze.snow . SnowWhiteMazeFactory();
}
}
if (prototypeFactory •= null) {
prototypeFactory z new MazeFactory();
}
factory= new MazePrototypeFactory(prototypeFactory.makeHaze (),
prototypeFactory . ma.keWall(),
prototypeFactory.ma.keRoom (0),
prototypeFactory . ma.keDoor(null, null));
maze= MazeGameAbstractFactory.createMaze(factory);
maze . setCurrentRoom(l) ;
maze . showFrame ( "Maze -- Prototype");
}

The MazePrototypeFactory class can be also invoked with an optional ar-


gument: Harry for the Harry Potter theme, or Snow for the Snow White theme.
The main O method first creates a concrete factory, prototypeFactory, for the
specified theme, or the default theme if the command-line argument is absent. The
prototype factory is used to create prototypes. Then an instance ofMazePrototype-
Factory, the Prototype pattern-based factory, is created using the prototypes. The
Prototype pattern-based factory is used as a concrete factory in the createMaze ()
method of the MazeGameAbstractFactory class to build the maze board.

Configurable Universal Factory


One of the advantages of using factories i reconfigurability. In practice, the config-
uration settings can be pas ed to program in several different ways:

1. Command-line argument
2. Operating system environment variable
3. Java properties
In the previou example the configuration, or the theme, i et u ing command line
arguments. Most operating ystem allow users to et g~obal propert_ies in environ-
ment variables, which are acce ible to all program . Envuonment variables are often
sed in CIC++ and other programs. Although Java applications can also access en-
~ironrnent variables, this approach i often avoided because environment variables
are platform dependent. Java program often use properties to configure programs.
500 • More Design Patterns

Properties are represented as a mapping whose ke~s an~ values are all strings. Prop-
erties can be stored in a text file, which can be easily edited.
The following Uni versalMazeFactory class can build maze games with var-
ious schemes and using various styles of factories. It selects the scheme and factory
based on the following two properties:
■ maze . theme indicates the theme of the game board, one of Harry, Snow, or
Simple.
■ maze . prototype indicates whether to use prototype-based factory, either true
or false .
These properties can be specified in two ways:
1. In a properties file named maze. properties. The properties can be specified
in the properties file as follows:
maze.theme=Harry
maze .prototype=true

2. Using the -D command-line option. The properties can be specified on the


command line as follows:
java -Dmaze.theme=Harry -Dmaze . prototype=true maze.UniversalMazeFactory

The Universal.MazeFactory class is implemented as a singleton. The static


initialization block is executed before the singleton instance is created. During the
initialization, it tries to find the properties file named maze. properties. If the file
is found, the properties specified in the file will be used. Otherwise, it tries to use the
system properties. In the get Instance() method, it creates a factory based on the
values of properties read in the initialization.

Claa aaze. UniYeraa.lllazeFacto~


package maze ;
import j ava. ut il. • ;
impor t j ava .io . •;
import j ava . awt .*;
import javax .swi ng .•;

public class Uni versalMazeFactory extends MazeFactory {


public static MazeFact ory getlnstance () {
i f (thelnstance =• nul l ) {
if (usePrototype) {
HazeFact ory factor y = null;
swi t ch (theme) {
case HARRY_PORTER_THEME :
factory • new maze• harry. HarryPotterMazeFactory () ;
break;
case SNOW_WHITE_THEME:
factory • new maze · 8 now. SnowWh1teM
. azeFactor y( ) ·
~e~; '
}
10.2 Creational Design Patterns • 501

if (factory,_ null) {
factory• new MazeFactory();
}
thelnstance •
new HazePrototypeFactory(factory.makeMaze () ,
factory.makeWall () ,
factory.makeRoom (O),
factory.makeDoor(nuli, null));
} else {
switch (theme) {
case HARRY_PORTER_THEHE:
thelnstance: new maze .harry .HarryPotterHazeFactory ();
break;
case SNOW_WHITE_THEME :
thelnstance • new maze.snow . SnowWhiteHazeFactory ();
break;
default:
thelnstance = new HazeFactory();
break ;
}
}
}
return thelnstance;
}

protected UniversalMazeFactory() {}

private static MazeFactory thelnstance = null;


private static final int SIMPLE_THEME = O;
private static final int HARRY_PORTER_THEME • 1;
private static final int SNOW_WHITE_THEME = 2;
private static boolean usePrototype = true ;
private static int theme• SIMPLE_THEME ;

static {
Properties configProperties • new Properties ();
try {
configProperties.load(new FileinputStream ("maze .pr operties" ));
} catch (!□Exception e ) {
e .printStackTrace ();
}
String value ;
value• System.getProperty ("maze .theme");
if (value =z null ) {
value '"' configProperties .getProperty( "maze .theme ");
}
if (value !• null) {
if (" Harry" . equals (value)) {
theme • HAAAY_PORTER_THEHE ;
} else if ("Snow" .equals (value )) {
theme a SNOW_WHITE_THEME;
}
}
502 ■ More Design Patterns

value• System.getProperty("maze.prototype");
if (value•• null) {
value • configProperties. get Property ("maze. prototype") ;
}

if (value !• null) {
usePrototype • Boolean .getBoolean(value) ;
}
}

public static void main(String[] args) {


MazeFactory factory• UniversalMazeFactory . getinstance();
Maze maze• MazeGameAbstractFactory.createMaze(factory);
maze.setCurrentRoom(1);
maze . showFrame (" Maze -- Universal Factory");
}

10.2.5 Design Pattern: Builder

In the previous examples, the method createMaze () constructs a maze board. It


is lengthy and contains many code segments that perform identical tasks such as
creating similar components of the maze board. The Builder design pattern can be
used to simplify the construction process by avoiding the duplication of similar code
and shortening the code for the construction.

Design Pattern Builder

Category: Creational design pattern.


Intent: To separate the construction of a complex object from the implementation
of its parts so that the same construction process can create complex objects
from different implementations of parts.
Applicability: Use the Builder design pattern
• when the process for creating a complex object should be independent
of the parts that make up the object.
• when the construction process should allow various implementations of
the parts used for construction.
10.2 Creatfonal Design Patterns • 503

The structure of the Builder design pattern is shown in the following diagram:

Director A
Builder

Product buildProduct() buildPart()

/
ConcreteBuilder1
~
ConcreteBuilder2
I P~oduct I buildPart() buildPart()

The participants of the Builder design pattern are the following:

• Builder (e.g., MazeBuilder), which defines an interface for creating parts of a


Product object.
• ConcreteBuilder (e.g., SimpleMazeBuilder, FactoryMazeBuilder), which
constructs and assembles parts of the product by implementing the Builder
interface.
• Director (e.g., MazeGameBuilder), which constructs a Product object using the
Builder interface.
• Product (e.g., Maze), which represents the complex object under construction.

The design of the maze game using the Builder design pattern is hown in
Figure 10.7. In this design, we define a builder interface MazeBuilder to build

Figure 10.7 MazeGameBuilder MazeBuilder

A design of the Maze createMaze(MazeBuilder builder) - Room buildRoom()


maze game using Door buildDoorO
the Builder pattern. 1~

I
SimpleMazeBuilder FactoryMazeBuilder

Room buildRoom() Room buildRoom()


Door buildDoor() Door buildDoor()

'
I MazeFactory I
504 • More Design Patterns

components of the maze board from the basic parts. The methods of the MazeBuilder
class are summarized in the following table:

Methods Description

newMaze () Start to build a new maze board.


getMaze () () Retrieve the maze boards that have been completely built.
buildRoom () Build a new room with walls on all four sides.
buildDoor () Build a door between two rooms.

Clallaae.lluehil4er-
package maze;
public interface HazeBuilder {
public void newMaze();
public Haze getMaze();
public void buildRoom(int roomNumber);
public void buildDoor(int roomNumberl, int roomNumber2,
Direction dir, boolean open);
}

Two concrete builders are implemented. The SimpleMazeBuilder class con-


structs the map site objects in the default themes using the new operator.

a. .... a lellazeBuilder
package maze ;
public class SimpleMazeBuilder implements MazeBuilder {
public void newHaze() {
maze= new Maze();
}

public Haze getHaze() {


return maze;
}

public void buildRoom(int roomNumber) {


if (maze s• null) {
newMaze();
}
Room room • new Room(roomNumber);
for (Direction dir = Direction.first(); dir != null;
dir • dir .next()) {
room.setSide(dir, new Wall());
}
maze.addRoom(room);
}
10.2 Creational Design Patterns • SOS

public void buildDoor(int roomNumberl, int roomNumber2 ,


Direction dir, boolean open) {
if (maze== null) {
newMaze() ;
}
Room rooml = maze.findRoom (roomNumberl) ;
Room room2 = maze.findRoom (roomNumber2) ;
if (rooml != null lk
room2 != null kk
dir != null) {
Door door= new Door(rooml , room2) ;
rooml.setSide(dir, door);
room2 . setSide (dir .opposite(), door) ;
door . set □pen(open);
}
}

protected Maze maze;


}

The FactoryMazeBuilder class uses a factory to construct the map site objects,
so that different themes can be supported.

Clallaaze.Facto~eBuilder
package maze;
public class FactoryMazeBuilder implements MazeBuilder {
public FactoryMazeBuilder (MazeFactory factory) {
this . factory= factory ;
}

public void newMaze () {


maze= factory .makeMaze ();
}

public Maze getMaze ( ) {


return maze;
}

public void buildRoom(int roomNumber ) {


if (maze == nul l ) {
newMaze O ;
}
Room room = factory .makeRoom(r oomNumber);
f or (Dire ct i on di r = Direction .f irst (); dir != null ;
dir = dir .next() ) {
r oom. setSi de (di r, fact ory .makeWall ( ));
}
maze . addRoom(room);
}
506 ■ More Design Patterns

public void buildDoor(int roomNwnber1, int roomNumber2,


Direction dir, boolean open) {
if (maze== null) {
newMaze();
}
Room rooml = maze.findRoom(roomNumber1);
Room room2 = maze .findRoom(roomNumber2);
if (rooml != null kk
room2 != null &k
dir != null) {
Door door= factory .makeDoor(room1, room2);
rooml.setSide(dir, door);
room2.setSide(dir.opposite(), door);
door . set□pen(open);
}
}

protected MazeFactory factory;


protected Maze maze;
}

The MazeGameBuilder class is the director in the Builder pattern. It constructs


the maze board from the components built by the builder. The createMaze () method
constructs the same 3 x 3 maze board constructed in the previous examples. Using
the Builder pattern, the createMaze O method becomes much simpler and shorter.

Class aaz•. MazeGueBuild•r


package maze;
import java.awt . *;
import javax.swing.*;
public class MazeGameBuilder {
public static Maze createMaze(MazeBuilder builder) {
builder.newMaze();
builder.buildRoom(l);
builder.buildRoom(2);
builder .buildRoom(3);
builder.buildRoom(4);
builder.buildRoom(5);
builder.buildRoom(6);
builder .buildRoom(7);
builder.buildRoom(8);
builder .buildRoom(9);
builder.buildDoor(l, 2, Direction.WEST, true);
builder .buildDoor(2, 3, Direction.WEST, false);
builder .buildDoor(4, 6, Direction . WEST, true);
builder .buildDoor(5, 6, Direction . WEST, true);
builder .buildDoor(5, 8, Direction.NORTH, false);
builder .buildDoor(6, 9, Di rection . NORTH, true) i
10.3 Behavioral Patterns • 507

builder.build0oor(7, 8, Direction.WEST, true);


builder . buildDoor(l, 4, Direction.NORTH, true);
return builder.getMaze();
}

public static void main(String[] args) {


Maze maze;
MazeBuilder builder;
MazeFactory factory= null;
if (args.length > 0) {
i f ("Harry". equals (args [OJ)) {
factory= new HarryPotterMazeFactory();
} else i f ("Snow". equals (args [OJ)) {
factory= new SnowWhiteMazeFactory();
} else i f ("Default".equals(args[O])) {
factory= new MazeFactory();
}
}
if (factory!= null) {
builder= new FactoryMazeBuilder(factory);
} else {
builder= new SimpleMazeBuilder();
}
maze= createMaze(builder);
maze.setCurrentRoom(l);
maze.showFrame("Maze -- Builder");
}

The MazeGameBuilder class can be invoked with an optional argument. If the


argument is present, the FactoryMazeBuilder will be used and the argument spec-
ifies the theme: Harry for the Harry Potter theme, Snow for the Snow White theme, or
Default for the default theme. If the argument is absent, the SimpleMazeBuilder
will be used.

10.3 BEHAVIORAL PATTERNS

10.3.1 Design Pattern: Command


In the previous section, we focused on constructing the maze board. In this section,
we focus on the behavior of the maze game. The player starts in a specified room of
the maze board. The player can be moved with the arrow keys: left, right, up, and
down. The player can move to an adjacent room through an open door. Furthermore,
we upport the undoing of move by the player.
508 ■ More Design Patterns

An ob~ec · ted approach to supporting the undoing of actions is to use the


. l- onen • b'
Command pattern, which is a behavioral design pattern that treats actions as o 1ects.

Design Pattern Command

Categol)•: Behavioral design pattern.


Intent: To encapsulate an action as an object, so that actions can be passed as
parameters, queued, and possibly undone.
Also KJ1own As: Action.
Applicability: Use the Command design pattern
• when actions need to be passed as parameters.
• when actions need to be queued and then executed later.
• when actions can be undone.

The structure of the Command design pattern is shown in the following diagram:

I Client I I lnvoker IA
r-,, - Command
ereate : execute()
..
Receiver
receiver
-- ConcreteCommand
actionO
·.. .......... ..... - .... . ......... . . .... . . .;:, execute()
9

receiver.action()

The participants of the Command design pattern are the following:

• Command (e.g., Command, UndoableCommand), which defines an interface to


perfonn or undo an action.
■ Receiver (e.g., Maze), which knows how to perform the actions.
• ConcreteCommand (e.g., MazeMoveCommand), which implements the Com-
mand interface and delegates the execution of the action to the Receiver.
• Client (e.g., Maze. MazeKeyListener), which creates the concrete commands
and binds the concrete commands to their receivers.
• Invoker (e.g., Maze), which asks the command to carry out the action.
10.3 Behavioral Patterns • 509

10.3.2 Supporting Undo

The design of the maze game with undoable moves using the Command pattern is
shown in Figure 10.8.

The Command Interfaces


The Command interface defines the basic interface of command objects. The exe-
cute O method performs the action represented by the command object.

Cllllllaaze,Coaaall4
package maze;
public interface Command {
public void execute() ;
}

The UndoableCommand interface extends the Command interface to define an


undo() method, which undoes the action performed by the execute() method.

Claas me. lJJldoabl.CO.aal


package maze;
public interface UndoableCommand extends Command {
public void undo ();
}

Figure 10.8 Command

Undoable moves in execute()


the maze game us- ~
ing the Command
pattern.
Maze.Keylistener create UndoableCommand
..... ... .. . . .......
keyPressed() undo()
~
I
I
I I

Maze MazeMoveCommand
······•·"···

move() • execute()
1
doCommand(Command c) A -
undo()
V

undoCommand() moves
51 0 • More D~ign Patterns

The Receiver and Invoker of Command


The receiver in the Command pattern is the class that is responsible for actually
carrying out the actions represented by the command objects. The invoker is the class
that invokes the command, that is, that has the action of the command object carried
out. In the maze game, the role of the receiver and the invoker are played by the
same class: Maze. The move () method of the Maze class fulfills the responsibility
of the receiver. It attempts to move the player in the specified direction. If there is
an open door in the specified direction, the player enters the room on the other side
of the door. Otherwise, the player stays in the current room. The doCommand () and
u.ndoCommand () methods of the Maze class fulfill the responsibility of the invoker.
The doCommand () method executes the command and saves the command in a stack
named moves for possible undoing of the command. The undoCommand () method
pops the command at the top of the moves stack and attempts to undo the command.

a.. ....11u.
public class Maze implements Cloneable {
(Methods for building the maze board. See Section 10.2.1 [p. 475]}
public void move(Direction direction) {
if (cu.rRoom != null) {
MapSite side= cu.rRoom . getSide (direction);
if (side != null) {
side.enter(this) ;
}
}
}

protected void doCommand(Command command) {


if (command!= null) {
moves . push(command) ;
command.execute();
}
}

protected void undoCommand() {


if (!moves.empty()) {
Object top = moves. peek() ; // looking at the top element
// without popping it
if (top instanceof UndoableCommand) {
moves. pop() ;
UndoableCommand undoableCommand • (UndoableCommand) top;
undoableCommand . undo();
}
}
}

protected List rooms . new ArrayList() ;


protected Room curRoom z null I·
protected Stack moves. new Stack();
(Nested class MazeKeyListener on page 511 )
}
10.3 Behavioral Patterns • 511

The Concrete Commands


The concrete command in the maze game is the MazeMoveCommand class, which
implements the UndoableCommand interface. The execute O method attempts to
move the player in the specified direction, and the undo () method attempts to move
the player in the direction opposite to the specified direction.

Class 1U1Ze .JlazdoveCommand


package maze;
public class MazeMoveCommand implements UndoableCommand {
public MazeMoveCommand(Maze maze, Direction direction) {
this.maze= maze;
this.direction= direction;
}

public void execute() {


maze.move(direction);
}

public void undo() {


maze.move(direction.opposite());
}

protected Maze maze;


protected Direction direction;
}

The Client of Command


The client of the Command pattern is the class that is responsible for creating the
command objects. In the maze game, this client is the nested class Maze . MazeKey-
List ener, which listens for key strokes. For the arrow keys (left, right, up, and
down) it creates a MazeMoveCommand object and sends the command object to the
maze object.

Nested class ol ■ue. Maze: NueKe Listener


static class MazeKeyListener extends KeyAdapter {
MazeKeyListener(Maze maze) {
this.maze= maze ;
}

public void keyPressed(KeyEvent e) {


System.out.println("Key pressed");
Command command"" null;
int code"" e.getKeyCode();
switch (code) {
case KeyEvent .VK_UP:
command= new MazeMoveCommand(maze, Direction . NORTH);
break ;
case KeyEvent . VK_DOWN :
command= new MazeMoveCommand(maze, Direction.SOUTH);
maze . move (Direction . SOUTH);
51 2 • More Design Patterns

break;
case KeyEvent · VK_LEFT: Direction. WEST);
commands new MazeMoveCommand(maze,
break;
case KeyEvent.VK_RIGHT:
command. new MazeMoveCommand(maze, Direction.EAST);
break;
}
if (command != null) {
maze .doCommand(command);
}
}

Haze maze;
}

The Undoable Maze Game


The maze. UndoableMazeGame class is the main class of the maze game that sup-
ports undo commands. The main () method is similar to the main () method of the
MazeGameBuilder class, except that it also builds a menu bar that contains an undo
menu item. The undo command can be invoked from the undo menu item.

package maze;

import java.awt.•;
import java.awt .event.•;
import javax.swing.•;
public class UndoableMazeGame {
public static void main(String[] args) {
Maze maze;
MazeBuilder builder;
MazeFactory factory= null;
if (args.length > 0) {
if ("Harry".equals(args[O])) {
factory= new HarryPotterMazeFactory();
} else if ("Snow" . equals (args [OJ)) {
factory s new SnowWhiteMazeFactory();
} else if ("Default".equals(args[O])) {
factory• new MazeFactory();
}
}
if (factory !• null) {
builder• new FactoryMazeBuilder(factory);
} else {
builder= new SimpleMazeBuilder();
}
maze= MazeGameBuilder.createMaze(builder);
maze.setCurrentRoom(l);
10.4 Structural Patterns • 513

JHenuBar menubar • nev JHenuBar();


JHenu menu• new JHenu("Command");
JHenuitem undoHenuitem • new JMenuitem("undo");
undoMenuitem.addActionListener(new MazeCommandAction(maze));
menu.add(undoMenuitem);
menubar.add(menu);
JFrame frame;
frame= new JFrame("Maze -- Builder");
frame . getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(menubar, BorderLayout.NORTH);
frame.getContentPane().add(new Maze.HazePanel(maze),
BorderLayout.CENTER);
frame. pack() ;
Dimension frameDim = frame.getSize();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation(screenSize.width / 2 - frameDim.width / 2,
screenSize.height / 2 - frameDim.height / 2);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}

static class MazeCommandAction implements ActionListener {


public MazeCommandAction(Maze maze) {
this.maze= maze;
}

public void actionPerformed(ActionEvent event) {


maze .undoCommand();
}

protected Maze maze;


}
}

'•

,1M STRUCTURAL PATTERNS

10.4.1 Design Pattern: Adapter

The Adapter design pattern is another very commonly used design pattern. We often
find some classes that provide functionality that can be reused. However, the interface
providing the functionality is not the interface expected by the client. The Adapter
pattern addresses the issue of adapting an interface to accommodate the needs of a
client that hopes to reuse the functionality but expects a different interface.

Design Pattern Adapter


Catego1)1: Structural design pattern.
Intent: To convert the interface of a cla into another interface that clients expect.
514 • More Design Patterns

Also Known As: Wrapper.


Applicability: Use the Adapter design pattern
• to use an existing class with an interface different from the desired
interface.

There are two forms of the Adapter pattern:

■ Class adapter, which relies on inheritance. The structure of the class adapter is
shown in the following diagram:

Client Target Adaptee

do Task() periormTask()

Adapter

doTask() 0 .......... periormTask()

■ Object adapter, which relies on delegation, or object composition. The structure


of the object adapter is shown in the following diagram:

Client Target Adaptee

do Task() periormTask()
~
I
I i
: ~ adaptee
Adapter

doTask() 0 . .. .. .. . . adaptee.periormTask()

The participants of the Adapter design pattern are the following:

■ Target (e.g., TableEntry), which defines the interface used by the Client .
■ Client (e.g., Table), which uses objects conforming to the Target interface.
■ Adaptee (e.g., Student), which defines the interface of an existing class to be
reused.
■ Adapter (e.g., StudentEntry, StudentEntry2), which adapts the interface of
Adaptee to Target.

To illustrate the Adapter pattern, we design a generic table Lo display a Ii t of


objects of any class in tabular form. An example of a table with entries of student
10.4 Structural Patterns • 515

Figure 10.9
T GP~
The table of entries 80108 103-367- ◄ 105 2. 1
134191 '156-303-8166 2.5
of student informa- 71126 230-525-18◄9 3. 1

tion. 93686 , 252- ◄ 38-9179 3.8


37857 272-666- 5555 .9
179910 1321-65 ◄ -◄ 567 3 7

Allen 51 Garden 5treet OR


Goslln 1 Oak 5treet CA 98650 516- 192- 9406 ◄. 0
Gates _ l Mtcroson W;ry -•"'A 65432
1
555 - 123 - 4567 j3.9
McNea 123 Maln 5treet CA 90715 590-298-4262 3.5

information is shown in Figure 10.9. When you click on the header of a column, the
list of objects will be sorted based on the values in that column.
Our table class, adapter. Table, extends the JTable class in swing. The design
of the generic table is shown in Figure I 0.10.
The key to the design of the generic table is that the data entries shown in the
table must be instances of a class that conforms to the TableEntry interface.

Class adaeter. TableEntry:


package adapter ;
import java.util . Comparator;
publi c interface TableEntry {
public int getColumnCount() ;
public String getColumnName(int col) ;
publi c Object getColumnValue (i nt col) ;
public String getColumnTip(int col);
public Class getColumnClass(int col) ;
public Comparator getColumnComparator ( int col);
public int getColumnWidth(int col) ;
}

Figure 10.1 O 1 1
javax.swing.JTable10- -----,;:il't javax.swing.table.TableModel

The design of the


generic table with javax.swing.table.AbstractTableModel
sorting capability.

Table.ListTableModel TableEntry
Table
516 • More Design Patterns

Each entry represents a row in the table. The methods of the TableEntry
interface are summarized in the following table:

Methods Description

getColumnCount () Returns the number of columns


getColumnName (col) Returns the name of column col, which will be
displayed on the header of the column
getColumnValue (col) Returns the value of column col, which will be
displayed in the cell of the column
getColumnTip ( col) Returns the text of pop-up tips for column col,
which will be displayed when the mouse moves
over the header of the column
getColumnClass (col) Returns the class of the values in column col
getColumnComparator(col) Returns a Comparator object for sorting
column col
getColumnWidthO Returns the minimum width of column col

The implementation of the Table class is shown in the following code. However,
the implementation of the Table class is immaterial to the discussion of the Adapter
pattern.

a- a48l~er. Table
package adapter;
import java.awt.Color;
import java.awt.Point;
import java.allt .event .•;
import javax.swing.• ;
import javax.swing.table.•;
import javax.swing. event . •;
import java.util.•;
public class Table extends JTable {
public Table() {
this (null) ;
}

public Table(List entries) {


super(new ListTableModel(entries));
model= (ListTableModel) dataModel·
'
--....
·- » -
- 10.4 Structural Patterns ■ 51 7

getTableHeader().addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
JTableHeader header= (JTableHeader) e.getSource();
int column= header.columnAtPoint(p) ;
if (model.sort(column)) {
clearSelection();
updateUI O ;
}
}
}) ;

setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

int columnCount = model.getColumnCount();


for (inti= O; i < columnCount; i++) {
TableColumn column= getColumnModel().getColumn(i) ;
DefaultTableCel lRenderer renderer=
new DefaultTableCellRenderer();
String tip= model.getColumnTip(i);
renderer.setToolTipText(tip);
column . setCellRenderer(renderer);
int w = model.getColumnWidth (i);
if (w > 0) {
column . setPreferredWidth(w);
}
}
}

protected ListTableModel model;

static class ListTableModel extends AbstractTableModel {

public ListTableModel(List entries) {


if (entries != null &&
entries.size()> 0) {
Object obj= entries .get(O);
if (obj != null &&
obj instanceof TableEntry) {
this . prototype= (TableEntry) obj;
setData(entries) ;
}
}
}

public ListTableModel (TableEntry prototype) {


this.prototype= prototype;
}
51 8 • More Design Patterns

public int getColumnCount() {


if (prototype != null) {
return prototype.getColumnCount();
}
return O;
}

public int getRowCount() {


if (entries != null) {
return entries.size();
} else {
return O;
}
}

public String getColumnName(int col) {


if (prototype != null) {
return prototype . getColumnName(col);
}
return null;
}

public Object getValueAt(int row, int col) {


if (entries != null) {
TableEntry entry= getTableEntry(row);
if (entry!= null) {
return entry.getColumnValue(col);
}
}
return null;
}

public Class getColumnClass(int col) {


if (prototype != null) {
return prototype.getColumnClass(col);
}
return String.class;
}

public String getColumnTip(int col) {


if (prototype != null) {
return prototype.getColumnTip(col);
}
return null;
}

public Comparator getColumnComparator(int col) {


if (prototype != null) {
return prototype.getColumnComparator(col);
}
return null;
}
10.4 Structural Patt~ms • 51 9

public int getColumnWidth(int col) {


if (prototype!= null) {
return prototype.getColumnWidth(col);
}
return -1 ;
}

public boolean isCellEditable(int row, int col) {


return false;
}

public void setValueAt(Object value, int row, int col) {


}

public void clearData() {


entries= null;
}

public void setData(List entries) {


this.entries= entries;
}

public boolean sort(int col) {


if (entries != null &&
col >=O &k
col< getColwnnCount ()) {
Comparator c = getColwnnComparator(col);
if (c != null) {
Collections . sort(entries, c) ;
return true;
}
}
return false;
}

public TableEntry getTableEntry(int i) {


if (entries != null kl
i >= 0 tt
i < entries.size ()) {
return (TableEntry) entries . get(i) ;
}
return null;
}

protected TableEntry prototype ;


protected List entries; // elements are Instance of TableEntry

}
5 20 • More Design Patterns

Now, given the following Student class, we want to display the student infor-
mation using the generic table.

ter.Stud•nt
package adapter ;
public class Student implements Cloneable {
public Student () {}
public Student(String ID,
String firstName,
String lastName,
String streetAddress,
String state,
String city,
String country,
String postalCode,
String telephone,
float GPA,
int totalCredits) {
this . ID = ID ;
this.firstName = firstName;
this . lastName = lastName;
this . streetAddress = streetAddress ;
this.state= state;
this.city= city;
this . country= country;
this . postalCode = postalCode;
this . telephone= telephone;
thi s .GPA= GPA ;
this.totalCredits = totalCredits;
}

public Object clone()


throws CloneNotSupportedException {
return super.clone();
}

public String getCity() {


return city;
}

public String getCountry() {


return country ;
}

public String getFirstName() {


return firstName;
}

public float getGPA () {


return GPA;
}
10.4 Structural Patterns ■ 5 21

public String getID() {


return ID;
}

public String getLastName() {


return lastName;
}

public String getPostalCode() {


return postalCode;
}

public String getState () {


return state;
}

public String getStreetAddress () {


return streetAddress;
}

public String getTelephone () {


return telephone;
}

public int getTotalCredits () {


return totalCredits ;
}

public void setCity (String city) {


this . city = city;
}

public void setCountry (String country) {


this . country = count ry ;
}

public voi d setFi rstName (String f irstName) {


this . first Name = f irstName ;
}

public void setGPA (fl oat GPA) {


this. GPA = GPA;
}

publi c void s etID (St ring ID) {


this . ID= ID ;
}

public void setLas tName(Str ing lastName) {


thi s . l astName = lastName;
}

public void setPost alCode (String postalCode ) {


this.postalCode = postalCode ;
}
522 • More Design Patterns

public void setState(String state) {


this.state= state;
}

public void setStreetAddress(String streetAddress) {


this.streetAddress = streetAddress;
}

public void setTelephone(String telephone) {


this.telephone= telephone;
}

public void setTotalCredits(int totalCredits) {


this.totalCredits = totalCredits;
}

public String toString() {


StringBuffer s = new StringBuffer();
s . append (" Student [") ;
s.appendC'totalCredits=");
s . append(totalCredits);
s . append ( 11 ; 11 ) ;
s . append( 11 GPA= 11 ) ;
s.append(GPA);
s . append (" ; ") ;
s . append ( i, telephone= 11 ) ;
s.append(telephone);
s . append (" ; 11 ) ;
s . append("postalCode=");
s.append(postalCode);
s . append (" ; 11 ) ;
s . append("country=");
s . append(country);
s . append (" ; ") ;
s.append("city=");
s . append(city);
s . append (" ; ") ;
s . append("state= 11 ) ;
s.append(state);
s . append ( 11 ; 11 ) ;
s.append( 11 streetAddress= 11 ) ;
s . append(streetAddress);
s . append ( 11 ; 11 ) ;
s . append( 11 lastName•");
s.append(lastName);
s . append ( 11 ; 11 ) ;
s.append( 11 firstName .. 11 ) ;
s.append(firstName);
s . append (" ; 11 ) ;
s.append( 11 ID• 11 ) ;
s.append(ID);
s . append ( "] 11 ) ;
return s.toString();
}
10.4 Structural Pattans • 523

protected int totalCredits;


protected float GPA;
protected String telephone;
protected String postalCode;
protected String country;
protected String city;
protected String state;
protected String streetAddress ;
protected String lastName;
protected String firstName;
protected String ID;
}

The problem is that the Table class expects the entries to be instances of a
class that confonns to the TableEntry interface, while the Student class does not
conform to the TableEntry interface. Instead of rewriting the Student class to
make it conform to the TableEntry interface, we can use the Adapter pattern. We
can use either form of the Adapter to accomplish the task. The design using the class
adapter form is shown in Figure 10.1 l. The StudentEntry class is the adapter, the
Student class is the adaptee, and the TableEntry interface is the target.

Class a ter. StudentEntry:


package adapter;
import java.util . Comparator;

• Adapter design pattern
*/
public class StudentEntry extends Student implements TableEntry {
// position of each column
public static final i nt ID_COLUMN = O;
public static final int FIRST_NAME_COLUMN = 1;
public static final int LAST_NAME_COLUMN = 2;
public static final int STREET_ADDRESS_COLUMN = 3;
public static final int STATE_COLUMN = 4;
public static final int CITY_COLUMN = S;
public static final int COUNTRY_COLUMN = 6;
public static final int POSTAL_CODE_COLUMN = 7;

1gure 10.11 1 •
1 1 ~-~ TableEntry Student
Table n-- -;:ll>j Table.ListTableModel
lass adapter: ,_____ _
I

adapt by lnherf- I
I
ance.
StudentEntry
5 24 ■ More Design Patterns

= 8;
public static final int TELEPHONE_COLUMN
public static final int GPA_COLUMN = 9;
public static final int TOTAL_CREDITS_COLUMN = 10;

public static final String[] columnNames = {


"ID",
"First Name",
"Last Name",
"Street Address",
"State",
"City",
"Country",
"Postal Code",
"Telephone",
"GPA",
"Total Credits",
};

public static final String[] colurnnTips = {


"ID",
"First Name",
"Last Name",
"Street Address",
"State",
"City",
"Country",
"Postal Code",
"Telephone",
"GPA",
"Total Credits",
};

public static final Comparator[] comparators= {


new StudentEntryComparator(ID_COLUMN),
new StudentEntryComparator(FIRST_NAME_COLUMN),
new StudentEntryComparator(LAST_NAME_COLUMN),
new StudentEntryComparator(STREET_ADDRESS_COLUMN),
new StudentEntryComparator(STATE_COLUMN),
new StudentEntryComparator(CITY_COLUMN),
new StudentEntryComparator(COUNTRY_COLUMN),
new StudentEntryComparator(POSTAL_CODE_COLUMN),
new StudentEntryComparator(TELEPHONE_COLUMN),
new StudentEntryComparator(GPA_COLUMN),
new StudentEntryComparator(TOTAL_CREDITS_COLUMN),
};

public StudentEntry(String ID,


String firstName,
String lastName,
String streetAddress,
String state,
String city,
String country,
String postalCode,
10.4 Structural Patt~ms • 5 25

String telephone,
float GPA,
int totalCredits) {
super(ID, firstName, lastName, streetAddress , state, city,
country, postalCode, telephone, GPA, totalCredits);
}

public StudentEntry(Student student) {


if (student != null) {
this . ID= student.ID;
this . firstName = student . firstName;
this . lastName = student . lastName;
this.streetAddress = student . streetAddress;
this . state= student . state;
this.city= student . city;
this . country= student.country;
this .postalCode = student.postalCode;
this.telephone= student .telephone ;
this.GPA= student.GPA;
this .totalCredits = student .totalCredits;
}
}

public int getColumnCount( ) {


return columnNames.length;
}

public String getColumnName (int col) {


i f ( col >= 0 &:&:
col< columnNames.length) {
return columnNames[col];
}
return null;
}

public Object getColumnValue (int col) {


i f (col>= 0 &:&:
col< columnNames.length) {
switch ( col) {
case ID_COLUMN : return ID;
case FIRST_NAME_COLUMN : return firstName;
case LAST_NAME_COLUMN : return lastName;
case STREET_ADDRESS_COLUMN : return streetAddress ;
case STATE_COLUMN : return state;
case CITY_COLUMN: return city;
case COUNTRY_COLUMN: return country;
case POSTAL_COOE_COLUMN : return postalCode;
case TELEPHONE_COLUMN : return telephone;
case GPA_COLUMN : return new Float(GPA);
case TOTAL_CREDITS_COLUMN : return new Integer(totalCredits);
}
}
return null;
}
526 • More Design Patterns

public String getColumnTip(int col) {


if (col >= 0 U
col< columnTips . length) {
return columnTips[col];
}
return null;
}

public Class getColumnClass(int col) {


if (col== GPA_COLUMN) {
return Float . class;
} else if (col== TOTAL_CREDITS_COLUMN) {
return Integer.class;
} else {
return String . class ;
}
}

public Comparator getColumnComparator(int col) {


if (col>= 0 ttz
col< comparators . length) {
return comparators[col];
}
return null;
}

public int getColumnWidth(int col) {


return -1;
}

static public class StudentEntryComparator implements Comparator {


public StudentEntryComparator(int col) {
this . col= col;
}

public int compare(Object ol, Object o2) {


if (ol != null tt
o2 !• null tt
ol instanceof StudentEntry tt
o2 instanceof StudentEntry) {
StudentEntry el= (StudentEntry) ol;
StudentEntry e2 • (StudentEntry) o2;
if (col c c GPA_COLUMN) {
return (int) (el . getGPA() • 1000 - e2.getGPA() • 1000);
} else if (col== TOTAL_CREDITS_COLUMN) {
return (el.getTotalCredits() - e2.getTotalCredits( ));
} else {
return ((String) el.getColumnValue(col)).compareTo
(e2 . getColumnValue(col));
}
}
return O;
}
10.4 Structural Patterns • 527

protected int col;


}
}

The design using the object adapter form is shown in Figure 10.12. The
StudentEntry2 class is the adapter, the Student class is the adaptee, and the
TableEntry interface is the target.

a. .StuclatEnt
package adapter;
import java.util.Comparator;
r
• Adapter design pattern
·1
public class StudentEntry2 implements TableEntry {
II position of each column
public static final int ID_COLUMN = O;
public static final int FIRST_NAME_COLUMN = 1;
public static final int LAST_NAME_COLUMN = 2;
public static final int STREET_ADDRESS_COLUMN = 3;
public static final int STATE_COLUMN = 4;
public static final int CITY_COLUMN = 5;
public static final int COUNTRY_COLUMN =6;
public static final int POSTAL_CODE_COLUMN = 7;
public static final int TELEPHONE_COLUMN = 8;
public static final int GPA_COLUMN = 9;
public static final int TOTAL_CREDITS_COLUMN = 10;

public static final String □ columnNames = {


"ID",
"First Name",
"Last Name",
"Street Address",
"State",
"City",
"Country",
"Postal Code",
"Telephone",
"GPA",
"Total Credits",
};

1 1 1 ..
Figure 10.12 Table JC>-- -::l"I Table.listTableModel > - -~ Tab/eEntry Student
1
Object adapter:
dapt by delega- 1
on. StudentEntry
Student
528 • More Design Patterns

public static final String[] columnTips = {


"ID",
"First Name",
"Last Name",
"Street Address",
"State",
"City",
"Country",
"Postal Code",
"Telephone",
"GPA",
"Total Credits",
};

public static final Comparator[] comparators= {


new StudentEntryComparator(ID_COLUMN),
new StudentEntryComparator(FIRST_NAME_COLUMN),
new StudentEntryComparator(LAST_NAME_COLUMN),
new StudentEntryCornparator(STREET_ADDRESS_COLUMN),
new StudentEntryComparator(STATE_COLUMN),
new StudentEntryComparator(CITY_COLUMN),
new StudentEntryComparator(COUNTRY_COLUMN),
new StudentEntryComparator(POSTAL_CODE_COLUMN),
new StudentEntryCornparator(TELEPHONE_COLUMN),
new StudentEntryComparator(GPA_COLUMN),
new StudentEntryComparator(TOTAL_CREDITS_COLUMN),
};

public StudentEntry2(String ID,


String firstName,
String lastName,
String streetAddress,
String state,
String city,
String country,
String postalCode,
String telephone,
float GPA,
int totalCredits) {
student= new Student(ID, firstName, lastNarne,
streetAddress, state, city, country,
postalCode, telephone, GPA,
totalCredits);
}

public StudentEntry2(Student student) {


this.student~ student;
}

public int getColumnCount() {


return columnNames.length;
}
...·--------
4

10.4 Structural Patterns • 529

public String getColumnName(int col) {


if (col>= O &i&c
col< columnNames. length) {
return columnNames[col);
}
return null;
}

public Object getColumnValue(int col) {


if (student != null k&
col>= 0 &&
col< columnNames.length) {
switch (col) {
case ID_COLUMN: return student . getID();
case FIRST_NAME_COLUMN : return student.getFirstName();
case LAST_NAME_COLUMN : return student .getLastName();
case STREET_ADDRESS_COLUMN : return student.getStreetAddress();
case STATE_COLUMN: return student.getState();
case CITY_COLUMN: return student.getCity();
case COUNTRY_COLUMN: return student . getCountry() ;
case POSTAL_CODE_COLUMN : return student.getPostalCode();
case TELEPHONE_COLUMN: return student . getTelephone();
case GPA_COLUMN: return new Float(student . getGPA()) ;
case TOTAL_CREDITS_COLUMN : return new Integer
(student . getTotalCredits()) ;
}
}
return null;
}

public String getColumnTip(int col) {


if (col>= 0 &&
col< columnTips.length) {
return columnTips[col];
}
return null ;
}

public Class getColumnClass(int col) {


if (col== GPA_COLUMN) {
return Float . class;
} else if (col== TOTAL_CREDITS_COLUMN) {
return Integer .class;
} else {
return String.class;
}
}

public Comparator getColumnComparator(int col) {


if (col >= 0 &&
col< comparators.length) {
return comparators[col];
}
return null ;
}
530 ■ More Design Patterns

public int getColumnWidth(int col) {


return -1 ;
}

protected Student student;


static public class StudentEntryComparator implements Comparator {
public StudentEntryComparator(int col) {
this.col= col;
}

public int compare(Object ol, Object o2) {


if (ol != null k&
o2 != null kk
ol instanceof StudentEntry2 kk
o2 instanceof StudentEntry2) {
StudentEntry2 el= (StudentEntry2) o1;
StudentEntry2 e2 = (StudentEntry2) o2 ;
if (col== GPA_COLUMN) {
return (int) (el . student.getGPA() * 1000 -
e2.student.getGPA() * 1000);
} else if (col== TOTAL_CREDITS_COLUMN) {
return (e1.student . getTotalCredits() -
e2.student.getTotalCredits());
} else {
return ((String) e1 . getColumnValue(col)) . compareTo
(e2.getColumnValue(col));
}
}
return O;
}

protected int col;


}

The Main class creates a list of instances of the Student class and displays the
instances using the generic table via either of the adapters. The adapters are selected
by an optional command-line argument. If the command-line argument Delegation
is present, the object adapter will be used; otherwise, the class adapter will be used.

a..~.llaia
package adapter;

import java . awt .Dimension;


import java . awt .Toolkit;
import java .util . •;
import javax . swing .•;
public class Main {

static Student[] students • {


10.4 Structural Patterns • 531

new Student (" 1001", "Bill", "Gates",


"1 Microsoft Way", "WA", "Redmond" , "USA", "65432",
"555-123-4567", 3 . 9f, 32),
II . . . more instances of Students . . .
};

publi c static final int INITIAL_FRAME_WIDTH • 800 ;


public static final int INITIAL_FRAME_HEIGHT • 400;
public static void main(String[] args) {
boolean useDelegation • false;
if (args.length > 0 &&
"Delegation".equals(args[0))) {
useDelegation = true;
}

List entries• new ArrayList(students.length);


for ( inti• 0; i < students.length ; i++) {
if (useDelegation) {
entries . add(new StudentEntry2(students[i] )) ;
} else {
entries . add(new StudentEntry (students[i]));
}
}

Table table= new Table(entries);

JFrame frame= new JFrame("Students" );


frame.setContentPane (new JScrollPane(table));
frame . setSize ( INITIAL_FRAME_WIDTH, INITIAL_FRAME_HEIGHT);
Dimension screenSize = Toolkit . getDefaultToolkit (). getScreenSize();
frame . setLocation(screenSize.width / 2 - INITIAL_FRAHE_WIDTH / 2,
screenSize . height / 2 - INITIAL_FRAME_HEIGHT / 2);
frame . setDefaultClose0peration (WindowConstants . EXIT_0N_CL0SE);
frame.setVisible(true);
}

10.4.2 Design Pattern: Composite

The Composite design pattern was discussed in Section 8.3.2 [page 336]. Here, we
present another example to illustrate the Composite pattern.
The Composite pattern is used to build a hierarchical structure of objects. An
example of such a hierarchy can be found in variou e-mail programs:

■ A mailbox con i t of a numbe r of ,nail folders.


■ A mail folder consist of a number of mails and/or sub-mail folders; that is, mail
folder can be ne ted any number of level .
Therefore, the mail folder fom1 a hierarchy. The Composite pattern can be used to
repre ent such a hierarchical tn1cture. The de ign of the e-mail manager program
u ing the Compo itc pattern is hown in Figure 10. I 3.
532 ■ More Design Patterns

Figure 10.13
Mailbox/tern
items
A design of the mail
application using 1
the Composite pat-
Mail MailFolder
tern.

The Mailboxitem class is the component role in Composite pattern.

:t•
package mail ;
public abstract class Mailboxltem {
public String getName() {
return name;
}

public void setName(String name) {


this.name= name;
}

public MailFolder get□wner() {


return owner;
}

public void setOwner(MailFolder owner) {


this.owner= owner;
}

public String toString() {


return name;
}

public abstract int count();


public abstract int countNewMail();
protected Hailboxltem(String name, MailFolder owner) {
this.name~ name;
this .owners owner;
}

protected String name;


protected HailFolder owner;
}

The MailFolder class is the composite role in Composite pattern.

package mail;
import java.util . • ;

public class MailFolder extends Mailboxitem {


10.4 Structural Patterns • 533

public HailFolder(String name) {


super(name, null);
}

public HailFolder(String name, HailFolder owner) {


super(name, owner);
}

public List getitems() {


return items;
}

public List getSubFolders() {


List folders= new ArrayList();
Iterator iterator= items . iterator();
while (iterator .hasNext()) {
Object item• iterator.next();
if (item instanceof HailFolder) {
folders.add(item);
}
}
return folders ;
}

public List getHails() {


List mails• new ArrayList();
Iterator iterator= items.iterator();
while (iterator.hasNext()) {
Object item= iterator.next();
if (item instanceof Mail) {
mails . add(item);
}
}
return mails;
}

public void add(Hailbox!tem item) {


items.add(item);
item.setOwner(this);
}

public void add(Mailboxltem[] items) {


if (items !• null) {
for (inti• O; i < items.length ; i++) {
add (items [i]) ;
}
}
}

public i nt count () {
i nt count • O;
Iterator iterator: items.iterator();
while (iterator . hasNext ()) {
Object item • iterator.next();
if (item instanceof Mailboxitem) {
534 ■ More Design Patterns

count+= ((Mailboxitem) item).count();


}
}
return count;
}

public int countNewMail() {


int count= O;
Iterator iterator= items . iterator();
while (iterator.hasNext()) {
Object item= iterator.next();
if (item instanceof Mailboxitem) {
count+= ((Mailboxitem) item).countNewMail();
}
}
return count;
}

protected List items= new ArrayList();


}

The Mail class is the leaf role in Composite pattern.

0-..U.llail
package mail;

import java.util.*;
public class Mail extends Mailboxltem {
public Mail(String from,
String subject,
Date date,
MailPriority priority,
MailStatus status) {
this(from, subject, null, date, priority, status, null, null);
}

public Mail(String from,


String subject,
MailFolder owner,
Date date,
MailPriority priority,
MailStatus status,
String message,
List attachments) {
super(subject, owner);
this.from= from;
this .date• date;
this . priority= priority;
this.status= status;
this .message= message;
this.attachments= attachments ;
}
10.4 Structural Patterns • 535

public String getSubject() {


return name;
}

public void setSubject(String subject) {


name,. subject;
}

public Date getDate() {


return date;
}

public void setDate(Date date) {


this.date= date;
}

public MailPriority getPriority() {


return priority;
}

public void setPriority(MailPriority priority) {


this .priority= priority;
}

public MailStatus getStatus() {


return status ;
}

public void setStatus(MailStatus status) {


this.status= status;
}

public String getFrom() {


return from;
}

public void setFrom(String from) {


this . from= from;
}

public String getMessage() {


return message;
}

public void setMessage (Strin.g message) {


this.message= message ;
}

public List getAttachments() {


return attachments ;
}

public void addAttachment (Object attachment) {


if (attachment !a null) {
attachments .add (attachment) ;
}
}
536 • More Design Patterns

public int count() {


return 1;
}

public int countNewMail() {


7
return (status== MailStatus.NEW 1 0);
}

public String toString() {


return "From: " + from + 11 i Subject: 11 + name + "i \n
Received: 11 + date + 11 ; Priority: 11 + priority + " ;
Status: 11 + status + ".ti.
}
' '

protected Date date;


protected MailPriority priority;
protected MailStatus status;
protected String from;
protected String message;
protected List attachments;
}

Two ordered enumeration types are used in the Mail class: MailPriori ty and
MailStatus.

a.. ..U.llailPriorit
package mail ;
public class HailPriority implements Comparable {
public static final HailPriority LOW = new MailPriority( 11 Low 11 ) ;
public static final HailPriority MEDIUM • new MailPriority("Mediwn");
public static final HailPriority HIGH = new MailPriority("High");
public static final HailPriority VERY_HIGH •
new HailPriority ("Very high") ;
public String toString() {
return name;
}

public int getOrdinal() {


return ordinal;
}

public int compareTo(Object o) {


if (o instanceof MailPriority) {
return ordinal - ((MailPriority) o) . getOrdinal();
}
return O;
}

private MailPriority(String name) {


this.name• name;
}
10.4 Structural Patterns • 537

private static int nextOrdinal • O;


private final String name;
private final int ordinal• nextOrdinal++;
}

package mail;
public class HailStatus implements Comparable {
public static final HailStatus NEW • new HailStatus("New");
public static final HailStatus READ • new HailStatus("Read");
public static final HailStatus REPLIED • new HailStatus("Replied");
public static final HailStatus FORWARDED• new HailStatus("Forwarded");

public String toString() {


return name;
}

public int getOrdinal() {


return ordinal;
}

public int compareTo(Object o) {


if (o instanceof HailStatus) {
return ordinal - ((MailStatus) o).getOrdinal();
}
return O;
}

private HailStatus(String name) {


this.name• name;
}

private static int next□rdinal = O;


private final String name;
private final int ordinal• next □rdinal++;
}

The mail . Main class is a simple test of the mail manager program. It populates
the mailbox hierarchy by creating a number of mail folders, subfolders, and mails.
Then it gets a count of the total number of mails and the total number of new e-mail
messages in all folders, including nested subfolders, of the in box.

package mail;

import java .util.•;


public class Hain {
static Hail(] se450 • {
new Mail("Bill Gates", "Need extension for final project",
getTime(2001, Calendar . NOVEMBER, 20, 23, 50),
MailPriority.VERY_HIGH, HailStatus . REPLIED),
538 ■ More Design Pattans

new Hail( . . . ) ,
};

static Hail[] se452 • {


new Hail( . . . ) ,
} ;

static Hail[] work• {


new Hail( . ) , ..
};

static Hail[] news• {


new Hail( .. . ) ,
};

static Hail[] junks• {


new Hail( . . . ) ,
};

public static MailFolder buildlnbox() {


HailFolder inboxFolder • new MailFolder("Inbox");
HailFolder coursesFolder • new MailFolder("Courses");
HailFolder se450Folder • new Mai1Folder("SE450");
se450Folder.add(se450);
Hail.Folder se452Folder = new Mai1Folder("SE452");
se452Folder.add(se452);
coursesFolder.add(se450Folder);
coursesFolder . add(se452Folder);
HailFolder workFolder • new MailFolder("Work");
workFolder.add(work);
Hail.Folder newsFolder • new MailFolder("News");
newsFolder.add(news);
HailFolder junksFolder • new MailFolder("Junk Mails");
junksFolder.add(junks);
inboxFolder . add(coursesFolder);
inboxFolder.add(workFolder);
inboxFolder.add(newsFolder);
inboxFolder.add(junksFolder);
return inboxFolder;
}

public static void main(Stri.n g(J args) {


HailFolder inboxFolder • buildlnbox();
System.out.println( 11 You 11 + inboxFolder.countO + " mails in Inbox");
System . out .println("You 11 + inboxFolder.countNewMailO +
11
new mails in Inbox 11 ) ;
}

protected static Date getTime(int year, int month, int date, int hour,
int minute) {
Calendar calendar• Calendar . getlnstance() ;
calendar . set(year, month, date, hour, minute, O) ;
return calendar . getTime() ;
}

}
10.4 Structural Patterns • 539

Figure 10.14
lnbox
A mall application. f ·CJ Courses
t [)SE-ts
[)SE'452
ux -- a small OS
for final project

[)work
[)News
D Junk Mails

A GUI Implementation
The next step in developing the mail manager program is to build a GUI interface.
The hierarchy of folders is represented by a tree structure using the JTree class in
Swing. The mail in each folder is displayed using the generic table discussed in the
previous section. A screen shot of the GUI interface is shown in Figure 10.14. The
object Adapter pattern is used to adapt the Mail to TableEntry. The design is shown
in Figure 10.15.
The MailEntry class is the adapter, the Mail class is the adaptee, and the
TableEntry interface is the target.

package mail .gui;


import java. util . Date;
import java. util.Comparator;
import adapter .TableEntry ;
import mail . •;

Figure 10. 15 1 1 •
TableEntry
Table Table.ListTableModel
A design of the
GUI interface for
• MailEntry
the mall application MailboxItem
using the Adapter items 1
pattern.
1 mail

Mail Mail Folder


1
540 • More Design Patterns

public class HailEntry implements TableEntry {


// position of each column
public static final int PRIORiry_coLUMN = O;
public static final int STATUS_COLUMN = 1;
public static final int FROH_COLUMN = 2;
public static final int RECEIVED_COLUMN = 3;
public static final int SUBJECT_COLUMN = 4;
public static final String[] columnNames = {
"Priority",
"Status",
"From" ,
"Received",
"Subject",
} ;

public static final String[] columnTips = {


"Priority",
"Status " ,
"From" ,
"Received",
"Subject" ,
} ;

publi c static final Comparator[] comparators= {


new HailEntryComparator(PRIORITY_COLUMN),
new MailEntryComparator(STATUS_COLUMN),
new HailEntryComparator(FROH_COLUMN),
new HailEntryComparator(RECEIVED_COLUMN),
new HailEntryComparator(SUBJECT_COLUMN),
};

public static final int O columnWidths = { 50, 50, 100, 150, 200 } ;
public HailEntry(Hai l mai l) {
this .mail= mai l ;
}

public int getColumnCount () {


return columnNames . length;
}

publi c String getColumnName( i nt col) {


i f (col>• 0 U
col< columnNames.length) {
return columnNames[col] ;
}
return null ;
}

publi c Object getColumnValue(int col) {


if (mail !• null &&
col>• 0 &&
col < col umnNames. length) {
10.4 Structural Patterns • 541

switch (col) {
case PRIORITY_COLUHN: return mail . getPriority();
case STATIJS_COLUHN: return mail .getStatus() ;
case FROM_COLUHN: return mail.getFrom();
case RECEIVED_COLUHN: return mail .getDate();
case SUBJECT_COLUHN: return mail .getSubject();
}
}
return null;
}

public String getColumnTip(int col) {


if (col>= 0 &:&:
col< columnTips . length) {
return columnTips[col];
}
return null;
}

public Class getColumnClass(int col) {


if (col== PRIORITY_COLUHN) {
return HailPriority.class;
} else if (col== STATUS_COLUHN) {
return HailStatus.class ;
} else if (col= RECEIVED_COLUHN) {
return Date.class;
} else {
return String . class;
}
}

public Comparator getColumnComparator(int col) {


if (col>= 0 &&
col< comparators.length) {
return comparators[col];
}
return null ;
}

public int getColumnWidth(int col) {


if (col>= 0 &&
col< columnWidths . length) {
return columnWidths[col] ;
}
return -1;
}

protected Mail mail ;


static public class MailEntryComparator i mplements Comparator {

public MailEntryComparator (int col) {


this . col • col ;
}
54 2 • More Design Patterns

public int compare(Object ol, Object o2) {


if (ol != null &&
o2 != null &&
ol instanceof MailEntry &&
o2 instanceof MailEntry) {
MailEntry el= (MailEntry) ol;
MailEntry e2 = (MailEntry) o2;
if (col== PRIORITY_COLUMN) {
return -((Comparable) el .getColumnValue(col)) . compareTo
(e2 .getColumnValue(col)) ;
} else if (col== STATUS_COLUMN) {
return ((Comparable) el.getColumnValue(col)) . compareTo
(e2 . getColumnValue(col)) ;
} else if (col== RECEIVED_COLUMN) {
return ((Date) el . getColumnValue(col)) . compareTo
(e2.getColumnValue(col));
} else {
return ((String) e1.getColumnValue(col)).compareTo
(e2 .getColumnValue(col));
}
}
return O;
}

protected int col;


}

The mail. gui. Main class constructs the GUI interface using a JTree object
for the folder hierarchy and a Table object for displaying the mail in the current
folder.

Clallaail..
package mail .gui;
import java. awt .Dimension;
import java.awt .Toolkit ;
i mport j ava.util .•;
i mport javax . swing .• ;
i mport javax . swing .tree . • ;
i mport j avax .swing.event.•;
import mail . • ;
i mport adapter . • ;
public class Mai n {
public static f i nal int INITIAL_FRAME_WIDTH • 800;
public static final i nt INITIAL_FRAME_HEIGHT • 400 ;
public stat ic void mai n(String[] args ) {
Mai lFol der i nboxFolder • mail . Main .buildinbox ();
10.4 Structural Patterns • 543

JTree tree• new JTree(buildHailFolderTree(inbox.Folder) );


JSplitPane splitPane • new JSplitPane( ) ;
splitPane.setLeftComponent(new JScrollPane (tree));
splitPane.setRightComponent(new JPanel( ));
tree . addTreeSelectionListener (new MailFolderTreeSelectionListener
(tree, splitPane) );

JFrame frame • new JFrame("Mails" ) ;


frame . setContentPane(splitPane);
frame . setSize (INITIAL_FRAME_WIDTH, INITIAL_FRAM.E_HEIGHT) ;
Dimension screenSize s Toolkit.getDefaultToolkit( ). getScreenSize();
frame . setLocation(screenSize . width / 2 - INITIAL_FRAME_WIDTH / 2,
screenSize .height / 2 - INITIAL_FRAM.E_HEIGHT / 2) ;
frame.setDefaultCloseOperation(WindowConstants . EXIT_ON_CLOSE) ;
frame.setVisible (true ) ;
}

static class MailFolderTreeSelectionListener implements


TreeSelectionListener {

MailFolderTreeSelectionListener (JTree tree, JSplitPane splitPane) {


this.tree• tree;
this . splitPane • splitPane ;
}

public void valueChanged(TreeSelectionEvent ev) {


DefaultMutableTreeNode node=
(DefaultMutableTreeNode) tree . getLastSelectedPathComponent ();
if (node !• null) {
Object item• node.getUserObject ();
if (item instanceof MailFolder) {
splitPane .setRightComponent (new JScrollPane (buildTable
((MailFolder) item)));
}
}
}

JTree tree;
JSplitPane splitPane ;
}

protected static DefaultHutableTreeNode buildHailFolderTree


(HailFolder folder ) {
if (folder !• null) {
DefaultHutableTreeNode root• new DefaultHutableTreeNode (folder);
List subfolders • folder . getSubFolders ();
Iterator iterator • subfolders . iterator () ;
while (iterator . hasNext ()) {
Object item• i terator . next ();
if (item instanceof MailFolder) {
r oot . add (buildHailFolderTree (( MailFolder) item) ) ;
}
}
544 • More Design Patterns

return root;
}
return null;
}

protected static Table buildTable(MailFolder folder) {


if (folder !• null) {
List mails • folder.getMails() ;
List entries • new ArrayList(mails . size());
for (inti• O; i < mails.size(); i++) {
entries . add(new MailEntry((Hail) mails.get(i))) ;
}
return new Table(entries);
}
return null ;
}

• The type-safe enumeration type idiom defines an enumeration type as a class .


Each value of the enumeration type is associated with a descriptive name and is
defined as a constant that references an instance of the class. This idiom ensures
that
• each enumeration type may only have a fixed number of distinct values.
• the enumeration types are type-safe; that is, variables and values of different
enumeration types are not interchangeable.
• the values can be printed with descriptive names.
• The Abstract Factory design pattern is for creating a set of related and compatible
products from several interchangeable product families. The product instances
are not created using the new operator. They are created using a set of methods
defined in a factory class, which represents a product family. The different
factories representing different product families share a common interface-the
abstract factory interface. Therefore, a client can easily switch from one factory,
or product famiJy, to another.
• The Prototype design pattern creates objects by cloning a set of prototypes .
Objects in different product families can be created by using a different set of
prototypes. Using the Prototype pattern, product families can be defined or altered
at run time by choosing a desired prototype. While using the Abstract Factory or
Factory Method patterns, product families are defined in the form of a class and
thus cannot be altered or configured at run time.
• The Builder design pattern separates the construction of a complex object from
the implementation of its parts so that the same construction process can create
complex objects from different implementations of the parts. The Builder design
Further R~adlngs • 54-5

pattern can be used to simplify the construction process by avoiding duplication


of similar code and shortening the code for the construction.
• The Command design pattern encapsulates an action as an object, so that actions
can be passed as parameters, queued, and possibly undone.
• The Adapter design pattern converts the interface of a class into an interface that
clients expect. There are two forms of the Adapter pattern: Class adapter, which
relies on inheritance, and Object adapter, which relies on delegation, or object
composition.

Gamma, E., et al. (1995). Design Patterns-Elements of Reusable Object-Oriented


Software. Addison-Wesley.
Metsker, S. J. (2002). Design Patterns Java Workbook. Addison-Wesley.
Shalloway, A., and J. R. Trott (2001). Design Patterns Explained: A New Perspective
on Object-Oriented Design. Addison-Wesley.
Concurrent Programming

CHAPTER OVERVIEW
In this chapter, we introduce concurrent, or multithreaded, programming. We discuss the
mechanisms that Java provides to support concurrent prog@mming. We also address the
issues of synchronization and cooperation among threads.

THREADS

Concurrent programming is al o known a multithreaded programming. A thread is a


single sequential flow of control within a program. Mo t conventional programming
languages are single-threaded, or sequential. A ingle-threaded program can handle
only one task at any given rnon1ent during it execution. In contrast, a 11111/tithreaded,
or concurrent, program has multiple thread running imultaneously and o may
handle multiple ta k at the same time during it execution. It i not necessary to
have multiproce or y terns to run multithreaded program . Mo t modem operating
systems support multitasking, which allow multithreaded programs to run on single-
proce or y tem on a time- haring ba i .

547
548 • Concurrent Programming

Multithreaded programming offers some important advantages over single-


threaded programming:
■ It is suitable for developing reactive systems, which continuously monitor arrays
of sensors and react to control systems according to the sensor readings. Exam-
ples of reactive systems include autopilot systems, which control modem aircraft
from takeoff to landing, and patient monitoring systems, which monitor patients'
vital signs.
■ It makes applications more responsive to user input. For example, it allows a
GUI application to respond to user input immediately even if the application is
engaged in a time-consuming computation task.
■ It allows a server to handle multiple clients simultaneously.
■ It may take advantage of the availability of multiple processors by executing the
threads on different processors in parallel.
However, multithreaded programming is more difficult than single-threaded pro-
gramming because each thread proceeds independently from the others. The exact
order of execution of different threads is nondeterministic. Interaction and coopera-
tion among different threads often become complicated. Such complications may lead
to safety and liveness problems, which are unique to multithreaded programs. Mul-
tithreaded programs also involve significant overhead, owing to the cost of thread
creation, context switching, and synchronization.
Threads are different from processes. A process is a heavyweight independent
flow that executes concurrently with other processes. Processes are managed by the
operating systems, and there is no shared memory space among different processes.
Different processes can only communicate with one another via interprocess com-
munication channels, such as pipes on Unix platforms. A thread is a lightweight flow
that executes concurrently with other threads within a single process. Java threads are
managed by the Java virtual machine. All threads managed by the same Java virtual
machine share common memory space. Therefore, different threads managed by the
same Java virtual machine can communicate with one another via shared variables
and objects.

11 .1 .1 Creation of Threads

A thread is an instance of the java. lang. Thread class. A Thread object is also
known as an active object. The graphical notation for active objects is shown in
Figure 11.1.
Threads can be created and declared in one of two ways: by directly extend-
ing the j ava. lang . Thread class or by implementing the j ava. lang . Runnable
interface.

Extending the Thread Class


All threads are instances of the Thread class. Therefore, the most straightforward way
of defining a new thread is by directly extending the Thread class. The run () me thod
11.1 Threads • 549

Figure 11.1
Thread Runnable
Graphical notation z;s
for active objects
(i.e., threads).
MyThread MyThread
runO run()

of the Thread class is a hook method, which must be overridden in the subclass. The
run () method defines the body of the thread and is similar to the main () method
of a sequential program. The run () method of a thread is invoked when execution
starts. Execution of a thread ends when the run () method returns. A template for
defining a new thread class is presented in the following program segment:

public class MyThread extends Thread {


public void run() {
II the thread body
}
II other methods and fields
}

To start a new thread defined by MyThread, we have to create an instance of


the MyThread class and invoke the start() method, which indirectly invokes the
run () method:

new MyThread().start();

Conversely, the run () method should not be invoked directly. Doing so would
cause the method to be executed in the thread of the caller, not in a new thread.

EXAMPLE 1 1 • 1 A Simple Infinite Counter

PURPOSE
This example demonstrates thread creation by extending the Thread class.

DESCRIPTION
The infinite counter starts from an initial value, and the counter will be incremented
by a specified amount at a given pace.

SOLUTION
The following is the thread class that implements the infinite counter. It extends the
Thread class. This class defines a thread whose body consists of an infinite loop. It
maintains a counter, which is incremented by the amount of inc in each iteration. Each
iteration of the loop sleep a duration pecified by the field delay in milliseconds.
550 • Concurrent Programming

public class Counter1 extends Thread {


protected int count;
protected int inc;
protected int delay;
public Counter1(int init, int inc, int delay) {
this.count= init;
this.inc• inc;
this.delay• delay;
}

public void run() {


try {
for (;;) {
System.out.print(count +" ");
count+= inc;
sleep(delay);
}
} catch (Interrupted.Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {


new Counter1(0, 1, 33).start();
new Counter1(0, -1, 100).start();
}
}

Two threads are started in the main () method. Both start counting from 0, but in
different directions. The second thread, the one with a negative increment, receives
a longer delay. Note in the following output that the output from the two threads is
interleaved:

venus% java Counter1


0 0 1 2 -1 3 4 5 -2 6 7 8 -3 9 10 -4 11 12 13 -6 14 16 16 -6 17 18
-7 19 20 21 -8 22 23 24 -9 26 26 -10 27 28 -11 29 30 31 -12 32 33
34 -13 35 36 37 -14 38 39 -16 40 41 42 -16 43 44 46 -17 46 47 -18
48 49 60 -19 61 62 -20 53 64 66 -21 66 67 -22 68 59 60 -23 61 62 63
-24 64 65 -25 66 67 68 -26 69 70 -27 71 72 73 -28 74 75 -29 76 77
-30 78 79 80 - 31 81 82 83 -32 84 85 -33 86 87 88 -34 89 90 91 -35
92 93 -36 94 95 96 -37 97 98 -38 99 100 -39 101 102 103 -40 104 105
106 -41 107 108 -42 109 -43 110 111 -44 112 113 114 -45 115 116 -46

Execution of this program actually involves three threads: the thread that executes
the main() method and the twoCounter1 threads created inside the main() method.
The thread that executes the main () method terminates when the main () method
returns from creating the two Counter1 threads. The two Counter1 threads will run
concurrently and indefinitely until they are explicitly killed. ■
11.1 Threads • 551

E X AM P L E 1 1 • 2 Stock Quote Generator

PURPOSE
This is another example that demonstrates thread creation by extending the Thread
class.

DESCRIPTION
This program generates a series of quotes for the price of a given stock by simulating
the fluctuation of stock prices.
SOLUTION
The foUowing class implements an infinite loop that generates stock quotes. It sim-
ulates the fluctuation of stock prices using a random number generation function
Math. random () .

public class Quote extends Thread {


protected int value;
public Quote(int init) {
value= init;
}

public void run() {


try {
for(;;) {
System.out.println(value);
value+= (Math.random() - 0.4) * (10.0 * Hath.random());
sleep(l00);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[J args) {


new Quote(l00).start();
}
}

The program is started with the initial price set at 100. The following is the output:

venusX java Quote


100
99
97
98
99
99
100
100
55 2 ■ Concurrent Programming

100
101
104
103
102
101

Implementing the Runnable Interface
Defining threads by directly extending the Thread class is only one of the options.
Sometimes, however, it may be impossible to use this option, because Java only
supports single inheritance among classes. For example, applets are required to
extend the class j ava. applet. Applet. Thus applets cannot also extend the Thread
class at the same time. Java provides an alternative means of defining threads-by
implementing the Runnable interface.
The Runnable interface is simple. It consists of a single method, run () :

public interface Runnable {


public abstract void run();
}

A template for defining a new thread class by implementing the Runnable


interface is shown in the following code fragment. As in the first approach, the run ()
method defines the body of the thread.

public class HyThread extends AnotherClass implements Runnable {


public void run() {
II the thread body
}

II other methods and fields


}

To start a new thread defined in this way, we must first create an instance of
MyThread. We use the MyThread instance to create an instance of Thread and then
invoke the start() method:

new Thread(new MyThread()) . start();

EXAM PLE 1 1 . 3 A Simple Infinite Counter 11

PURPOSE
This example demonstrates thread creation by implementation of the Runnable
interface.

DESCRIPTION
This class defines a thread that behaves the same as the one in Example 11 .1.
11.1 Th~ads • 553

SOLUTION

public class Counter2 implements Runnable {


protected int count;
protected int inc;
protected int delay;
public Counter2(int init, int inc, int delay) {
this.count= init;
this . inc= inc;
this.delay= delay;
}

public void run() {


try {
for (;;) {
System . out .print(count + 11 ");

count+= inc;
Thread .sleep (delay);
}
} catch (InterruptedException e) {
e .printStackTrace() ;
}
}

public static void main(StringO args) {


new Tbread(new Counter2(0, 1, 33)).start ();
new Thread(new Counter2(0, -1, 100)) . start( );
}
}

The following is the output:


venus¾ java Counter2
0 0 1 2 -1 3 4 5 -2 6 7 8 -3 9 10 -4 11 12 13 -5 14 15 16 -6 17 18
-7 19 20 21 -8 22 23 24 -9 25 26 -10 27 28 -11 29 30 31 -12 32 33
34 -13 35 36 -14 37 38 39 -15 40 41 42 -16 43 44 45 -17 46 47 -18
48 49 50 -19 51 52 -20 53 54 55 -21 56 57 -22 58 59 60 -23 61 62 63
-24 64 65 -25 66 67 68 -26 69 70 -27 71 72 -28 73 74 -29 75 76 77
-30 78 79 -31 80 81 82 -32 83 84 -33 85 86 87 -34 88 89 90 -35 91
92 -36 93 94 95 -37 96 97 -38 98 99 -39 100 101 102 -40 103 104 105
-41 106 107 - 42 108 109 110 -43 111 112 -44 113 114 115 -45 116 117

11.1. 2 Controlling Threads

Programmers have only high-level control of thread execution by controlling the states
of the life cycle of threads. The execution of thread i ultimately controlled by the
Java virtual machine. Programmer may also influence the execution of threads by
manipulating the priorities of threads.
554 • Concurrent Programming

The Life Cycle of a Thread


Toe life cycle of a thread is shown in Figure 11.2 on a state chart. A thread can be in
one of the following states:
New A thread is in the New state after its creation (that is, new MyThread())
and before the start O method is invoked.
Alive When the start O method is invoked on a thread, it enters the Alive
state. The run () method is invoked implicitly, and the execution of the
thread begins. The Alive state has two substates:
Runnable Threads in the Runnable state are ready to run. Threads
in this state may be running or waiting for their tum to run.
Blocked A thread in the Blocked state is not ready to run. It is
blocked until a certain event happens, at which time it may become
runnable.
Dead A thread enters the Dead state when the run () method returns. A dead
thread cannot be restarted.
The methods of the Thread class for controlling threads are summarized in
Table 11.1. The wait(), notify(), and notifyAll() methods defined in the
Object class can also affect the states of threads. Invoking the wait () method on an
object will cause the thread to be blocked until either the notify() ornotifyAll ()
method is invoked on the same object, at which time the thread will be returned to the
Runnable state. The wait(), notify(), and notify All() methods are discussed
in more detail in Section 11.2.2 [p. 564].

Figure 11.2
Alive yield()
The life cycle of Blocked
threads. wait()
Wait to be Not
notified interrupted
notify() I
notifyAll()
join()
Wait for target interrupt 0
to finish
Target finish
Dead
sleep()
Sleeping Interrupted
run() returns
Time out

interrupt()
/throws InterruptedException
11.1 Threads • 555

Methods of j ava. lang. Thread Class

Method Description

start() The thread should be in the New state. The start ()


method causes the thread to enter the Alive state and start
execution.
sleep O The thread should be in the Runnable state. The sleep()
method causes the thread to enter the Blocked state and
sleep a given amount of time. It will be awakened when
the specified duration of time expires and returned to the
Runnable state.
join O The thread should be in the Runnable state. The join()
method causes the thread to enter the Blocked state and
wait for another thread to finish, at which time it will be
returned to the Runnable state.
yield O The thread should be in the Runnable state and will
remain in the Runnable state. The yield() method gives
other runnable threads an opportunity to run.
interrupt O If the thread is in the Runnable state, the interrupted
flag will be set If the thread is in the Blocked state.
it is awakened and enters the Runnable state, and an
Interrupted.Exception is thrown.
i sAl i ve () Returns true if the thread is in the Alive state.
islnterrupted () Returns true if the interrupted flag is set.

Thread Priority and Scheduling


The Java virtual machine implement a rather simple cheduling trategy to determine
which of the runnable threads should be running. It is based on the priority of each
runnable thread. Each thread contains a priority attribute, which i an integer value
assigned when the thread is created. By default, a new thread has the same priority
as the one that create it. The priority of a thread may be changed during its lifetime.
The Java virtual machine will elect the runnable threads with the highest priority
for execution. If more than one rnnnable thread has the same highest priority, one
of them will be elected arbitrarily. In other word , the Java virtual machine is not
required to guarantee fairn ess. A thre~d of higher_prio~ty will also p~eempt a thre_ad
of lower priority. In other word , while a thread 1s bemg executed, 1f a thread with
higher priority become rnnnable, the current thread will stop running (but remain
556 • Concurrent Programming

runnable) and the higher priority thread will start running. A thread that is currently
running relinquishes control when one of the following occurs:

■ Yielding, that is, invoking the yield O method on the thread.


■ Blocking, that is, invoking the sleep O, join O, or wait O method on the
thread.
■ Preempting, when a thread with a higher priority becomes runnable.
■ Switching, when its time-slice has expired.

The following guideline is an important constraint on the use of priorities:

Design Guideline Thread Priority


Use priorities only to tune the performance of programs. The correctness of programs
should not depend on the priorities of the threads involved.

11.1 THREAD SAFETY AND LIVENESS

Safety properties are conditions that should hold throughout the lifetime of a program.
They stipulate that nothing bad should ever happen. An important safety property is
the consistency of object states. While an object is being modified, it may go through
a series of intermediate states that are inconsistent or invalid. If the thread that is
modifying the object is interrupted, it may leave the object in an inconsistent state.
When another thread tries to access an object in an inconsistent state, this action
may lead to incorrect, perhaps disastrous, behavior. Let us consider an example of
a simplified bank account class. The withdraw () method attempts to withdraw a
specified amount of money from an account. It succeeds when the withdrawal amount
is less than or equal to the balance in the account.

public class Account {


II . . .
public boolean vithdraw(long amount) {
i f (amount<• balance) {
long nevBalance ~ balance - amount;
balance • nevBalance ;
return true;
} else {
ret urn false;
}
}
private long balance;
}
11.2 Thread Safety and Uvcncss • 551

This implementation is valid when used in single-thread programs. However,


this class cannot safely be used with multithreaded programs. Consider the following
scenario:
Assume that the initial balance is $1,000,000. 1\vo withdrawal requests of $1 ,000,000
each are issued almost simultaneously, and they are executed on separate threads.

The following is a plausible, although highly unlikely, sequence of events, which


would lead to both withdrawal requests being successful, albeit incorrect:

Balance Withdrawal 1 Withdrawal 2


1000000 amount <= balance
1000000 amount <= balance
1000000 newBalance =
1000000 newBalance =
0 balance=
0 balance=
0 return true ;
0 return true;

This problem, which is common in multithreaded programs, is known as a race


hazard or race condition. A class is said to be thread-safe if it ensures the consistency
of the states of the objects and the results of method invocations upon these objects
in the presence of multiple threads. As we have demonstrated, the Account class is
not thread-safe.
To maintain the consistency of object states and the results of method invocations,
more than one thread must be prevented from simultaneously entering certain program
regions, known as critical regions, which are segments of code that should be executed
by only one thread at a time on a given object. Java provides a synchronization
mechanism to ensure that, while a thread is executing the statements in a critical
region, no other threads can execute statements in the same critical region at the same
time on a given object.

11.2.1 Synchronization

An operation that cannot be interrupted is known as an atomic operation. In Java the


reading and assignment of variables of primitive types, except long and double. are
atomic. All other operations should be explicitly synchronized to ensure atomicity.
Synchronization can be applied to methods or a block of tatements. A synchro-
nized instance method can be declared as in the following code segment:
class MyClass {
synchronized void aMethod( ) {
(do something)
}
}
558 • Concurrent Programming

In this case, the entire body of the method is the critical region. A synchronized
statement takes the following form:
synchronized(exp) {
(do something)
}

where the expression exp must be of reference type. The statements enclosed in the
synchronized block comprise the critical region.
The synchronization mechanism is implemented by associating each object with
a lock. A thread must obtain exclusive possession of the appropriate lock before
entering the critical region.
■ For a synchronized instance method, the lock associated with the receiving object
this is used.
■ For a synchronized statement, the lock associated with the result of the expression
exp is used.
The lock is released when the thread leaves the critical region. The lock may also be
released temporarily before leaving the critical region, when the wait () method is
invoked (see Section 11.2.2 [p. 564]).
The synchronized method in the following code fragment:
class HyClass {
synchronized void aMethod() {
(do something)
}

is equivalent to the following synchronized statement:


class HyClass {
void aMethod() {
synchronized(this) {
(do something)
}
}
}

As only one thread can have exclusive possession of the lock of a critical region,
only one thread at a time can execute the statements in the critical region. Moreover,
different critical regions may share the same lock. For example, all the synchronized
instance methods use the lock associated with the receiving object. Consider the
following example:
public class A {
synchronized void ml O { . . . }
synchronized void m2 0 { . . . }
void m3 () { . . . } // unsynchronized
}
11.2 Thread Safety and Liveness • 559

Given an instance a of class A, when one thread is executing a. m1 0 , another


thread will be prohibited from executing a. m1 () or a . m2 (). A synchronized method
is allowed to invoke another synchronized method of the same class because the
invocation will be on the same thread. In the preceding example, m2 0 may be invoked
inside the body of method ml() . A synchronized method may execute concurrently
with unsynchronized methods on the same object. In the preceding example, when
one thread is executing a . ml () , another thread may execute a. m3 () concurrently.

EXAM P L E 1 1 . 4 Bounded Queue (Sequential Version)

PURPOSE
This example shows a simple bounded queue implementation that is not thread-safe.
Therefore, it is only suitable for sequential, that is, single-threaded, applications.

DESCRIPTION
A bounded queue is a first-in, first-out queue with a fixed capacity.

SOLUTION
The following program for the BoundedQueue class is implemented with a circular
array. The capacity of the queue is specified by the argument to the constructor. The
capacity of the queue may not be changed. The methods of the BoundedQueue clas
are summarized in the following table:

Method Description

isEmpty () Returns true if the queue is empty


isFull () Returns true if the queue is full
get Count() Returns the number of elements in the queue
put() Inserts an element at the end of the queue
get () Removes the element at the head of the queue and return. the
element

uentlal version)
public class BoundedQueue {
protected Object rep[];
protected int front= O;
protected int back= -1;
protected int size= 0;
protected int count= O;
560 • Concurrent Programming

public BoundedQueue(int size) {


if (size> 0) {
this . size • size;
rep= new Object[size] i
back• size - 1;
}
}

public boolean isEmpty() {


return (count=• O);
}

public boolean isFull() {


return (count • = size) ;
}

public int getCount() {


return count;
}

public void put(Object e) {


if (e != null ll !isFull()) {
back++;
if (back>= size)
back= O;
rep [back] = e ;
count++;
}
}

public Object get() {


Object result= null;
if (!isEmpty()) {
result= rep[front];
rep [front] = null;
front++;
if (front>• size)
front= O;
count--;
}
return result;
}

public static void main(String args[]) {


BoundedQueue queue• new BoundedQueue(10) ;
for (inti• O; !queue.isFull(); i++) {
queue.put(new Integer(i));
System . out.println("put : "+i);
}
while (!queue . isEmpty()) {
System. out . println ("get : "+queue. get()) ;
}
}
}
11.2 Thread Safety and Uvenas • 561

This implementation of the bounded queue has two shortcomings. The put ()
method ignores the new element when the queue is full, and the get () method returns
~ull when the queue is empty. When used with sequential programs, however, this
unplementation is reasonable.
The main () method carries out a simple test of the BoundedQueue class, giving
the following output:
put: 0
put : 1
put: 2
put: 3
put: 4
put: 5
put : 6
put : 7
put: 8
put : 9
get: 0
get : 1
get : 2
get : 3
get: 4
get: 5
get : 6
get : 7
get: 8
get : 9 ■

The result in Example 11.4 is exactly what we expected. However, the sequential
version of the BoundedQueue class is not thread-safe. An easy way to make a class
thread-safe is to synchronize all the methods of the class.

EXAM P L E 1 1 . 5 Bounded Queue (Fully Synchronized Version)

PURPOSE
This example demonstrates the use of synchronization to ensure thread afety.

SOLUTION
The SyncBoundedQueue clas is identical to the the BoundedQueue in Exam-
ple 11.4, except that it i fully synchronized and thread- afe.

public class SyncBoundedQueue extends BoundedQueue {


public SyncBoundedQueue(int size) {
super(size);
}
562 • Concurrent Programming

synchronized public boolean isEmpty() {


return super .isEmpty();
}

synchronized public boolean isFull() {


return super.isFull();
}

synchronized public int getCount() {


return super .getCount();
}

synchronized public void put(Object e) {


super.put(e);
}

synchronized public Object get() {


return super.get();
}

{Method main() for testing on page 563)


}

A typical use of the SyncBoundedQueue class is to serve as a buffer between a


producer and a consumer, both of which are threads. The producer produces items and
puts them in the bounded queue, and the consumer retrieves items from the bounded
queue and consumes them. The relationships among the producer, the consumer, and
the bounded queue are shown in Figure 11.3.


~
• '
.
I • ~
'
.
t' ' •
. .

public class Producer extends Thread {


protected BoundedQueue queue;
protected int n;
public Producer(BoundedQueue queue, int n) {
this . queues queue;
this .n s n;
}

Figure 11.3
Thread

The producer,
consumer, and
bounded queue. Producer Consumer

SyncBoundedQueue
i ~ -~ -=:-------------
11.2 Thread Safety and Uvcncss • 563

public void run() {


for (inti• O; i < n; i++) {
queue.put(new Integer(i)) ;
System.out.println("produce : 11 + i);
try {
sleep((int)(Math . random() • 100));
} catch (InterruptedException e) {
e .printStackTrace();
}
}
}

public class Consumer extends Thread {


protected BoundedQueue queue;
protected int n;
public Consumer(BoundedQueue queue, int n) {
this . queue= queue;
this.n = n;
}

public void run() {


for (inti= O; i < n; i++) {
Object obj= queue . get( ) ;
if (obj != null)
System.out.println("\tconsume : "+obj );
try {
sleep((int) (Math . random () • 400));
} catch (InterruptedException e) {
e. printStackTrace () ;
}
}
}

The main () method of SyncBoundedQueue first creates a bounded queue of


size 5. It then creates and starts two threads: one for the producer and one for the
consumer.

public static void mai n (Stri ng args O) {


SyncBoundedQueue queue • new SyncBoundedQueue (S);
new Producer (queue, 15) . start O; // produce 15 Items
new Consumer (queue , 10) . start () i // consume 10 Items
}
564 • Concurrent Programming

The following is the output:

produce: 0
consume : 0
produce: 1
produce : 2
produce: 3
produce : 4
produce : 5
consume : 1
produce : 6
produce : 7
consume: 2
produce : 8
produce : 9
produce : 10
produce : 11
consume: 3
produce : 12
produce: 13
produce : 14
consume: 4
consume: 5
consume: 6
consume : 8
consume: 12

Note that the producer produces items faster than the consumer consumes the
items. This implementation of the bounded queue causes some of the items to be lost.

11.2.2 Cooperation Among Threads

Synchronization ensures the mutual exclusion of two or more threads in the critical
regions, but it does not address cooperation among threads. The SyncBoundedQueue
in the preceding example is thread-safe. However, the producer and the consumer
threads are not cooperating very well, and this lack of cooperation leads to a loss of
items in the queue. The producer and the consumer should cooperate in the following
ways:

• When the producer attempts to put a new item into the queue while the queue is
full, it should wait for the consumer to consume some of the items in the queue,
making room for the new item.
• When the consumer attempts to retrieve an item from the queue while the queue
is empty, it should wait for the producer to produce items and put them in the
queue.

Thread cooperation is a requirement in many applications and can be accom·


plished with guarded suspension. A guard is the precondition for a certain action tc
11.2 Thread Safety and Liveness • 565

TAILI 11-,I
Thread-Controlling Methods

Method Description

wait O The current thread is temporariJy blocked and is placed in


the wait queue associated with the receiving object. The lock
associated with the receiving object is temporarily released. The
thread will resume execution when it is awakened by notify()
or notifyAll () .
notify O One of the threads in the wait queue associated with the receiving
object will be awakened and removed from the wait queue. The
awakened thread must reobtain the lock before it can resume at
the point immediately following the invocation of the wait()
method.
notifyAll() This method is the same as the notify() method, except that all
threads that are in the wait queue associated with the receiving
object will be awakened and removed from the wait queue.

complete successfully. Guarded suspension is a requirement for threads to cooperate


in the following way:
■ Before a method is executed, the guard is tested.
■ Execution continues only when the guard is true, ensuring the successful com-
pletion of the method invocation.
■ Execution is temporarily suspended until the guard become true, at which time
execution may continue.
Guarded suspension can be implemented by u ing the wait (), notify O. and
notifyAll() methods of the Object class. The wait() method hould be invoked
when a thread is temporarily unable to continue and we want to let other thread
proceed. The notify() or notifyAll O method hould be invoked\ hen we want
a thread to notify other threads that they may proceed. The e three methods can be
invoked only by a thread that currently own the lock of the receiving object. The
methods are summarized in Table 11 .2.

EX AM p LE 1 1 . 6 Bounded Queue with Guarded Suspension

PURPOSE
Thi example demon trate the u e of the wait () and notify () methods to imple-
ment guarded uspen ion and en ure cooperation among threads.
566 ■ Concurrent Programming

DESCRIPTION
This version of bounded queue supports the cooperation between the producer and
the consumer in the way described previously.

SOLUTION
The BoundedQueueWithGuard class extends the BoundedQueue class. It is fully
synchronized. The put () and get () methods are overridden to support cooperation
between the producer and the consumer.
~ :--r-rn,·~~ ,-~:~-. '~';"~Y'"-~~";,., ..·.
"-:a............-.:..... . . , . . . . ~ \ , , , , , . _ ~ ~ ,...,...,.....~ f.,
: ·: ·,·:
~
.. ....·~- .l

public class BoundedQueueWithGuard extends BoundedQueue {


public BoundedQueueWithGuard(int size) {
super(size);
}

synchronized public boolean isEmptyO- {


return super.isEmpty();
}

synchronized public boolean isFull() {


return super. isFull () ;
}

synchronized public int getCount() {


return super.getCount();
}

(Method put () on page 566)


(Method get () on page 567)
(Method main() for testing on page 568)
}

The guard of the put O method ensures that the queue is notfull, and the guard of
the get () method ensures that the queue is not empty. The put () method invokes the
wait O method to suspend the producer thread temporarily when the queue is full.
Similarly, the get O method invokes the wait () method to suspend the consumer
thread temporarily when the queue is empty. Both methods invoke the notify 0
method at the end to wake up a suspended thread, if any.

synchronized void put(Object obj) {


try {
while (isFull()) {
wait();
}
} catch (InterruptedException e) {
e .printStackTrace();
}
super.put(obj);
notify();
}
11.2 Thread Safety and Liveness • 56 7

, , - . .
'
0 I • ' • •
' '
- ••
. ••
.
synchronized Object get() {
try {
while (isEmpty()) {
wait();
}
} catch (Interrupted.Exception e) {
e . printStackTrace();
}
Object result= super.get();
notify();
return result;
}

The interaction between the put () and get () methods, which are invoked by
the producer and consumer threads, respectively, is best illustrated by two scenarios:

Scenario A:
The producer produces items faster than the consumer consumes them.

1. The producer thread acquires the lock associated with the queue and invokes the
put() method, but the queue is full.
2. The wait () method is invoked, and the producer thread is suspended. The lock
associated with the queue is temporarily released by the producer thread.
3. The consumer thread acquires the lock associated with the queue and invokes the
get () method. The queue must not be empty; indeed, it is full. So the guard of
the get () method returns true, and one item is removed from the queue.
4. The notify () method is invoked to awaken the suspended producer thread. The
consumer thread completes the invocation of the get () method and releases the
lock associated with the queue.
5. The producer thread is awakened in the put () method and reacquires the lock
associated with the queue. The guard of the put () method is true this time, and
execution resumes where it left off.

Scenario B:
The producer produces items more slowly than the consumer consumes them.

1. Toe consumer thread acquire the lock associated with the queue and invokes the
get () method when the queue i empty.
2. The wait () method is invoked, and the consumer thread is suspended. The lock
associated with the queue is temporarily released by the consumer thread.

3. The producer thread acquire the lock a~sociate~ ~th the queue and invokes the
ut () method. The queue is not full; mdeed, 1t 1s empty. So the guard of the
:ut () method returns true, and one item is inserted into the queue.
568 • Concurrent Progranvning

4. Toe notify() method is invoked to awaken the suspended consumer thread.


Toe producer thread completes the invocation of the put () method and releases
the lock associated with the queue.
S. The consumer thread is awakened in the get () method and reacquires the lock
associated with the queue. The guard of the get () method returns true this
time, and execution resumes where it left off.

The main() method performs a simple test of the bounded queue, with the
producer and the consumer running on separate threads.

public static void main(String args[]) {


BoundedQueueWithGuard queue=
new BoundedQueueWithGuard(5);
new Producer (queue, 15) . start() ; // produce 15 items
new Consumer(queue, 15). start(); // consume 15 items
}

The following is the output:

produce : 0
consume: 0
produce : 1
produce: 2
consume: 1
produce: 3
consume: 2
produce: 4
produce: 5
consume: 3
produce : 6
produce : 7
produce: 8
consume : 4
produce: 9
consume: 5
produce: 10
consume : 6
produce: 11
consume: 7
produce : 12
consume: 8
produce: 13
consume: 9
produce: 14
consume: 10
consume: 11
consume: 12
consume: 13
consume: 14

11.2 Thread Safety and Liveness • 569

11 ·2 ·3 Liveness Failures

liveness refers to desirable conditions that will come about during the lifetime of
a program. In other words, liveness properties stipulate that something positive will
eventually happen. For example, common liveness properties include the following:
(I) A certain task will be completed eventually; (2) a thread should always respond
to user input until the thread is terminated; and (3) the status of certain systems must
be displayed and updated constantly.
Safety properties can be ensured locally, but liveness properties are context
dependent. Ensuring liveness properties is a much more difficult task than ensuring
safety properties. Some common types of liveness failures are contention, dormancy,
deadlock, and premature termination.

Contention
Contention (also known as starvation or indefinite postponement) occurs when a
runnable thread never gets a chance to run. This can occur when there are always
one or more runnable threads with higher priorities or when there is a runnable thread
with the same priority but it never yields.
Let us consider the run () method of a typical animation applet:
public class AnAnimation {
protected Thread animationThread;
public void run() {
while (Thread.currentThread() == animationThread) {
repaint ();
try {
Thread . currentThread(). sleep (delay);
} catch (InterruptedException e){
e.printStackTrace( );
}
}
}
II other fields and methods . . .
}

The repaint() method draws the current frame on a separate thread having the
same priority as the animation thread. If the sleep O method were not invoked, the
repaint thread would never get a chance to run and the animation would appear as a
blank area.
To avoid contention, the thread with the highest priority must periodically invoke
the sleep() or yield O method to provide other cooperating threads having the
ame or lower prioritie with a chance to run.

Dormancy
Dormancy occurs when a thread that i blocked never becomes runnable. A com-
mon cause of dormancy is that a thread blocked by an invocation of the wait()
method i never awakened by notify O or notify All() . For example, in the
5 70 • Concurrent Programming

BoundedQueueWithGuard class [p. 566], if the invocation of the notify () method


in either the put () or get () method were omitted, the consumer and the producer
threads could both become donnant. If the invocation of notify() in the put()
method were omitted, the following scenario could occur:

t. The queue becomes empty. The consumer thread invokes the wait () method
inside the get () method and becomes blocked.
2. Toe producer thread puts items into the queue, but the consumer thread is not
awakened because the invocation of the notify() method is omitted. The
consumer thread remains blocked.
3. The queue becomes full. The producer thread invokes the wait () method inside
the put () method and becomes blocked.

At this point, both the consumer and producer threads are blocked. If there are no
other threads to awaken them, they become dormant.
To avoid dormancy caused by waiting, be sure that each thread that can be blocked
by the wait () method will be awakened by another thread that invokes the notify ()
or notifyAll() method. When in doubt, use the notify All() method, which
awakens all the waiting threads and makes the system less dormancy-prone.

Deadlock
Deadlock occurs when two or more threads block each other and none can make
progress. It is usually caused by two or more threads competing for multiple shared
resources and each thread requiring exclusive possession of those resources simul-
taneously. Consider the following oversimplified example. The DiskDri ve class
represents a disk drive, and the copy () method copies a file from this disk drive
to another drive.

public class DiskDrive {


public synchronized void copy(DiskDrive destination, String filename) {
InputStream in• openFile(filename);
destination.writeFile(filename, in);
}

public synchronized InputStream openFile(String filename) {


II . ..
}

public synchronized void writeFile(String filename, InputStream in) {


II copy the contents read from the parameter Into a file
}
}

To maintain the integrity of the file being copied, all the methods are synchro-
nized. The copy () method needs to acquire the locks associated with both the source
and destination disk drives. Let us assume that we have two disk drives, c and d . On
11.3 Design CaK Study-Tic-Tac-Toe Game • 571

one thread we do c . copy(d, file1) , and on another thread we do d . copy(c,


f ile2). The following is a possible scenario:

Thread 1: c. copy(d, f ile1) Thread 2: d.copy(c, file2)


Invoke c . copy ( . . . )
Obtain lock of c
Invoked.copy( . . . )
Obtain lock of d
Invoke c . openFile ( . )
Invoke d . openFile ( . . . )
Invoke d . wri teFile ( . . . )
Unable to obtain lock of d
Invoke c. wri teFile ( . . . )
Unable to obtain lock of c

Neither thread is able to obtain the lock needed to proceed, so they are in a
"deadlock."
In general, detecting and preventing deadlock is difficult. An extensive body of
work on deadlock detection and prevention is available. A more detailed discussion
on this topic is presented in Lea [2000].

Premature Termination
Premature termination occurs when a thread is terminated before it should be, im-
peding the progress of other threads. For example, in the BoundedQueueWithGuard
class [p. 566], premature termination of either the producer or the consumer th.read
causes the other thread to be blocked forever (that is, to become dormant).

DESIGN CASE STUDY-TIC-TAC-TOE GAME

In this section, we will develop a imple game: tic-tac-toe. Here we implement it as


a two-player game on a 3 x 3 board, but it is designed o that it can be extended to
a k-player (k > 2) game on an m x n board. The program i multithreaded, and each
player is represented as a separate thread. 1\vo types of players are implemented:
■ A human player, which wait for a human to make move by clicking the mouse
on the game board.
■ A machine player, which automatically generate move (not neces arily good
ones).
The game can be played by two human player , two machine player , or a human and
a machine player. The tructure of the program is hown in Figure 11.4.
572 • Concurrent Programming

Figure 11.4 Thread Applet Canvas

Structure of the tic-


tac-toe game. 1 1 r--._____,
* Game ------I Board

Human Player MachinePlayer

11.3.1 The Game Board


The class Board represents the game board.
r , - •• •., "-• •

._;,.'7',..,__..:_· •..... •:;.~

import java.awt.•;
import java.awt.event.•;
public class Board extends Canvas {
protected Game game;
protected int row, col;
protected int rowHeight, colWidth;
protected int board O [] ;
protected int maxHoves;
protected int moves ;
protected boolean over= false;
protected int winner= O;
(Constructor Board() on page 572}
(Accessors of fields on page 573}
(Method paint() on page 574}
(Method recordMove () on page 575}
(Method isLegalMove () on page 575}
(Method checkGame ( ) on page 576}
(Auxiliary methods checkRowO, checkColO , and checkDiagonalO on page 576}
(Inner class MouseHandler on page 577}
}

The fields of the Board class are described in Table 11 .3. The constructor of the
Board class simply initializes the fields.

public Board(Game game, int row, int col) {


this .game • game ;
this.row• row;
this.col • col;
maxMoves • row * col;
11.3 Design case Study-Tic-Tac-Toe Game • 573

moves • O;
board• new int[row] [col];
addMouseListener(new MouseHandler());
}

The following accessors of the Board class return the values of the corresponding
fields:

public int getRow() {


return row;
}

public int getCol() {


return col ;
}

public boolean i sOver() {


return over ;
}

public int getWi nner () {


return winner;
}

Fields of the Board Class

Methods Description

game The Game object with which this game board is associated
row, col The number of rows and columns on the game board
rowHeight The height of each row
col Width The width of each column
board The two-dimensional array that records the state of the game
board, each cell containing the ID of the player who occupies
the cell and the player ID starting at 1
moves The number of moves that have been made
maxMoves The maximum number of move allowed in the game
over The boolean flag that is set to true if the game i over
wi nner The ID of the player who won the game, which is O while the
game is in progre
574 • Concurrent Programming

Figure 11.5

The tic-tac-toe
game board.

X
0 X
0
Player 1's tum
Applet started.

The paint () method paints the game board. A screen shot of the game board is
shown in Figure 11.5. The paint () method first draws the grid. It then draws an X
for each cell occupied by player 1 and an O for each cell occupied by player 2.
~- ~
\,. ,
~
I
: -~~-~-~r--:~~~7 .-r ,· ):~;-----~:1~'!";):r~~~;.v-.f");
./ .. • -• .,-~-- .,--,•~-~-r ..,.,. ~ · - ..... - } •~..,...., - : '

public void paint(Graphics g) {


Dimension d = getSize();
rowHeight • d.height / row;
colWidth = d.width / col;
inti, j;
for (i = 1; i < row; i++)
g.drawLine(O, i * rowHeight, d.width, i * rowHeight);
for (j = 1; j < col; j++)
g.drawLine(j * colWidth, 0, j * colWidth, d.height);
Font font• new Font( 11 Sans-serif 11 , Font.BOLD, 48) ;
g.setFont(font);
for (i • O; i < row; i++) {
for (j • O; j < col; j++) {
if (board [i] [j] ! • 0) {
int x • i * colWidth + 12;
int y • j * rowHeight + 60;
switch (board[i] [j]) {
case 1:
g.setColor(Color . red);
g .drawString("X", x, y) ;
break;
11.3 Design Case Study-Tic-Tac-Toe Game • 5 75

case 2:
g.setColor(Color.blue);
g.drawString("O", x, y);
break;
}
}
}
}
}

The recordMove () method records a move made by the specified player. A


~ove is an instance of the following Move class, which indicates the cell the player
rntends to occupy:

public class Move {


public int row, col;
}

Before a move is recorded, the recordMove () method checks to determine


whether the move is a legal move. It returns true if the move is legal and false
if the move is iJlegal.

'Die recordllove() mietlel.CIC'


public boolean recordMove(Move move, int playerid) {
if (isLegalHove(move) ) {
moves++;
board[move .row] [move.col]= playerid;
repaint();
return true;
} else {
return false;
}
}

The isLegalMove O method returns true if the specified move is legal. A move
is legal if the cell is unoccupied.

public boolean isLegalMove(Move move) {


return (board[move .row] [move.col]= 0);
}

The checkGame () method checks to determine whether the game is over. The
game is over if all the cell on the game board are occupied or one of the players
has won. It depends on three auxiliary method - checkRow O, checkCol (), and
checkDiagonal ()- to detennine whether one of the players ha won.
5 76 • Concurrent Programming

protected void checkGame(int playerld) {


if (moves>= maxMoves) {
over= true;
return ;
}

boolean win= false;


for (inti• O; i < row; i++) {
if (checkRow(playerld, i)) {
win= true;
break;
}
}
if ( !win) {
for (inti= O; i < col ; i++) {
if (checkCol(playerld, i)) {
win= true;
break;
}
}
}
if ( !win) {
win= checkDiagonal(playerld);
}
if (win) {
winner = playerld;
over= true ;
}
}

The auxiliary methods checkRow(), checkCol (), and checkDiagonal ()


check on whether the specified player occupies an entire row, an entire column, or an
entire diagonal.

protected boolean checkRow(int playerid, int row) {


for (inti• O; i < col; i++) {
if (board[row] [i] ! = playerid) {
return false;
}
}
return true;
}

protected boolean checkCol(int playerld, int col) {


for (inti• O; i < row ; i++) {
if (board [i] [col] ! • playerId) {
return false;
}
}
11.3 Dalgn Case Study-Tic-Tac-Toe Gam~ • 577

return true;
}

protected boolean checkDiagonal(int playerid) {


boolean result= true;
for (inti= O; i < row; i++) {
if (board(i] [i] != playerid) {
result• false;
break;
}
}
if (result) {
return true;
}
result= true;
for (inti= O; i < row; i++) {
if (board[i] [col - i - 1] != playerid) {
result= false;
break;
}
}
return result;
}

The inner class MouseHandler receives mouse clicks on the game board, which
indicate moves made by a human player.

1be Inner class oftbe Boucldall


class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent event) {
Point p = event.getPoint () ;
game.getPlayer().selectCell (p.x / colWidth , p .y / rowHeight) ;
}
}

11.3.2 The Game

The Game class extends the Applet cla . It contain a game board and a me age
bar. The field player is an array of players in the game. In thi implementation the
number of players is fixed at 2. The field turn refer to the player ~ ho currently has
the turn (that is, the player who i upposed to make the ne, t move).

Clailhae
import java .awt.•;
import java. applet .Applet;
public class Game extends Applet {
protected Board board;
protected Label messageBar ;
578 • Concurrent Programming

protected Player players[] ;


protected Player turn ;
{Constructor Game() on page 578)
{Method ini t () on page 582)
{Methods getPlayer() , getBoard() , isOver() ,
and displayMessage () on page 578}
{Method recordMove () on page 578)
}

The constructor initializes the fields by creating the board and the message bar.

0-llldDI .... Gueda


public Game O {
players= new Player[2];
board= new Board(this, 3, 3);
messageBar = new Label ( 11 Game begin . 11 ) ;

setLayout(new BorderLayout());
add(board, BorderLayout.CENTER);
add(messageBar, BorderLayout .SOUTH);
}

The getPlayer () method returns the player who has the tum. The get Board ()
method returns the game board. The is Over () method returns true if the game is
over. The displayMessage () method displays a message on the message bar.

public Player getPlayer() {


return turn;
}

public Board getBoard() {


return board ;
}

public boolean isOver() {


return board .isOver();
}

public void displayMessage(String msg) {


messageBar . setText(msg);
}

The record.Move() method records a move made by a player. It returns true


if the move is legal and returns false otherwise. It also displays messages regarding
the outcome of the game.

'Die rec:ordllcwe() .,,... fl.


public boolean recordMove(Move move) {
if (board .recordMove(move, turn .getid())) {
board . checkGame(turn . getid());
11.3 0a1gncascStudy-Tic-Tac-TocGamc • 579

if (is0verO) {
int winner• board.getWinner();
if (winner>= 1) {
displayHessage("Player" +winner+" won . ");
} else {
displayHessage("It's a draw");
}
for (int n • 0; n < players.length; n++) {
players[n].stop();
}
}
return true;
} else {
return false;
}
}

11.3.3 The Players

The Player class is an abstract class that represents players in the game. Different
types of players are represented by different subclasses of this class. The field game
refers to the game the players are in. The field id is the ID of the first player. The
field next refers to the player whose tum follows that of the preceding player. In two-
player games, next refers to the second player. The field turn refers to the player who
currently has the turn. It is this player's turn if turn = this. The turn and next
fields of the player objects are initialized in the init () method of the Game class.

abstract public class Player extends Thread {


protected Game game;
protected int id;
protected Player next;
protected Player turn;
public Player(Game game, int id) {
this.game= game;
this.id= id;
}

public int get!d() {


return id;
}

public synchronized void setNext(Player p) {


next= p;
}
public synchronized void hasTurn () {
turn• this;
game . turn • this ;
notify() ;
}
580 • Concurrent Programming

abstract public Move makeMove();


public void selectCell(int x, int y) {}
public synchronized void run() {
while (!game.isOver()) {
while (turn!• this) {
try {
wait();
}
catch (InterruptedException ex) { return; }
}
game.displayMessage("Player" +id+ "'s turn");
while (true) {
Move move= makeMove();
if (game.recordMove(move)) {
break;
} else {
game . displayMessage("Illegal move!");
}
}
turn= null;
next.hasTurn();
}
}

The getld() method returns the ID of this player. The setNext O method
sets which player is after this player. The has Turn () method is invoked when this
player should have the tum. This method is invoked by the preceding player, and it
awakens this player (see the run () method). The makeMove () method is a hook
method for the subclasses to override. This method should return the next move to be
made by the player. The selectCell () method is invoked by the MouseHandler
of the Board class. It informs the player that a mouse click has been received and
a cell position given. This method is ignored by the MachinePlayer class, but it is
used by the HwnanPlayer class to get the move made by a human.
The run () method is the body of the player thread and is also a template method.
First, it waits until this player gets the tum. Then it invokes the hook method
makeMove () to make a move.
The HwnanPlayer class implements the makeMove () method. It simply waits
for a mouse click on the game board.

public class HumanPlayer extends Player {


protected Move move;
public HumanPlayer(Game game, int id) {
super(game, id);
move= new Move();
}
11.3 Design Case Study-Tic-Tac-Toe Game • 581

synchronized public Move makeMove() {


try {
wait() ;
} catch (InterruptedException e) {
e . printStackTrace( );
}
return move;
}

synchronized public void selectCell(int x , int y) {


move.row= x;
move . col= y;
notify();
}
}

The MachinePlayer also implements the ma.keMove () method. It randomly


generates a move.

c..
public class Machi nePlayer extends Player {
protected int nCells ;
publ ic MachinePlayer(Game game , i nt i d) {
super (game , id);
Board board= game.getBoard ();
nCells = board.getRow() * board .getCol ();
}

publi c Move makeMove () {


try {
Thread. currentThread (). sleep ( lOOO);
} catch (InterruptedExcepti on e ) {}
Move move= new Move ();
Board board = game.getBoard ();
int r ow = game .getBoard () .getRow();
inti= (int )(Math .random() * nCells );
move . row= i / row;
move .col = i X row;
while (!board.isLegalMove (move)) {
i ++;
if ( i >z nCells) {
i = i Y. nCells;
}
move .row= i / row;
move .col= i X r ow;
}
return move;
}
}
582 • Concurrent Programming

The init () method of the Game class initializes the players according to the
parameter type.

public void init() {


String gameType = getParameter("type");
i f (game Type. equals ("human-human")) {
players[O] = new HumanPlayer(this, 1);
players[1] = new HumanPlayer(this, 2);
} else i f (game Type. equals ("machine-machine")) {
players[O] • new MachinePlayer(this, 1);
players[1] = new MachinePlayer(this, 2);
} else {
players[O] = new HumanPlayer(this, 1);
players[1] = new MachinePlayer(this, 2);
}
players[O].start();
players[1].start();
players[O] .setNext(players[1]);
players[1].setNext(players[O]);
players[O] .hasTurn();
}

11.3.4 Idiom: Taking Tums

The Player class implements a behavior that allows the players taking turns to make
moves. This behavior can be generalized by the following idiom that extends to more
than two objects, or players, and is applicable to nongame applications:

Idiom Taking Turns

Category: Behavioral implementation idiom.


Intent: For each participant in a group of objects to take turns in a fixed order
and perfonn a certain task.
Applicability: This idiom can be used to implement k-player games (k > 2), or
other applications that require the participants to take turns.

Each participant is a thread. The following code comprises the implementation:

class Participant extends Thread {


protected Participant next;
protected Participant turn;
Chapter Summary • 583

public synchronized void run() {


while (!isDone()) {
while (turn!= this) {
try {
wait();
}
catch (InterruptedException ex) {return;}
}
II perform an action or make a move
turn= null;
next . has Turn O ;
}
}

public synchronized void hasTurn() {


turn= this;
notify();
}

II other fields and methods


}

If there are n participants and they take turns in the order, Po, p 1, •. . , p,._ 1, they
must be initialized as

Po-next == Pt
Pt .next = P2

Pn - 2 ·next == Pn- 1
Pn- 1· next == Po
To start the process, we do the following:
Po . start() ;
Pt. start O;

Pn - t. start() ;
Po. has Turn O ;

Using the taking-turns idiom, we can extend the two-player game shown here to
multiplayer games. This is left as a project (Project l l.1 ).

■ Concurrent programs, al o known as multithreaded program , are capable of


running several thread imultaneou ly.
■ A thread i a ingle equential flow of control within a program. Threads are dif-
ferent from proce se . A proce i a heavyweight flow that executes concurrently
with other proce e . A thread i a lightweight flow that executes concurrently
with other thread~ within the ame proce .
584 • Concurrent Programming

Threads can be defined by extending the Thread class or by implementing the



Runnable interface.
The life cycle of a thread includes three main states: new, alive, and dead. Threads
• in the alive state can be either runnable or blocked.
The Java virtual machine assigns and maintains a priority for each thread. This
• priority, which is an integer value, should be manipulated by the programmer
only for performance tuning.
• Critical regions are segments of code that should be executed by only one thread at
a time. Race hazards occur when more than one thread executes the statements in
a critical region at the same time. Race hazards could leave objects in inconsistent
or invalid states. Java provides a synchronization mechanism to ensure that, while
a thread is executing the statements in a critical region, no other threads can
execute statements in that same critical region at the same time.
• A guard is the precondition for a certain action to complete successfully. Guarded
suspension is a policy for threads to cooperate in the following way:
I. Before executing a method, the guard is tested.
2. Execution continues only when the guard is true, which ensures successful
completion of the method invocation.
3. Execution is temporarily suspended until the guard becomes true, at which
time execution may continue.
• Liveness refers to desirable conditions that will come about during the lifetime
of a program. Some common types of liveness failures are
contention (also called starvation or indefinite postponement), when a
runnable thread never gets a chance to run.
donnancy, when a thread that is blocked never becomes runnable.
deadlock, when two or more threads block each other and none can progress.
premature tennination , when a thread is terminated before it should be,
impeding the progress of other threads.

f FURTHER READING

Lea, D. (2000). Concurrent Programming in Java-Design Principles and Patterns,


2nd ed. Addison-Wesley.

[ EXERCISES

11.1 Complete the Account class on page 556 getBalance () . All critical regions of the clas
and be sure that il is thread-safe. ll should should be protected with synchronization as
contain methods such as deposit O and needed.
Projects ■ 585

ll.l Implement a thread-safe linked list class based emergency vehicles occasionally, in different
~ the_~inkedlist class in Chapters 6 and 7. directions. Develop a GUI program to animate
Cntical regions of the class should be traffic lights and traffic flows. (Note: Take
protected with synchronization as needed. precautions so that your program does not
ll.J Using the BoundedQueueWithGuard class as deadlock.) Bonus: Add a left-tum signal to
a model, implement a thread-safe Stack class each set of traffic lights.
with guarded suspension. The Stack class 11.3 Develop a Java program that simulates an
should contain the methods pop (), push () , elevator control system for a bank ofm elevators
isFull O, and isEmpty () . Test the Stack of an n-story building. Each floor, except the
class by using classes similar to the Producer top and bottom, has an Up and a Down button.
and Consumer classes presented in this chapter. The top floor has only the Down button. and
the bottom floor has only the Up button. Inside
each elevator is a button for each floor. The goal
of the system is to ensure that
(a) one of the elevators moving upward will
stop at the floor for which the Up button is
11.1 Design and implement a multiplayer tic-tac-toe pushed.
game. (Hint: Use a larger board and design a (b) one of the elevators moving downward will
new scoring system.) stop at the floor for which the Down button
11.2 Develop a Java program that simulates a traffic is pushed.
control system for a four-way intersection. The (c) each elevator must stop at the ith floor if
system controls four sets of traffic lights, facing the button i in this elevator is pushed.
north, east, south, and west. Each set of lights
Develop a GUI program to animate the status
can be in one of the following states: green,
of all the buttons and the movement of the
yellow, and red. The goals of the traffic control
elevators. Two testing modes of the system
system are
should be supported:
• never to give green lights simultaneously
(a) Use independent threads to simulate the
to traffic flows that may collide.
requests of the passengers.
• to avoid unnecessary blocking of the traffic
flows. (b) Manually push the buttons on each floor
• to give green lights to emergency vehicles. and inside each elevator.

For testing purposes, use independent threads


to generate simulated traffic flows, which have
Distributed Computing

CHAPTER OVERVIEW
In this chapter, we first discuss two pure Java mechanisms for distributed computing:
socket-based communication and remote method invocation (RMI). We illustrate the
design and implementation of distributed applications, using a stock ticker application.
We then discuss a Java mechanism, JDBC, for communicating w ith relational databases.
We also briefly discuss a mechanism for interfacing Java applications with non-Java
applications in distributed computing environments: the Common Object Request Broker
Architecture (CORSA). This chapter serves as an introduction to developing distributed
applications in Java.

Today's computing environment are inherently di tributed and heterogeneou . In-


ternet and intranet application often nm on different operating y tern in different
locations. Distributed applications consi t of collaborating component that re ide
and execute on different network ho t . The component re iding on different ho t
do not share storage pace (that i , memory and di ). Exchange of infonnation
among these component ' can take place only via communication connection. '. or
link , among the host . Furthermore, the host can be running different operating
y terns, forming a heterogeneou. network computing environment.

587

588 • Distributed Computing

In this chapter we discuss two Java mechanisms for distributed computing:


Socket-based communication: Sockets are the end points of two-way connections
between two distributed components that communicate with each other. A
connection must be explicitly established by both parties.
Remote method invocation: RMI makes the network "transparent." It allows
distributed components to be manipulated (almost) as if they were all on
the same host. Programmers need not deal with interhost communications
at all. All network communication is handled implicitly by the run-time
environment supporting the remote method invocation.
We also briefly discuss a mechanism for interfacing Java applications with non-
Java applications in distributed computing environments: the Common Object Request
Broker Architecture (CORBA). CORBA is an object-oriented framework that supports
interoperability among objects written in different languages and running on different
platforms. CORBA support is available for most programming languages, including
Java, and platforms used for industrial applications. CORBA is especially u seful in
interfacing applications developed with modern languages, such as Java and C++,
with legacy systems.
This chapter serves only as an introduction to developing distributed applications
in Java.

SOCKET-BASED COMMUNICATION

Sockets are the end points of logical connections between hosts and can be used to
send and receive data. At the application programming level, it is assumed that the
socket connections are reliable; that is, the data sent from one end of the connection
will be received at the other end in the same order and with no loss . Sockets are sup-
ported by most programming languages and platforms in use today. Java applications
can use sockets to communicate with applications written in other languages.

12.1 .1 Server and Client Sockets

There are two kinds of sockets: server sockets and client sockets. A server socket
waits for requests for connections from clients. A client socket can be used to send
and receive data.

Server Sockets
Each server socket listens at a specific port . The port number is necessary to distin-
guish different servers running on the same host, so each server must have a unique
port number.
A server socket must be running on the server host before its clients initiate con-
tact. After the server socket is contacted by a client, a connection can be established,
and a client socket is created for the application running on the server host to com -
municate with the client that initiated the contact.
LIIL1
12.1 Socket-Based Communication • 589

A server socket is an instance of the ServerSocket class and can be created


with one of these constructors:

ServerSocket(int port)
ServerSocket(int port, int backlog)

The parameter port specifies the port number at which the server socket will
be listening to requests from clients. When multiple clients are contacting the same
server socket at the same time, they will be put in a waiting queue and will be processed
in the order in which they are received. The optional parameter backlog specifies
the maximum length of the waiting queue. Server sockets can be created only with
Java apps, not applets. The commonly used methods of the ServerSocket class are
summarized in the following table:

Method Description

accept O Waits for a connection request. The thread that executes the
method will be blocked until a request is received, at which time
the method returns a client socket.
close O Stops waiting for requests from clients.

The following program segment is a typical use of erver sockets:

try {
ServerSocket s = new ServerSocket (port);
while (true) {
Socket incoming = s . accept () ; II obtain a client socket
(Handle a client}
}
} catch (I □Exception e) {
(Handle exception: fail to create a erver ocket}
}

Client Sockets
A client socket i an instance of the Socket cla and can be obtained in two way :
1. On the client side, client ocket can be created by u ing the con tructor
Socket (String host, int port)

The parameter host pecifie the addre of the ho t, and the parameter port
specifie the port number. Client ocket, can be created and u ed by both Java
app and applet .
2. On the erver ide, client ocket are returned by the accept O method of the
ServerSocket cla , after reque t for onnection have been received from
lient .
590 • Distributed Computing

Communication between the server and the client is handled by client sockets
at both ends. Each client socket has an InputStream object for receiving data and
an OutputStream object for sending data. When the InputStream and Output-
Stream objects are used, sending and receiving data between a client and a server
are essentially no different from reading data from and writing data to files . The com-
monly used methods of the Socket class are summarized in the following table:

Method Description

getinputStream () Returns an InputStream object for receiving data


getOutputStream () Returns an OutputStream object for sending data
close O Closes the socket connection

The following program segment illustrates the typical use of client sockets for
sending and receiving text data:
try {
Socket s = new Socket (host, port) ; II create a client socket
PrintWriter out= new PrintWriter(
new OutputStreamWriter(s . get □utputStream()));
BufferedReader in= new BufferedReader(
new InputStreamReader(s.getlnputStream()));
{Send and receive data)
in . close() ;
out . close O ;
s . close() ;
} catch ( ! □Exception e) {
(Handle exception: connection fails)
}

12.1.2 Servers and Clients Using Sockets

In this section, we illustrate client-server programming, using sockets, through a


series of examples. Let us start with a simple server that only echoes the messages it
receives from its client.

EXAMPLE 1 2. 1 A Simple Echo Server

PURPOSE
This example illustrates the basic elements of a server application.
DESCRIPTION
Thfa server echoes each line of text that it receives from its client. The client can
terminate the connection by sending a line that says "BYE."
12.1 Socket-Based Communication • 591

SOLUTION
Typically, a server is supposed to run for a long time. So a server usually contains an
infinite loop of some kind. In this case, each iteration of the loop handles one client.
Thus this server can handle multiple clients sequentially (that is, only one client at a
time), not simultaneously.

import java.io.•;
import java.net.•;
public class EchoServer {
public static void main(StringO args) {
try {
ServerSocket s a new ServerSocket(8008);
while (true) {
Socket incoming= s . accept();
BufferedReader in
= new BufferedReader(new InputStreamReader(
incoming.getinputStream()));
PrintWriter out
= new PrintWriter(new OutputStreamWriter (
incoming.get0utputStream()));
out.println("Hello! This is the Java EchoServer.");
out .println("Enter BYE to exit . ");
out . flush();
while (true) {
String str = in . readLine() ;
if (str =-- null) {
break ; // client closed connection
} else {
out .println("Echo : 11 + str);
out. flush() ;
if (str.trimO . equals ( "BYE")) {
break;
}
}
}
incoming . close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

The echo server will listen on port 8008. The method invocation in. readLine ()
attempt to receive a line of text from the client. If the connection i alive but the client
doe not send anything, the method invocation will be blocked. If the client sends a
line of text, the method invocation will return with that line of text. If the client closes
592 • Distributed Computing

the connection, the method invocation will return with null. The flush() call on
the output stream forces the messages to be sent to the client right away.
The server application can be compiled as usual. Let us say that we compile and
start the echo server on a host called saturn:
saturn¼ java EchoServer

Now, the server is ready and is waiting for clients. We can test the echo server
with the telnet program, which is available on almost all platforms. Let us say that
we run telnet on a host called venus:1
venus¼ telnet saturn 8008
Trying 140.192.34.63 .. .
Connected to saturn .
Escape character is •-]' .
Hello! This is the Java EchoServer.
Enter BYE to exit .
Hi, this is from venus
Echo: Hi, this is from venus
BYE
Echo : BYE
Connection closed by foreign host.

We can also write a Java client that communicates with the echo server:

EXAM P LE 1 2 . 2 A Simple Client Talks to the Echo Server

PURPOSE
This example illustrates the basic elements of a client application.

DESCRIPTION
This client contacts the echo server, sends 10 lines of text to the echo server, and prints
out the data it receives from the server.

SOLUTION
A
import java. io.•;
import java. net.•;
public class EchoClient {
public static void main(String[] args) {
try {
String host;

I. Although here we run the server and the client on difTcrent hosts, it should be okay to test the server
and client on the same host. 1n this case, the host name is localhost or 127 . o.o. 1.
12.1 Socket-Based Communication • 593

if (args.length > 0) {
host• args[0];
} else {
host= 11 localhost 11 ;
}
Socket socket= new Socket(host, 8008);
BufferedReader in
= new BufferedReader(new InputStreamReader(
socket . getinputStream O)) ;
PrintWriter out
= new PrintWriter(new 0utputStreamWriter(
socket.getOutputStream( )));
// send data to the server
for (inti= 1; i <= 10; i++) {
System . out.println( 11 Sending: line 11
+ i);
out.println( 11 line 11 + i);
out. flush() ;
}
out . println ( 11 BYE 11 ) ;

out.flush();
II receive data from the server
while (true) {
String str = in . readLine();
if (str = null) {
break;
} else {
System.out.println (str) ;
}
}
} catch (Exception e) {
e.printStackTrace() ;
}
}
}

The first argument of the program is the name of the host on which the erver
is running. If the host name is absent, localhost is assumed. Thi client contacts
the server on the given host at port 8008. The method invocation in. readLine ()
behaves the same on the client side as on the erver ide. To te t this client, we first
need to run the echo server on the host saturn: saturn% j ava EchoServer. Then
we compile and run EchoClient on the host called venus. The output i
venus¾ java EchoClient saturn
Sending: line 1

Sending: line 10
Hello! This is Java EchoServer .
Enter BYE to exit .
Echo : line 1

Echo : line 10
Echo : BYE
59 4 • Distributed Computing

This echo server handles one client at a time. This behavior is not acceptable
in some situations. It is more desirable for the server to be able to serve multiple
clients simultaneously. To accomplish that purpose, each client should be handled b y
a separate thread.

EXAM p LE 1 2 . 3 An Echo SeNer That Handles Multiple Clients Simultaneously

PURPOSE
This example illustrates a server that handles multiple clients simultaneously with the
use of threads.
DESCRIPTION
The behavior of this echo server is similar to that of the simple echo server; the
difference is that each client is handled by a separate thread.

SOLUTION
The ClientHandler class defines the threads that handle the clients. Note that the
body of the run () method is essentially the same as the body of the loop of the echo
server.

A.. I tlllltl C adlmt: ClientBancller


import java.io.•;
import java.net.•;
public class Cli entHandler extends Thread {
protected Socket incoming;
public ClientHandler(Socket incoming) {
this.incoming= incoming;
}

public void run () {


try {
BufferedReader in
: new BufferedReader(new InputStreamReader(
incoming.getinputStream()));
PrintWriter out
c new PrintWriter(new OutputStreamWriter(

incoming.getOutputStream()));
out .println ("Hello! This is the Java MultiEcboServer.");
out.println("Enter BYE to exit.");
out. flush() ;
while (true) {
String str = in.readLine();
if (str •• null) {
break;
} else {
out . println( "Echo : "+ str) ;
12.1 Sock~-BaKd Communication • 595

out.flush();
if (str.trimO .equals (" BYE" ))
break ;
}
}
incoming . close();
} catch (Exception e) {
e . printStackTrace();
}
}
}

The Mul tiEchoServer class is the server, which creates a server socket. When-
ever a cUent request is received, a new ClientHand1er object, which is a thread, is
created and started.

Eclao • ver 1bat bandies~ dlmts slm


public class MultiEchoServer {
public static void main (String O args ) {
try {
ServerSocket s = new ServerSocket (8009);
while (true ) {
Socket incoming= s . accept( );
new ClientHandler(incoming) . start () ;
}
} catch (Exception e ) {
e . printStackTra ce () ;
}
}
}

So far, we have developed two erver and a client that are all Ja a app . Although
only a Java app can be a erver in ocket-ba ed client- erver application . a client can
be written as either an app or an applet. Example L.4 how a client that i written
as an applet.

EXAM P L E 1 2 . 4 Visitor Counter Applet and Server

PURPOSE
Thi example illu trates a client implemented a an applet.

DESCRIPTION
This applet , how, the number of vi its, or hits, of the Web page that contains the
applet. A creen shot of the applet i hown in Figure 12. l .
596 ■ Distributed Computing

Figure 12.1
I Applet
The visitor counter
applet. 'You are visitor: 17
I
Applet started.

SOLUTION
The CounterServer class is the visitor counter server. It must be running on the
host where the Web server (that is, the HTIP server) is running.

import java.io .• ;
import java. net . •;
public class CounterServer {
public static void main(String[] args) {
System . out . println("CounterServer started . ");
int i = 1;
try {
// read count from the file
InputStream fin= new FileinputStream( "Counter . dat");
DatainputStream din= new DatainputStream(fin);
i = din . readlnt() + 1;
din.close();
} catch (!□Exception e) {
e.printStackTrace();
}
try {
ServerSocket s = new ServerSocket(8190);
while (true) {
Socket incoming= s.accept();
DataOutputStream out
= new DataOutputStream(incoming . getOutputStream()) ;
System. out . println( "Count: " + i) ;
out.writeint(i) ;
incoming . close();
OutputStream fout .. new FileOutputStream("Counter.dat") ;
DataOutputStream dout • new DataOutputStream(fout);
dout . writeint(i) ;
dout . close() ;
out . close() ;
i++;
}
} catch (Exception e ) {
e . pri ntStackTrace() ;
}
12.1 Socket-BaHd Communication • 597

System . out .println ( "CounterServer stopped.");


}
}

When the server starts up, it attempts to open a data file named Counter. dat to
retrieve the last count. If the file Counter. dat does not exist, an !□Exception will
be thrown. The exception will be caught and the count set to I , the initial value of i.
The current count is saved to the file every time the count changes so that the count
can be restored and continue after the server is shut down and restarted. The data file
is written with a DataOutputStream object, so it is in binary form. Similarly, the
count is sent to the client in binary form.
The Counter class is the visitor counter applet. The applet communicates with
the server to retrieve the count. In the ini t () method, a client socket is created.
Recall that an applet can communicate only with a server that resides on the same
host as the Web server. The host name of the client socket can only be the host of the
document base.

import java. io.• ;


import java .net . • ;
import java.awt.• ;
import java.applet . Applet ;
public class Counter extends Applet {
protected int count= O;
protected Font f ont= new Font ("Serif", Font .BOLD , 24);
public void init () {
URL url = getDocumentBase ();
try {
Socket t = new Socket (url . get Host () , 8190);
DatainputStream in
= new DatainputStream (t . getinputStream ());
count= in . readint ();
} catch (Exception e) {
e .pri ntStackTrace ();
}
}

publi c void paint (Graphics g) {


int x = 0, ya font . getSi ze ();
g . setColor (Color .green);
g . set Font (font ) i
· ( "You are v1s1
g . drawStr ing · · t or ·. " + count, x, y ) ·,
}
} •
598 • Distributed Computing

The servers in all the examples so far deal with each client independently from
other clients. No client-to-client communication is supported. The following example
illustrates how clients can communicate with each other via the server.

EXAM PLE 1 2 . 5 Broadcasting Echo Server

PURPOSE
This example illustrates how clients can interact with one another through the server.

DESCRIPTION
The behavior of this echo server is similar to that of the echo server in Example 12.4;
the difference is that this server will broadcast messages received from a client to all
other active clients. This capability allows its clients to "chat" online.

SOLUTION
The BroadcastClientHandler class defines a thread that handles the clients. The
BroadcastEchoServer class is the server. It contains a set of active clients, which
will be used while the server is broadcasting messages.

Tbt ..........;.s __
public class BroadcastEchoServer {
static protected Set activeClients = new HashSetO;
public static void main(String[] args) {
inti= 1;
try {
ServerSocket s = new ServerSocket(8010);
while (true) {
Socket incoming= s.accept();
BroadcastClientHandler newClient =
new BroadcastClientHandler(incoming, i++);
activeClients.add(newClient);
newClient.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

The BroadcastClientHandler class defines a thread that handles a client.


The sendMessage () method sends a message to the client. The key feature in
this client-handling thread is the loop that iterates through the active cli~nts. set
BroadcastEchoServer . activeClients. The message received from this client
12.1 Socket-Based Communication • 599

is also broadcast to all other active clients. Thus the sendMessage O method needs
to be synchronized because it will be invoked by all the threads that are handling
clients.

A thread that handlet a dleat


public class BroadcastClientHandler extends Thread {
protected Socket incoming;
protected int id;
protected BufferedReader in;
protected PrintWriter out;
public BroadcastClientHandler(Socket incoming, int id) {
this.incoming= incoming;
this.id= id;
try {
if (incoming!= null) {
in= new BufferedReader(new InputStreamReader(
incoming.getinputStream ()) ) ;
out= new PrintWriter(new OutputStreamWriter (
incoming.getOutputStream()));
}
} catch (Exception e) {
e.printStackTrace();
}
}

public synchronized void sendMessage (String msg) {


if (out != null) {
out.println(msg);
out . flush();
}
}

public void run () {


if (in!= null &&
out != null) {
sendMessage("Hello! This is Java BroadcastEchoServer." );
sendMessage("Enter BYE to exit." );
try {
while (true) {
String str = in .readLine () ;
if (str == null) {
breaki
} else {
// echo back to this cllent
sendMessage("Echo : 11 + str );
if (str. trim (). equals ("BYE" )) {
break;
} else {
// broadcast to other active clients
Iterator iter •
BroadcastEchoServer . activeClients . iterator();
600 • Distributed Computing

while ( iter . hasNext ( ) ) {


BroadcastClientHandler t =
(BroadcastClientHandler) iter . next ( ) ;
if (t != this) {
t . sendMessage (" Broadcast(" +id+"): " +
str);
}
}
}
}
}
incoming . close();
// this client is no longer active
BroadcastEchoServer.activeClients.remove (this) ;
} catch (IOException e) {
e . printStackTrace();
}
}
}
}

To test this broadcast server, we first start the server on a host called neptune:
neptunei. java BroadcastEchoServer. Then we start two telnet clients on
hosts venus and saturn, respectively:

Client 1 on venus Client 2 on saturn


venus% telnet neptune 8010 saturn% telnet neptune 8010
Hello! This is Hello! This is
Java BroadcastEchoServer . Java BroadcastEchoServer.
Enter BYE to exit . Enter BYE to exit.
Echo : Hi there! Broadcast(!) : Hi there!
Broadcast(2) : Hello! Echo: Hello!
Echo : I ' m on venus. Where are you? Broadcast(!): I ' m on venus.
Where are you?
Broadcast (2): I'm on saturn. Echo : I'm on saturn.

The broadcasting echo server is a prototype of many useful applications, includ-


ing multiplayer games and distributed online collaboration.

12.1 .3 Design Case Study-Stock Quotes I

In this section, we develop a stock quote server and stock ticker clie nt apple t. The
stock quote server maintains the current quotes of the stocks of various companie .
A random number generator is used to simulate the fluctuation of the quotes ( ee
Example l 1.2 [p. 551 ]). The stock ticker client applet displays the quotes of a elected
list of companies that are of interest. The ticker symbols and quotes scroll acros the
12.1 Socket-Based Communication • 601

Figure 12.2
lcilJJ•j$JM4W§i@I-J8J xf
The stock ticker Applel
client applet. 8 Sun 103 Int
jApplet started.

ticker banner continuously. A screen shot of the stock ticker client applet is shown in
Figure 12.2.
This application involves real-time updates of stock quotes. There are generally
two strategies for handling real-time updates:
Client pull: The client periodically contacts the server to receive the current
quotes from the server.
Server push: The server notifies clients whenever the quote of one of the tocks
watched by the clients has changed.
The strategy chosen depends on many factors, including acceptable latency of up-
dates, frequency of changes, and the like. We implement the tock ticker application
by using both strategies.

The Client Pull Implementation


The information flow of the client pull implementation is as follows: ( 1) Periodically,
the ticker client connects to the quote server; and (2) the quote erver end the quote
of all the companies in the following format and then disconnect :

name1 quote 1
name2 quote2

The structure of the de ign is shown in Figure 1__ 3_ The three haded cla e
are the new classes to be implemented here. The Ticker clas handle the di play
and animation of the quote . The di play and animation portion of the client i
implemented similarly to the ScrollingBanner applet in Example -.3 [p. 194].

Figure 12.3 DBAnimationApplet Thread

The client pull de-


sign of the stock
ticker applet and
the quote server.

----L---,* 1 ...---..._---
TlckerPullCllent - - - - -Server
L - - - - - - - ' Client
- -.___
QuotePullSeNer
______,
602 • Distributed Computing

The update strategy of the stock quote is separate from the display and animation of
the stock quotes and is implemented in the class TickerPullClient . The separation
allows the Ticker class to be reused in different implementations of the stock ticker
client with different update strategies and communication mechanisms.

The Stock Ticker Animator


The Ticker class extends the double-buffered generic animation applet class DBAn-
imationApplet in Example 7.2 [p. 259]. The fields are summarized in the following
table:

Field Description

vatch Watch list-a string that consists of a list of the names of the
companies to be watched by this client
symbol Array of names of the companies in the watch list
quote Array of current quotes of the corresponding companies in the
symbol array
prevQuotes String containing the quotes of the previous cycle
curQuotes String containing the quotes of the current cycle
url URL of the quote server

In order to create the appearance of continuously scrolling stock quotes, we use


two strings: prevQuotes and curQuotes. For example, at any given moment, the
two strings may have the following values:

prevQuotes:"IBM 91 Sun 105 Intel 99"


curQuotes: "IBM 89 Sun 106 Intel 99"

The methods of the Ticker class are summarized in the following table:

Method Description

ini tAnimator () In1tializes the ticker client


ini tQuotes () Hook method for retrieving the initial quotes from the
quote server
updateQuotes O Hook method for updating quotes. The prevQuotes
and curQuotes strings will be updated and properly
formatted after calling this method
paintFrame O Paints the current frame
12.1 Sockd-Based Communication • 603

import
import
java. awt.*;
java.util.•;
.......
,,
import java. net.•;
import java.io.•;
public class Ticker extends DBAnimationApplet {
protected Font font= new Font("Sans-serif", Font.BOLD, 24);
protected int offset= 1;
protected int x, y;
protected String watch;
protected String symbol O , quote[] ;
protected String prevQuotes, curQuotes;
protected URL url;
(The ini tAnimator () method on page 603)
(The paintFrame () method on page 604)
(The ini tQuotes () and updateQuotes () methods on page 604)
}

The ini tAnimator () method initializes the fields of the ticker client. The watch
list is obtained from the applet parameter watch. The watch list is broken down into
single names and then stored in the array symbol.

flit illttbi■atorU ••• 1i aftlle Tic:ardw


public void initAnimator() {
String att = getParameter ( "delay" ) ;
if (att != null ) {
setDelay (Integer.parseint (att));
}
watch= getParameter ( "watch" );
if Catt != null) {
StringTokenizer tk = new StringTokenizer (watch);
List list= new ArrayList ();
while (tk.hasMoreTokens ()) {
list.add(tk . nextToken ());
}
int n = list . size() ;
symbol = new String [n] ;
quote= new String[n];
for ( inti= O; i < n; i++) {
symbol[i] = (String) list.get (i );
quote [i] = 11 0 11 ;
}
}
url = getDocumentBase ();
ini tQuotes O ;
updateQuotes O ;
prevQuotes = curQuotes;
x = d .width;
y • font .getSize ();
}
604 • Distributed Computing

Figure 12.4 ~-- --- - - -- -~~~~~-~~-~~~-- ·- - - -·-_ *_ _- - ----.


-~-~~-~~~~~-~ -.----. . --·>i
Drawing of the
stock ticker string.
!IBM 91 Sun 10sl Intel 99 1BM 89 Sun jos Intel 9~
1

\ Viewing area
(x, y)

The paintFrame () method implements a behavior similar to that of the


scrolling banner. The drawing of the ticker strings is done by concatenating two
strings: prevQuotes and curQuotes, as illustrated in Figure 12.4. When the string
prevQuote moves completely off the left end of the viewing area, the update-
Quotes () method is invoked to retrieve the new quotes of the companies and form
a new curQuotes string. The current curQuotes becomes the prevQuotes.

Tbe :tfrw() ..-..1 of the Ticker elass


public void paintFrame(Graphics g) {
g . setColor(Color.black);
g . fillRect(O,O,d . width,d.height);
// set the font and color, and draw the text
g . setFont(font);
g.setColor(Color . green);
g.drawString(prevQuotes + curQuotes, x, y);
II get the font metrics to determine the length of the text
FontHetrics fm = g .getFontMetrics() ;
int length= fm . stringWidth(prevQuotes) ;
II adjust the position of the ticker string for the next frame
x -= offset ;
II if the prevOuotes string is completely off to the left end
II update the quotes and adjust the position.
if (x < -length) {
X = O;
prevQuotes = curQuotes;
updateQuotes () ;
}
}

The methods ini tQuotes () and updateQuotes () are hook methods that can
be overridden in the subclasses. A null implementation is provided for ini tQuot es () .
The implementation of updateQuotes O simply forms the curQuotes string by
concatenating all the symbols and quotes.

Tbe WtQutu() IIMl11


public void initQuotes ( ) {}
protected synchronized void updateQuotes() {
StringBuffer sb • new StringBuffer();
for (inti = O; i < quote.length ; i++) {
sb. append ( symbol [i] + 11 11 + quote (i] +11 II) j
}
curQuotes = sb.toString() ;
}

----=::,---
_..
12.1 Socket-~d Communication ■ 605

The Stock Ticker Pull Client


The TickerPullClient class is the client applet. It extends the Ticker class and
overrides the updateQuotes () method. In the updateQuotes O method, a socket
connection is established with the quote server to retrieve the current quotes of all the
companies in the format
name 1 quote 1
namei quotei

namen quoten
The client picks out the companies on its watch list and updates the quote array.
Then a new curQuotes string is formed, using the new quotes.

dlint
import java.awt . *;
import java.util.* ;
import java.net . *;
import java.io.*;
public class TickerPullClient extends Ticker {
protected void updateQuotes() {
int i ;
try {
Socket t = new Socket (url.getHost () , 8001 );
BufferedReader in=
new BufferedReader (new InputStreamReader (t. getlnputStream ()));
String line ;
while ((line= in.readLine ()) != null) {
StringTokenizer tk = new StringTokenizer (line);
String name= tk . nextToken();
for (i = O; i < quote .length ; i++) {
if (symbol[i] .equals (name)) {
String newQuote = tk .nextToken();
quote[i] = newQuote ;
}
}
}
t. close ();
} catch (I0Exception e ) {
e . printStackTrace ();
}
super. updateQuotes O ; // form the curOuotes string
}
}
This completes the implementation of the stock ticker client with the client pull
strategy.

The Stock Quote Pull Server


The quote server involve two threads: ( 1) The main thread listens to incoming
client ; and (2) another thread monitor changes in the quotes. In this example,
606 • Distributed Computing

changes are simulated with a random number generator. The array symbol contains
the names of all the companies, and the array quote contains the current quotes of
the corresponding companies in the array symbol.

import java.io.• ;
import java .net.•;
public class QuotePullServer extends Thread {
static protected String symbol[] =
{ "IBM", "Sun", "Intel", "Apple" };
static protected int quote[] =
{ 100, 100, 100, 100 };
{The main() method on page 606)
(The run() method on page 606)
}

The main () method listens to incoming ticker clients. It sends the quotes of all
companies to any client that contacts the quote server.

Dlaua() aetlaellettlle QutePullServer class


public static void main(String[] args) {
new QuotePullServer().start();
try {
ServerSocket s = new ServerSocket(8001);
while (true) {
Socket incoming= s . accept();
PrintWriter out=
new PrintWriter(new OutputStreamWriter(
incoming.getOutputStream()));
for (inti= O; i < quote.length; i++) {
out. println (symbol [i] + " " + quote [i] ) ;
}
out.flush();
out . close O ;
incoming . close() ;
}
} catch (Exception e) {
e.printStackTrace();
}
}

The run () method simulates the changes of the quotes with a random number
generator and updates the quote array.

'Ille rm() IIA.:dGttlleQIIOteh118enerdall


publi c void run () {
while (true) {
try {
Thread . currentThread() . sleep(10000);
12.1 Socket-Based Communication • 607

for (inti= O; i < quote.length; i++) {


quote[i] += (Math.random() - 0 . 4) • (10.0 * Math . random());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

This completes the stock quote server implementation using the client pull
strategy.

The Server Push Implementation


The information flow of the server push implementation is as follows:
■ The ticker client contacts the quote server initially and provides a watch list in
the format WATCH name 1 namei ... namen.
■ The quote server registers the client and sends the quotes of only the companies
on the watch list to the client in the format

name 1 quote 1
namei quotei

namen quoten
DONE

■ The connection between the ticker client and the quote server i maintained until
the client decides to close it.
■ Whenever the quote of a company is changed, the quote erver end the quote
to those clients who are watching the company. in the format

name quote

■ The client closes the connection by ending the me age CLOSE.

The structure of the design is shown in Figure 12.5. The haded boxe contain
the new classes to be implemented here.

The Stock Ticker Push Client


The client involve two cla se :

1. The TickerPushClient cla extend the Ticker cla , . It make. the initial
contact with the quote erver, retrieve the initial quote . and handle the di play
of the quote .
2. The QuoteListener class i a thread that Ii ten~ to the quote erver for updates
on the quote. Each client ha a QuoteListener object that maintain a connec-
tion with the quote server and update the quote displayed by the ticker client
when change. occur.
608 ■ Distributed Computing

Figure 12.5 DBAnimationApplet Thread

The server push


design of the stock
ticker applet and
the quote server.

* 1
TickerPushCllent QuotePushServer
Client Server
1 1
1 *
1 1
QuoteUstener lickerClientHandler
Client Server

The TickerPushClient overrides the ini tQuotes () method of the Ticker


class. In the ini tQuotes () method, the quote server is contacted to retrieve the
initial quotes of the companies on the watch list. Then a QuoteListener object,
wltich is a thread, is created and started to listen to the changes in the quotes.

The stock ticker


import java.awt.•;
import java.util . *;
import java.net.•;
import java.io.•;
public class TickerPushClient extends Ticker {
protected Socket socket;
protected PrintWriter out;
protected QuoteListener quoteListener;
public void initQuotes() {
try {
socket= new Socket(url.getHost(), 8002);
// send the watch list
out= new PrintWriter(new 0utputStreamWriter(
socket .get0utputStream()));
out .println("WATCH 11 + watch);
out.flush();
// receive the initial quotes
BufferedReader in=
new BufferedReader(new InputStreamReader(
socket.getlnputStream()));
String line;
while ((lines in . readLine()) != null) {
if (line . trim() .equals("D0NE")) {
break;
}
12.1 Socket-Based Communication • 609

StringTokenizer tk = new StringTokenizer(line);


String name= tk.nextToken();
for (inti= O; i < quote.length; i++) {
if (symbol [i] . equals (name)) {
String newQuote = tk .nextToken();
quote[i] = newQuote;
}
}
}
quoteListener = new QuoteListener(this, in);
quoteListener . start();
} catch (IOException e) {
e.printStackTrace();
}
}

public void destroy() {


out . println("CLOSE");
quoteListener.interrupt();
}
}

The Quote Listener


A QuoteListener object is a thread that listens to the quote server for changes of
the quotes. In contrast to the client pull strategy, a QuoteListener object does not
actively contact the quote server but passively waits for the server to send any changes.

a.. Qu~.Unaer
class QuoteListener extends Thread {
public QuoteListener(TickerPushClient ticker, BufferedReader in) {
this.ticker= ticker;
this.in= in;
}

public void run() {


String line;
try {
while ((line= in.readline()) != null) {
StringTokenizer tk a new StringTokenizer(line);
String name= tk.nextToken();
for (int i a O; i < ticker.n; i++) {
if (ticker . symbol[i] . equals(name) ) {
String newQuote • tk.nextToken();
ticker . quote[i] = newQuote ;
}
}
if (islnterrupted ( )) {
break ;
}
}
61 0 • Distributed Computing

in . close();
} catch (! □Exception e) {
e.printStackTrace();
}
}

protected TickerPushClient ticker ;


protected BufferedReader in;
}

This completes the implementation of the stock ticker client using the server push
strategy.

The Stock Quote Push Server


The quote server implementation consists of two classes:

1. The QuotePushServer class involves two threads: one listens to incoming


clients, and the other monitors the changes in the quotes.
2. The TickerClientHandler class defines a thread that handles a ticker client. A
TickerClientHandler object maintains a connection with a QuoteListener
object of a ticker client and pushes the changes of quotes to that client.
The field clients is a set that holds handlers for active ticker clients.

~iEiiU
import java.io .•;
import java . util.•;
import java.net.•;
public class QuotePushServer extends Thread {
static protected String symbol[] =
{ "IBM", "Sun", "Intel", "Apple"};
static protected int quote[] =
{ 100, 100, 100, 100 };
static protected Set clients s new HashSet();
(The main () method on page 610)
(The run () melhod on page 611 )
(The get Quote() method on page 612)
}

The main() method listens to incoming ticker clients, and creates a Ticker-
ClientHandler object, which is a thread, for each ticker client, adds the client
handler to the active clients set clients, and starts the thread.

Tbe uJ.o() medlGd ~tbe tePuJalener clall


public static void main(String(] args) {
new QuotePushServer().start( );
try {
ServerSocket s • new ServerSocket(8002);
12.1 Socket-Based Communication • 611

while (true) {
Socket incoming• s . accept();
TickerClientHandler newClient • new TickerClientHandler(incoming) ;
clients.add(newClient);
newClient.start();
}
} catch (Exception e) {
e.printStackTrace() ;
}
}

The run () method simulates the changes of the quotes with a random number
generator and updates the quote array. Furthermore, if the quote of a stock is
changed, it iterates through the set of handlers of active ticker clients and invokes
the newQuote () method of the TickerClientHandler class to request that the
handlers push the change to their respective clients.

public void run() {


while (true) {
try {
Thread.currentThread (). sleep (10000) ;
for (inti= O; i < quote . length ; i++) {
int dq = O;
if (Math.random( ) < 0 . 5) {
dq = (int) ( (Math . random () - 0.4) * (10 . 0 * Math .random ())) ;
}
if (dq !a 0) {
// quote is changed
quote[i] + dq;
2

// push to ticker clients


Iterator iter • clients . iterator ();
while (iter . hasNext()) {
TickerClientHandler t • (TickerClientHandler) i ter. next O ;
t .newQuote(symbol[i] , quote(i] );
}
}
}
} catch (Exception e) {
e .printStackTrace ( );
}
}
}

The get Quote() method retrieve the current quote of a company by its name.
It i u ed by the TickerClientHandler cla s when ending the initial quotes of
companie to the client on its watch li t.
61 2 • Distributed Computing

static public int getQuote(String name) {


for (inti= 0; i < n; i++) {
if (symbol[i] . equals(name)) {
return quote[i];
}
}
return 0;
}

The Client Handler for the Stock Quote Push Server


The TickerClientHandler class defines a thread that handles a ticker client.

class TickerClientHandler extends Thread {


protected PrintWriter out;
protected Buffered.Reader in;
protected Socket socket;
protected String symbol[];
public TickerClientHandler (Socket socket) {
this.socket= socket;
try {
out= new PrintWriter(new 0utputStreamWriter(
socket.getOutputStream()));
in= new Buffered.Reader(new InputStreamReader(
socket.getlnputStream()));
} catch (!□Exception e) {
e.printStackTrace();
}
}

(The run () method on page 613)


(The newQuoteO method on page 613)
}

The run () method defines the body of the thread that handles a ticker client. It
waits for messages sent by its client and responds to two messages:

1. WATCH: This watch list is sent by the client at the beginning of the session. The
client handler responds by sending to the client the initial quotes of companies
on the watch list.
2. CLOSE: This message should be the last one received from the client. The client
handler responds by removing itself from the active-clients set QuotePush-
Server . clients and terminates itself.

_. ....
12.1 Socket-Based Communication • 61 3

The handler thread is blocked during the session.

The ran() m of tlae Ticbz'Cliatllandlv dw


public void run() {
try {
String line;
while ((line= in.readLine()) != null) {
if Cline . startsWith( 11 WATCH 11 ) ) {
StringTokenizer tk = new StringTokenizer (line);
tk .nextTokenO;
Vector v = new Vector ();
while (tk.hasMoreTokens( )) {
v.addElement(tk.nextToken());
}
int n = v.size();
String symbol[] = new String[n] ;
inti;
for (i = O; i < n; i++) {
symbol[i] = (String) v.elementAt (i ) ;
}
this . symbol= symbol;
for (i = O; i < n; i++) {
out.println(symbol[i] + 11 11 +
QuotePushServer.getQuote (symbol [i]));
}
out .println( 11 D0NE");
out . flush () ;
} else if (line.trim () . equals ( "CL0SE 11 ) ) {

break;
}
}
socket.close ();
in . close() ;
out . close () ;
} catch (I0Exception e) {
e . printStackTrace ();
}
QuotePushServer.clients.remove (this );
}

The newQuote () method pu he a new quote to the ticker client. Thi method is
not invoked by the client handler thread but by the quote-monitoring thread defined
in the run() method of the QuotePushServer clas [p. 611].

Tile aevQuote() metbod of TickerCliudancll•r c1as.1


public void newQuote (String name, int quote) {
boolean needToSend = false ;
if (symbol ! = null) {
for (inti= O; i < symbol.length; i++)
61 4 • Distributed Computing

if (symbol[i] .equals(name)) {
needToSend = true;
break ;
}
} else {
needToSend = true;
}
if (needToSend) {
out . println(name + 11 11
+ quote);
out. flush();
}
}

This completes the stock quote server implementation using the server push
strategy.

REMOTE METHOD INVOCATION

Developing distributed applications using socket-based communications requires ex-


plicit connections between clients and servers and transmission of data through the
connections. Java remote method invocation (RMI) is a mechanism that simplifies the
programming model of distributed applications. It does not require explicit connec-
tions and data transmission. Objects residing on different hosts can be manipulated
as if they were all on the same host. Interaction and communication among objects ·
residing on different hosts can be accomplished similarly to regular method invoca-
tion in Java. The connections among different hosts and the transmission of data are
handled implicitly by JVM.

12.2.1 The Architecture

The key participants of the RMI architecture are the following:

Server: An object that provides services to objects residing on remote hosts.


Service contract: An interface that defines the services provided by the server.
Client: An object that uses the services provided by objects residing on remote
hosts.
Stub: An object that resides on the same host as the client and serves as a proxy,
or surrogate, for the remote server.
Skeleton: An object that resides on the same host as the server, receiving requests
from the stubs and dispatching the requests to the server.

The clients and servers are written by programmers. Stubs and skeletons are automat-
ically generated by the RMI compiler from the server code.
12.2 Ranote Method Invocation • 61 5

Figure 12.6
Client Server
The architecture
of remote method 1 6 3 4
invocation (RMI).

Stub 2 Skeleton
JVM JVM
5

A remote method invocation is simply an invocation of a method of a remote ob-


ject. Remote method invocation uses the same syntax as regular method invocations.
Figure 12.6 is a high-level view of the RMI invocation process.
1. The remote method invocation server. m() by the client is carried out as an
invocation of a method of the stub: stub . m() .
2. The stub marsbals2 the arguments and sends the arguments and call infonnation
to the skeleton on the server host.
3. The skeleton unmarshals the call infonnation and the arguments and invokes the
method of the server: server. m() .
4. The server object executes the method and returns the result to the skeleton.
5. The skeleton marshals the result and sends the result back to the stub.
6. The stub unmarshals the result and returns the result to the client.

Key Issues
In Java, there are actually two types of objects: ( 1) Objects that are accessible
only within the local host are called local objects or nonremote objects (by default,
objects are local); and (2) objects that are accessible from a remote host are called
remote objects, and are instances of classes that implement a marker interface called
j ava . rmi. Remote.
Remote objects and local objects are similar in the following respect : Reference
to local or remote objects can be passed as arguments and returned as result of method
invocation (local or remote). References to local or remote objects can be cast u ing
the same syntax. The instanceof operator can be applied to local or remote objects.
One difference between remote and local objects i that the client of remote
objects interact with the stubs representing the remote object, not the actual object .
Another difference between remote and local object is in the pa ing of arguments
and return values of remote method invocation . lf an argument or return value of a
remote method invocation is a local object, the object i erialized, ent to the remote
host, and deserialized (that is, a copy of the local object i pas ed to the remote host).
If an argument or return value of a remote method invocation i a remote object, a

2. Marshal in thi context mean· to nmmgc lhc information in a linear strenm.


61 6 • Distributed Computing

remote object reference i pas ed. A remote object reference can uniquely identify
and locate a remote object in the network. In hort, in remote method invocation, local
objects are pa ed by value and remote objects are passed by reference.
An important question is, How doe a client locate the erver that will provide
the service? Thi question is resolved by use of a naming scheme coupled with the
RMI registry. Each RMI erver is identified by a URL with the protocol rmi. An RMI
registry i like a telephone directory. It contain a mapping between the RMI servers
and their names. Each RMI server mu t be regi tered to an RMI registry to make it
available to remote clients, a proce known a binding. A client can locate an RMI
server by contacting an RMI registry running on a remote host and looking up the
server by name. The following URL identifie an RMI server:
rmi: //host: pon I name

where host i the name or IP address of the host on which the RMI registry is running,
port is the port number of the RMI registry, and name is the name bound to the RMI
server.

12.2.2 Using RMI

The classes and interfaces related to RMI are contained in the j ava. rmi package.
The programming interface of the RMI registry is provided in the class Naming. All
the methods of the Naming class are static. They are summarized in the following
table:

Method Description

bind (name, obj) Binds the remote object obj to the specified name
rebind(name, obj ) Binds the remote object obj to the specified name,
even if the name is already bound. The old binding is
discarded
unbind(name) Removes the binding of the specified name
lookup(url) Returns the remote object bound to the specified URL
list(url) Returns a list of the bindings registered in the RMI
registry at the specified URL

U ing remote method invocation involves the following steps:


1. Define an interface of the remote object. This is the contract between the erver
and its client .
public interface Contract extends Remote {
public void aService ( . . . ) throws RemoteException;
// other services .. .
}
12.2 RetnOte Mdhod Invocation • 617

The Contract interface must extend the Remote interface. The methods in
thjs interface must declare that they may throw the RemoteException excep-
tion. The types of the arguments and return values must be seriilizable.
2. Define a service implementation class that implements the Contract interface.
public class ServiceProvider extends UnicastRemoteObject,
implements Contract {
public String aService( . . . ) throws RemoteException {
II implementation .. .
}
II implementation of other services . . .
}

The service implementation class must extend the UnicastRemoteObj ect


class.
3. Create an instance of the server, and register that server to an RMI registry:

Contract server = new ServiceProvider ( . . . ) ;


Naming.rebind(name, server);

4. Generate the stub and skeleton classes, using the RMI compiler. The tub
class is named ServiceProvider_Stub, and the skeleton class is named
ServiceProvider_Skel. The stub class also implements the Contract in-
terface.
S. Develop a client that uses the service provided by the Contract interface. The
client can be a local or remote object. It must first locate the remote object that
provides the service before remote methods can be in oked:

Remote remoteObj = Naming . lookup (name );


Contract serverObj = (Contract ) remoteObj;
II . . .
serverObj . aService (. . . ) ; II remote method invocation
II . ..

The remoteObj is actually an instan e of the rub cla , hich al o im-


plements the Contract interface. It hould be downca t to Contract before
invoking its method .

The typical structure of RM1 application hown in Figure 1__ 7.

Design Pattern: Proxy


The tructure of RMI application . illu trate~ the Prox de ign pattern.

Design Pattern Proxy


Categm:v: tructural de ·ign pattern.
/ 11 1e111: To provide a surrogate or placeholder that repre ents another object.
61 8 • Distributed Computing

Applicability: The Proxy design pattern is applicable when there is a need


for a more versatile or sophisticated reference to an object than a simple
reference (or pointer). Common situations in which the Proxy design pattern
is applicable include
• a remote proxy, such as the stub in RMI, providing a local representative
for an object residing on a remote host.
• a virtual pro~), creating space- or time-consuming objects on demand.
• a protection proxy, controlling access to the original object to provide
different levels of access rights.

The structure of the Proxy design patterns is shown in the following diagram:

Client p---------1\ Subject \


4,
I
r- ----- ------- -- ~-- -------
1 I

RealObject \
realObject tj I

~Proxy
--~
The participants of the Proxy pattern are the following:

■ Proxy (e.g., ServiceProvider_Stub), which maintains a reference that lets


the proxy access the real object and implements the Subject interface so that a
proxy can be substituted for a real object.
■ Subject (e.g., Contract), which defines the common interface for RealObject
and Proxy so that a Proxy can be used anywhere a RealObject is expected.
■ Rea/Object (e.g., ServiceProvider), which defines the real object that the
proxy represents.

For a detailed discussion of the Proxy design pattern see Gamma et al. (1995].

Figure 12.7
Remote 11 UnicastRemoteObject I
6. d,
The typical struc-
ture of RMI appli-
cations. AClient > - - -- - - -~ Contract
aService()

I
r- -- ---- - L __ __- - ~
I I
I I

ServiceProvlder_Stub Service Provider


aService() aService()
12.2 Remote Method Invocation • 619

EXAM PL E 1 2 . 6 A Simple RMI Program-Hello from Venus!

PURPOSE
This example shows the basic elements and steps involved in building an RMI
application.

DESCRIPTION
We develop an RMI server and an RMI client. The server sends a string "Hello from
Venus!" to its clients. The client, which is an applet, displays the string it receives
from the server.

SOLUTION
The Hello interface defines the service to be provided by the server. The Helloimpl
,, class is the server that implements the Hello interface. The HelloApplet class is
the client.

Tbe ceatract lntelf-=e far DII


--
public interface Hello extends java. rmi.Remote {

}
String sayHello () throws java.rmi.RemoteException;

The main O method of the Helloimpl class creates an instance of the erver
and binds it to the name HelloServer in the RMI registry.

'Die RMI m va ~emmtatlGll


import java.rmi.*;
import java.rmi .server .UnicastRemoteObj ect ;
public class Hellolmpl
extends UnicastRemoteObject
implements Hello {
private String name ;
public Helloimpl (String s )
throws java.rmi . RemoteException {
super() ;
name= s;
}
public String sayHello () throws RemoteE..~ception {
return "Hello from Venus! ";
}
public static void main (String args[]) {
System . setSecurityManager(new RMISecurityManager () ); try {
Helloimpl obj= new Helloimpl ("HelloServer ") ;
Naming .rebind ("HelloServer", obj);
} catch (E.~ception e) {
e .printStackTrace ();
}
}
}
620 • Distributed Computing

Running the RMI server involves the following steps:

1. Compile the server implementation class Helloimpl. j ava.


2. Generate the stubs and skeletons, using the RMI compiler rmic:

venus¼ rmic Helloimpl

The compilerrmic generates two files: Helloimpl_Stub. class (the stub) and
Helloimpl_Skel. class (the skeleton).
3. Start the RMI registry on the server host:

venus¼ rmiregistry k

4. Run the server:

venus¼ java Helloimpl &

The ini t () of the client applet looks up the server through the RMI registry on
the server host. It downcasts the server to Hello, the contract interface, and makes
remote method invocation obj. sayHello O.

The RMI client


import java.avt. • ;
import java.rmi.*;
public class HelloApplet
extends java.applet . Applet {
String message= 1111 ;
public void init() {
try {
Hello obj= (Hello)
Naming. lookup ( "rmi: / / " + getCodeBase () . getHost O +
"/HelloServer");
message= obj . sayHello();
} catch (Exception e) {
e.printStackTrace();
}
}
public void paint(Graphics g) {
g . drawString(message , 25 , 50);
}
}

The client can be compiled and executed as usual on any host. ■

12.2.3 Design Case Study-Stock Quotes II

In Lhis section we implement the stock ticker application by using RMI. We implement
both the client-pull and server-push strategies.
12.2 Remote Method Invocation • 621

Figure 12.8
Ticker
I Remote
&
I UnicastRemoteObject
The client pull de-
sign of stock ticker
applet and the QuoteSsrver
quote server, using TlckerPul~llentK>------t
RMI.
I
'- ----~I
* *
StockQuote QuoteServerlmpl
I
getQuote()
S7
Serializab/e I

The RMI Client Pull Implementation


In the client pull implementation, the clients periodically contact the server for current
quotes. Therefore, only the server needs to be a remote object; the clients are local
objects. The structure of the design is shown in Figure 12.8.

The RMI Pull Server Contract Interface


The service provided by the server simply returns an array of quotes, so the service
contract interface is quite simple.

Remote Interface QuoteSen•r


public interface QuoteServer extends java.rmi .Remote {
StockQuote[] getQuote ()
throws java.rmi .RemoteException;
}

The StockQuote class is a simple class that represents the quote of a stock.
Its instances are passed as the result of the get Quote O method, o it must be
serializable.

CIMs 8'octQuot•
import java. io.• ;
public class StockQuote implements Seriali zable {
public String name ;
public int quote;
public StockQuote (Stri ng name , i nt quote) {
this .name= name;
this.quote~ quote;
}
}
6 22 • Distributed Computing

The RMI Stock Quote Pull Server


The RMI server implementation class QuoteServerimpl provides an implementa-
tion for the get Quote() method defined in the Quote Server interface.

1beRMlse1ter tatlon oteSBVerlmP.l


import java.rmi.•;
import java .rmi.server .UnicastRemote0bject;
public class QuoteServerimpl extends UnicastRemote0bject
implements QuoteServer {
public QuoteServerimpl() throws java.rmi.RemoteException {}
protected StockQuote quote[] = {
new StockQuote("IBM", 100),
new StockQuote("Sun", 100),
new StockQuote("Intel", 100),
new StockQuote("Apple", 100) };

public StockQuote[] getQuote() throws java.rmi.RemoteException {


return quote;
}

{The main O method on page 622}


(The monitorQuotes () method on page 623}
}

The main () method creates an instance of Quote Server Impl and binds it to the
name QuoteServer. At the end, the moni torQuotes () method is invoked, which
continuously monitors the changes in the stock quotes. Unlike a socket server, an
RMI server does not need to wait for a client to contact the server. An RM1 client
will contact the RMI registry first, and a remote reference of the RMI server will be
passed to the client.

Tbe 1184a() lllf6od afdle Qaot•n•r


public static void main(String[] args) {
System . setSecurityHanager(new RHISecurityManager());
try {
QuoteServerimpl obj= new QuoteServerimpl();
Naming . rebind("QuoteServer", obj);
obj .monitorQuotes();
} catch (Exception e) {
e . printStackTrace ( );
}
}

The moni torQuotes () method contains an infinite loop that simulates the
changes to the quotes with a random number generator.
12.2 Remote Method Invocation • 623

--~-", .. ·~-- rr~ ~·.-- '" .. _.. -- - . ., .


) ...· . - ll' ....... .:..::....,r•lt!.~~,:-\:.. . _,_·,,,;_ - ,, - .

public void monitorQuotes() {


while (true) {
try {
Tbread.currentTbread().sleep(10000);
for (inti• 0 ; i < quote.length; i++) {
quote[i) . quote+•
(Hath . random() - 0.4) • (10.0 • Hath.random());
}
} catch (Exception e) {
e .printStackTrace();
}
}
}

This completes the RMI stock quote server implementation using the client pull
strategy.

The RMI Stock Ticker Pull Client


The RMI client TickerPullClient class extends the class Ticker, which handles
the display of the quotes. It contains a reference to the remote server and overrides
the ini tQuotes O and the updateQuotes () methods.

import java.awt.•;
import java.util.•;
import java.io.•;
import java.rmi .•;
public class TickerPullClient extends Ticker {
protected QuoteServer server ;
(The initQuotes () method on page 623)
(The updateQuotes O method on page 624)
}

In the ini tQuotes O method, the client looks up the quote erver.

public void initQuotes() {


try {
server• (QuoteServer)
Naming . lookup ( "rmi: / / 11 + getCodeBase () . get Host O +
11 / QuoteServer 11 ) ;

} catch (Exception e) {
e .printStackTrace ();
}
}
62 4 ■ Distributed Computing

In the updateQuotes () method, the client invokes a remote method, server


. getQuote () , and updates the quote array with the new quotes.

teaQllotNO mediod ot1be Tl


protected void updateQuotes() {
inti, j;
try {
StockQuote newQuote[] = server.getQuote();
for (j = O; j < newQuote.length; j ++) {
for (i = O; i < quote.length; i++) {
if (newQuote[j].name.equals(symbol[i])) {
quote[i] = Integer.toString(newQuote(j] . quote);
}
}
}
} catch (Exception e) {
e . printStackTrace () ;
}
super. updateQuotes () ;
}

This completes the implementation of the RMI stock ticker client using the client
pull strategy.

The RMI Server Push Implementation


In the server push implementation, a client contacts the server and provides a watch
list. The client also retrieves the current quotes of the companies that it watches. The
server keeps track of all the active clients. When the quote of a company changes, it
contacts (also known as a call-back) all the clients that are watching the company.
Thus the server and the clients are all remote objects. Each client has a reference to
the server, and the server maintains references to all the active clients. We have two
remote interfaces: one for the server and one for the clients. The structure of the design
is shown in Figure 12.9.

The RMI Push-Server and Client-Contract Interfaces


The contract interface for the push server extends the QuoteServer interface. The
watch() method lets the clients provide their watch lists to the server.

import java.rmi.•;
public interface QuotePushServer extends QuoteServer {
void watch(QuoteClient client, String[] list)
throws RemoteException;
}

The QuoteClient interface defines a call-back method supported by the clients.


The newQuote () method allows the push server to inform the clients of new quote
of the stocks that they are watching.
12.2 Remote Method Invocation • 625

Figure 12.9
I Remote
LS
/ UnicastRemoteObject

The server push I


I
I
design of the stock
ticker applet and Ticker QuoteServer
the quote server, getQuoteO
using RMI. ~
I
I
I

TickerPushCllent OuotePushServer
newauoteo watchO

QuoteC/ient l---------01 QuotePushServerlmpl


newQuoteO getQuote()
watchO

import java.rmi.*;
public interface QuoteClient extends Remote {
void newQuote (StockQuote newQuote)
throws RemoteException;
}

The RMI Stock Quote Push Server


The QuotePushServerimpl class implements the QuotePushServer interface.
The field clients is a set of active clients. Each element in the clients et i
an instance of the inner class Clientinfo which contain a remote reference to
the client and the client's watch list. The getQuote O method return. the array of
current quotes. The watch O method create a Client Info object and add it to the
clients set.

import java.util.*;
import java.rmi.•;
import java .rmi .server .UnicastRemoteObject;
public class QuotePushServerimpl extends UnicastRemoteObject
implements QuotePushServer {
public QuotePushServerimpl () throws java.rmi .RemoteE..~ception {}
protected StockQuote quote [)= {
nev StockQuote ( 11 IBM 11 • 100),
new StockQuote( 11 Sun11 , 100),
6 26 • Distributed Computing

new StockQuote("Intel" , 100),


new StockQuote("Apple", 100) };
protected Set clients= new HashSet();
class Clientinfo {
Clientinfo(QuoteClient client, String[] watch) {
this.client= client;
this . watch= watch;
}
QuoteClient client ; // remote reference to a client
String watch[] ;
}

public StockQuote[] getQuote() throws java . rmi .RemoteException {


return quote;
}

public void watch(QuoteClient client, String[] list)


throws java .rmi.RemoteException {
clients.add(new Clientlnfo(client, list));
}

(The main O method on page 626)


(The moni torQuotes () method on page 626)
}

The main() method creates an instance of QuotePushServerimpl and binds


it to the name QuotePushServer. At the end, the monitorQuotes () method is
invoked, which continuously monitors changes in the stock quotes.

1be main() method of the QuotePuahServer l dasl


public static void main(String[] args) {
System . setSecurityManager(new RMISecurityManager());
try {
QuotePushServerlmpl obj= new QuotePushServerlmpl() ;
Naming.rebind("QuotePushServer", obj);
monitorQuotes();
} catch (Exception e) {
e . printStackTrace() ;
}
}

The moni torQuotes () method simulates the changes to the quotes with a
random number generator. When the quote of a stock is changed, it iterates through
the clients set. For each client that is watching the stock, the newQuote () method
is invoked through the remote reference to the client.

The ■onitorQuotea () method of the QuotePuehSenerI■ l class


public void monitorQuotes() {
while (true) {
try {
Thread.currentThread( ) .sleep ( l0000);
12.2 Remote Method Invocation • 627

for (inti• 0; i < quote.length; i++) {


int dq • O;
if (Hath . random()< 0.5) {
dq • (int)((Hath.random() - 0 . 4) • (10 .0 * Hath.random()));
}
if (dq > 0) {
quote[i].quote +- dq;
Iterator iter • clients.iterator();
while ( i ter. has Next O) {
Client!nfo ci • (Clientlnfo) iter.next();
for (int k • 0; k < ci . watch . length; k++) {
if (ci .vatch[k] . equals(quote[i] . name)) {
ci.client.newQuote(quote[i]);
break ;
}
}
}
}
}
} catch (Exception e) {
e .printStackTrace();
}
}
}

This completes the RMI stock quote server implementation using the server push
strategy.

The RMI Stock Ticker Push Client


The TickerPushClient class implements the remote client interface Quote-
Client. ThenewQuote () method is remotely invoked by the quote server. It updates
the quote array, which is local to each client. The ini tQuotes O method locates
the quote server and sends the server the watch list by invoking the watch() method
of the server. Note that it passes itself, this, as the argument to the remote method
invocation. The object reference to a remote object is automatically converted to a
remote object reference when it is passed as an argument to a remote method in-
vocation. Furthermore, to allow the server to invoke the client's call-back method
newQuote (), the client must be exported as a remote object by calling the static
method export Object () of the UnicastRemoteObj ect class.

dllllt
import java.awt.•;
import java.util . *;
import java.io .•;
import java.rmi .•;
import java.rmi . server . •;
public class TickerPushClient extends Ticker
implements QuoteClient {
628 • Distributed Computing

public void initQuotes() {


try {
UnicastRemoteObject.exportObject(this);
QuotePushServer obj= (QuotePushServer)
Naming. lookup (
11
/ + getCodeBase () . getHost () +
/
11

"/QuotePushServer");
obj.watch(this, symbol);
StockQuote newQuote[] = obj.getQuote();
for ( int j = O; j < newQuote.length; j ++) {
for (inti= O; i < n; i++) {
if (newQuote[j] .name .equals(symbol[i])) {
quote[i] = Integer.toString(newQuote[j] .quote);
}
}
}
} catch (Exception e) {
e . printStackTrace();
}
}

public void newQuote(StockQuote newQuote) throws


java.rmi.RemoteException {
for (inti= O; i < n; i++) {
if (newQuote.name.equa ls(symbol[i] )) {
quote[i] = Integer.toString(newQuote .quote);
}
}
}
}

This completes the implementation of the RMI stock ticker client using the server
push strategy.

11.3 JAVA DATABASE CONNECTIVITY

Java database connectivity (JDBC) allows Java applications to interface with rela-
tional databases, which are widely used today. A wealth of information is stored in
relational databases. Through JDBC, Java applications can access information stored
in existing databases and share information with applications written in other lan-
guages.
Java database connectivity is a mechanism that allows Java programs to access
relational databases. It is a simple Java interface for Structured Query Language
(SQL), the standard language for accessing relational databases . The JDBC interface
supports full access to relational databases, including creating new tables, m odifying
existing tables, inserting and updating data in existing tables, querying a databa e,
and retrieving meta data (information about a database and its conte nts).
12.3 Java OatabaK Connectivity • 629

A key component in the implementation of JDBC is the JDBC driver; which


communicates between Java applications and the databases. The following table
summarizes the main characteristics of the four types of JDBC drivers:3

Driver Description

1. IDBC-ODBC bridge Provides JDBC access via most ODBC


drivers
2. Native-AP! (partially Java) Converts IDBC calls to DBMS calls
3. Net-protocol (pure Java) Translates JDBC calls into a DBMS
independent net protocol. which is then
translated to a DBMS protocol by a net
server. The net server can connect all its
Java clients to several different databases
4. Native-protocol {pure Java) Converts IDBC calls to the network
protocol used by the DBMS directly

An Overview of the Structured Query Language


The SQL is a programming language for accessing relational databases. Mo t
industrial-strength relational databases support SQL. Some of the commonly u ed
SQL commands are summarized in the following table:

Command Description

INSERT Inserts new row( ) in a table


DELETE Remove row( ) from a table
UPDATE Modifie the values of existing table rm ( )
SELECT Retrieves databa e information based on a condition
CREATE Create a new databa e object
DROP Remove an e:u ting database object
ALTER Alters the format of an cxi ting database object

3. For more informnlion about JDBC drivers and lheir u uilability, cupnbilitics, and limitations, see
http ://java . sun . com/ .
630 • Distributed Computing

Assume that we want to create the following table in a relational database:

name address city state zip email creditCard orderno

M. Jordan 549 E. Surf Chicago IL 60657 mjordanOnba.com Visa 102219985502


s. Pippen 11 S . 59th Oakbrook IL 60606 spippenOnba.com Discover 103119983212
D. Rodman 234 N. 3rd Chicago IL 60030 drodmanOnba . com MasterCard 103119983223
s. Kerr 4010 Pine Los Angeles CA 90507 skerrOnba . com Visa 010219991201
R. Harper 234 N. 3rd New York NY 20304 rharperOnba.com MasterCard 012019992013

The following SQL statement creates a new table called Beetles. It specifies
the columns that the table must have and declares a data type for each. For example,
the column specification name VARCHAR(35) defines a 35-character column called
name.

CREATE TABLE Beetles (name VARCHAR(35),


address VARCHAR(35),
city VARCHAR(20),
state VARCHAR(2),
zip VARCHAR(5),
email VARCHAR(30),
creditCard VARCHAR(15));

Note that the orderno column is missing from the Beetles table. We can add
the column to the table by using the SQL statement

ALTER TABLE Beetles ADD orderno VARCHAR(12);

After the table is built, rows are added to it with the INSERT command. The following
SQL statement adds a row to the Beetles table:

INSERT INTO Beetles VALUES('Michael Owen',


'123 Elmwood Street ' ,
'Lake Forest' ,
'IL', 1 65431 ' ,
'mowenOaol . com' ,
'MasterCard' ,
'112819981304');

The SELECT statement retrieves data from a database, based on a specified


criterion. When no criterion is supplied, as in the following example, all the rows
will be returned from the specified table:

SELECT* from Beetles ;


12.3 Java Database Connectivity • 631

The following example would select every row from the Beetles table where the
name column contained the value "Michael Owen."

SELECT* from Beetles WHERE name= 'Michael Owen';

For the remainder of this section, familiarity with SQL and relational databases
is assumed. For detailed discussions of SQL and relational databases see Bowman
et al. (1996] and Darwen and Date (J 997].

An Overview of JDBC
The interfaces and classes of JDBC are contained in the j ava. sql package. The
commonly used JDBC interfaces and classes are summarized in the following table:

Interface/Class Description

Dri verManager Manages the loading of JDBC drivers and upports


new database connections
Connection Represents database connections
Statement Represents SQL statements to be passed to the DBMS
via an already established connection
ResultSet Provides access to the results of a query. as well as
information about the database
ResultSetMetaData Provides information about the columns in a
ResultSet

Using JDBC involves establishing a connection with a database. by mean of


the Dri verManager and Connection clas e , u ing Statement objects to pa
SQL commands to a DBMS, and proce sing the re ults of an SQL query, which are
captured as Rasul tSet objects.

Establishing a Connection with a Database


The Dri verManager clas is used for loading and managing JDBC driver . A JDBC
driver must be loaded before any other activitie can take place. The JDBC driver
are loaded, using dynamic class loading, a follm :

Class.forName(JDBCDriverName);

For example, the following tatement load the JDBC- ODBC Bridge driver
provided by Sun Micro y tem :

Class.forName("sun .j dbc . odbc . JdbcOdbcDriver" );


63 2 • Distributed Computing

JDBC uses URLs to locate databases. JDBC URLs are presented in one of two
formats :
■ For a database on the local machine:
jdbc :subprotocol :subname

• For a database on a network host:


jdbc:subprotocol: //host [ :port] /subname

The components of a JDBC URL are summarized in the following table:

Component Description

subprotocol Specifies a driver or a database connectivity mechanism that may


be supported by one or more drivers. A common example of a
subprotocol name is odbc
subname Identifies a database

Instances of the Connection class represent open database connections.


Connection objects are created by using a static method getConnection () of the
Dri verManager class. Toe getConnection O method takes a JDBC URL, a user
ID, and a password as parameters. In the following example the ODBC subprotocol
is used to access a database named myDatabase:
String url = "jdbc:odbc:myDatabase";
Connection conn=
DriverManager.getConnection(url, "myID", "myPassword");

Once the connection to a database has been established successfully, a


Connection object is returned. It can be used to query the database. The most com-
monly used methods of the Connection interface are summarized in the following
table:

Method Description

createStatement O Returns a new Statement object for executing SQL


statements
commit () Commits (makes permanent) any database change that
have been made since the last call to commit . A call to
commit al o releases any database locks held by thi
Connection object
close() Releases the DBMS and any other JDBC resources
currently being used by this Connection object
12.3 Java Database Connectivity • 633

Querying the Database


Instances of the Statement class are used to compose SQL commands and send them
to a DBMS. Statement objects can be obtained from an opened database connection:
Statement stmt = conn . createStatement();

The commonly used methods of the Statement interface are summarized in the
following table. The parameter sql is a string that represents a SQL statement.

Method Description

executeQuery(sql) Executes the statement sql that retrieves data from the
database and returns a ResultSet object
executeUpdate (sql) Executes the statement sql that updates the database (that
is, one of INSERT, UPDATE, or DELETE statements) and
returns an integer indicating the number of rows affected
by the statement
close O Releases the DBMS and any other JDBC re ources cur-
rently being used by this Statement object, immediately
rather than relying on automatic closure by the garbage
collector

Using the Beetles table example, the following statements create the table,
using JDBC:
String createString =
"CREATE TABLE Beetles (name VARCHAR(35), "+
"address VARCHAR(35), 11 +
"city VARCHAR(20), 11 +
"state VARCHAR (2) , 11 +
"zip VARCHAR(5), " +
"email VARCHAR(30), " +
"creditCard VARCHAR(15))";
stmt.executeUpdate (createString);

A query on the Beetles table can be made as


ResultSet rset = stmt.executeQuery("SELECT name, email FROM Beetles");

It retrieves the name and email column of the Beetles table.

Processing the Results


The result of a query are tored a a et of rows and column in a Resul tSet object.
The methods of the Resul tSet interface are summarized in Table 12. l .
The row of the Resul tSet object can be acce sed by u ing a combination of the
next () method and the get Type () method. The next () method moves the cursor
to the next row of a Resul tSet object and make that the current row (i.e., the row
634 ■ Distributed Computing

t'AILI 11.1
Methods of the Resul tSet Interface

Method Description

next() Advances to the next row of the result set; returns true if
successful or false when there are no more rows in the result
set
getType(i) Returns the value of the ith column. Type is the data type of the
column, and it can be one of
Byte, Boolean, Double, Float, Int, Long, or String
among others. The return value is of the corresponding type.
get Type (name) Same as the preceding method, except that it retrieves the values
of a column by its name
void close O Releases the DBMS and any other JDBC resources currently
being used by this ResultSet object

that is currently available for processing). Initially, the cursor is positioned just above
the first row of a Resul tSet object. Invoking next() moves the cursor to the first
row and makes it the current row. Subsequent calls to next () cause the cursor to
be moved down through the result set, one row at a time, from top to bottom. The
next() method will return false when there are no more rows. There are two ways
to identify the columns in the current row: by the column name or by the column index.
For example, getString(1) returns the first column of the result set as a String
object, and get String ( 11 name 11 ) returns the name column as a String object. The
following while loop iterates through the rows of the Resul tSet object, displaying
the name and email fields as it goes:

while (rset .next()) {


String name== rset.getString("name");
String email = rset . getString("email");
System . out.pri ntln (name + ", "+ email);
}

The following is an example of what the output might look like.

M. Jordan, mjordanOnba . com


S. Pippen, spippenOnba.com
D. Rodman, drodmanOnba . com
S. Kerr, skerrOnba . com
12.3 Java Database Connectivity • 635

EXAM P LE 1 2 . 7 Building a Table with JDBC

PURPOSE
Illustrate the basic concepts of JDBC.

DESCRIPTION
This program builds a new Beetles table, inserts a test record into the table, and queries
the table.

SOLUTION
BaDd JDBC Table
import java . sql .•;
import java . io .•;
import java.util.Date ;
public class BuildBeetlesTable {
public static void main (String args O)
throws SQLException, IOException {
System.out.print( 11 \n Loading JDBC-ODBC driver . .. \ n\ n") ;
try {
Class . f orName ( 11 sun . j dbc . odbc . J dbcOdbcDri ver 11 ) ;
} catch(ClassNotFoundException e) {
e.printStackTrace() ;
System.exit(l);
}
System.out.print( 11 Connecting to Orders database ... \n\n");
String url = 11 jdbc :odbc:DrdersDriver 11 ;
Connection conn=
DriverManager. getConnection (url, 11 rPasenko", "mpf98eub") ;
System .out .print ( 11 Building new Beetles table . .. \ n\ n");
String createString =
"CREATE TABLE Beetles (name VARCHAR (35) , 11 +
"address VARCHAR(35) , 11 +
"cit y VARCHAR (20), " +
"state VARCHAR (2), " +
"zip VARCRAR (5) , 11 +
"email VARCHAR(30), 11 +
"credi tCard VARCHAR(15))";
stmt . executeUpdate ( createStri ng);
System . out.print( "Inserting test r ow in Beetles table . . . \ n\ n") ;
String insertStri ng =
"INSERT INTO Beetles VALUES ('Michael Oven ' , " +
" ' 123 Elmvood Street ' , " +
"'Lake Forest 1 , " +
111 I L I . II +

II 65431 > II +
1 1

11 1
mowenOaol . com' , 11
+
111
MasterCard ') 11 ;
636 • Distributed Computing

stmt .executeUpdate(insertString);
ResultSet rset = stmt . executeQuery("SELECT * FROM Beetles");

while( rset . next() ) {


System .out.print(""+ rset .getString( "name")+" ");
System.out.print(rset .getString ( 2) + ", ");
System . out . print(rset.getString ( 3) + ", ");
System.out . print (rset.getString ( 4) + ",\n");
System.out . print(rset .getString( 5) + ", ");
System.out.print(rset.getString( 6) + ", ");
System.out . print(rset . getString( 7) + "\n");
}
System . out .print("\n Closing database connection ... ");
conn.commit();
stmt . close() ;
rset.close () ;
conn . close O ;
}
}

Following is the output of the BuildBeetlesTable program:

venus% java BuildBeetlesTable


Loading JDBC-ODBC driver .. .
Connecting to Orders database ...
Building new Beetles table ...
Inserting test row in Beetles table ...
Michael Owen, 123 Elmwood Street, Lake Forest, IL,
65431, mowenOaol.com, MasterCard
Closing database connection .. .

EXAMPLE 1 2. 8 Modifying a Table with JDBC

PURPOSE
This example shows how to modify a table with JDBC.

DESCRIPTION
This program modifies the structure of the Beetles table, updates the test record in
that table, displays the modified table contents, and then deletes the test record.

SOLUTION
JDBC~Table
import java . util.Calendar;
import java . sql . *;
import java . io . •;
12.3 Java Database Connectivity • 63 7

class ModifyBeetlesTable {
public static void main( String args O )
throws SQLException, I OException {
try {
Class . forName("sun . jdbc . odbc . JdbcOdbcDriver") ;
} catch(ClassNotFoundException e) {
e.printStackTrace();
System . exit(! ) ;
}
System . out . print("\n Connecting to Beetles table ... \n\n" );
String url = "jdbc : odbc:OrdersDriver ";
Connection conn=
DriverManager .getConnection (url,"rPasenko" , "mpf98eub");
Statement stmt = conn.createStatement ();
System.out.print("Modifying Beetles table . .. \ n\ n ");
String updateString =
"ALTER TABLE Beetles ADD ORDERNO VARCHAR(12)" ;
stmt . executeUpdate(updateString) ;
Calendar cal= Calendar.getlnstance ();
String orderNo = String .valueOf (cal . get (cal .MONTH) +l ) +
String .valueOf (cal . get (cal.DAY_ OF_ MONTH)) +
String . valueOf (cal . get (cal . YEAR)) +
String.valueOf (cal . get (cal.HOUR)) +
String . valueOf (cal.get (cal . MINUTE)) +
String. valueOf (cal . get (cal .SECOND));
updateString = "UPDATE Beetles SET ORDERNO = " +
orderNo +"WHERE NAME= 'Michael Owen '";
stmt . executeUpdate (updateString);
ResultSet rset = stmt.executeQuery ( "SELECT * FROM Beetles");
System.out . print (" Displaying tabl e contents ... \ n\ n");
while (rset .next ()) {
System . out.println ( " " +
rset . getStri ng ("name" ) + ", 11 +
rset.getString (" address" ) +" 11
+
rset.getString ( "city") + ", " +
rset .getString (" state ") + "\n " +
rset .getStri ng ( 11 zi p 11 ) + ", 11 +
rset.getString (" email 11 ) + " , 11 +
rset . getString ( 11 creditCard") + 11 11 +
rset . getString (" orderno") );
}
System .out.print ( "\ n Del et ing t est re cord ... \ n\ n 11 ) ;
updateString = "DELETE FROM Beetl es WHERE NAME= ' Michael Owen '";
stmt . executeUpdate (updateStri ng);
System.out.print ( 11 Clos i ng connect ion . . . \ n\ n");
conn . commit O ;
stmt . close O ;
rset . cl os e O ;
conn . close O;
}
}
638 • Distributed Computing

The following are the results of the Modif yBeetlesTable program:

venus¼ java ModifyBeetlesTable


Connecting to Beetles table . ..
Modifying Beetles table .. .
Displaying table contents .. .
Michael Owen, 123 Elmwood Street, Lake Forest, IL
65431, mowen@aol.com, MasterCard, 179917556
Deleting test record .. .
Closing connection .. .

EXAMPLE 1 2. 9 Adding Database Functionality to the Order Applet

PURPOSE
This example demonstrates database updates with JDBC.

DESCRIPTION
This example extends the Order applet from Example 8.13 so that it includes database
functionality. In this version, the applet writes each order to the Order database.

SOLUTION
We extend the Order class in Example 8.13. The OrderDialogclass will be replaced
by a new class JdbcOrderDialog.

JDBC Order Dialo&


import java. util.Calendar;
import java. awt.event .•;
import javax . sving.•;
import java.sql . •;
import java.io.•;
public class JdbcOrderDialog extends OrderDialog {
public JdbcOrderDialog(JFrame owner) {
super(ovner);
}

protected ActionListener makeButtonHandler() {


return new JdbcButtonHandler();
}

class JdbcButtonHandler implements ActionListener {


public void actionPerformed(ActionEvent evt) {
JButton button• (JButton) evt.getSource();
String label• button . getText ();
if (" Ok". equals (label )) {
try {
insertNewOrder ();
} catch(IOException x) {
e .printStackTrace( );
}
12.3 Java Database Connectivity • 639

dialogPanel. reset O ;
setVisible(false);
}
}

public void insertNewOrder() throws SQLException, IOException {


System . out.print( "Loading JDBC OCI driver .. . \n\n\n" );
try {
Class .forName( "sun.jdbc.odbc.JdbcOdbcDriver" );
} catch (ClassNotFoundException e) {
e. printStackTrace O ;
System . exit(!);
}
String url • "jdbc :odbc:OrdersDriver";
Connection conn•
DriverManager.getConnection(url, "rpasenko","mpfBeub");
Statement stmt = conn . createStatement() ;
String creditCard • null;
if (dialogPanel .visaBox . isSelected()) {
creditCard .. "Visa" ;
} else if (dialogPanel.mcBox . isSelected()) {
creditCard :s "MasterCard";
} else if (dialogPanel . discoverBox.isSelected ()) {
creditCard • "Discover";
}

Calendar cal .. Calendar .getlnstance () ;


String orderNo • String .valueOf ( cal .get (cal . HONTii) +l ) +
String . valueOf (cal .get (cal . DAY_ OF_ HONTii)) +
String .valueOf (cal .get (cal . YEAR)) +
String .valueOf (cal .get (cal.HOUR)) +
String.valueOf (cal.get (cal . HINUTE)) +
String .valueOf (cal .get (cal . SECOND));

String insertString;
insertString • "INSERT INTO Beetles VALUES (" +
11
' " + dialogPanel. nameField . get Text () + 11 ' , " +
11
" ' + dialogPanel. addressFi eld . getTert () + " ' , " +
11
' " + dialogPanel . ci t yField . getText () + 11 ' , 11 +
" • " + dialogPanel. stateField . getText () + 11 ' , 11 +
11
' " + dialogPanel. zipField . getText () + 11 ' , 11 +
11
' " + dialogPanel . emailField . getText () + " ' , 11 +
o tn
+ creditCard + II I • II +
" ' " + orderNo + II I )11 j
stmt.executeUpdate (insertString);
stmt . close ();
conn .commit ();
conn . cl ose O;
}
}
}
640 • Distributed Computing

The Jdbc0rderDialog class extends the 0rderDialog class in Example 8.13.


The new method insertNew0rderO open a database connection, builds an SQL
string for inserting an order in the Beetles table, executes the SQL command, and
then closes the database connection and other JDBC resources.
We have now introduced most of the basic aspects of JDBC. For more complete
coverage of JDBC, see Hamilton et al. [1997]. a

11.4 COMMON OBJECT REQUEST BROKER ARCHITECTURE

Socket-based communication and remote method invocation are adequate and ef-
fective mechanisms for distributed Java applications. However, the reality of today's
computing environment is that the majority of existing applications are written in lan-
guages other than Java. In this section we briefly discuss a mechanism for interfacing
Java applications with non-Java applications in distributed computing environments:
Common Object Request Broker Architecture (CORBA).
CORBA is an open, distributed object-computing infrastructure being standard-
ized by the Object Management Group (OMG), an industrial consortium that com-
prises 500 software vendors, developers, and users.
CORBA simplifies the development of distributed applications by providing a
unified view of all distributed systems. The CORBA application framework provides
interoperability between objects in heterogeneous distributed environments, where
the objects can be implemented in different languages, such as Java, CIC++, Ada,
and COBOL.
The basic architecture of CORBA resembles that of RMI. The participants in a
CORBA application are similar to participants in a Java RMI application:
Server: An object that provides services to objects residing on remote hosts.
Service contract: An interface that defines the services provided by the server.
Client: An object that uses services provided by objects residing on remote hosts.
Stub: An object that resides on the same host as the client and serves as a proxy,
or surrogate, of the remote server.
Skeleton: An object that resides on the same host as the server, receives requests
from the stubs, and dispatches the requests to the server.
The centerpiece of CORBA is the Interface Definition Language (IDL). The
CORBA clients and servers can be implemented in different languages. The ervice
contract interface of a CORBA server is defined in a language-neutral fa hion, u ing
IDL. The IDL compliers translate the interfaces written in IDL to variou implemen-
tation language , such as Java and CIC++. The IDL compilers also generate tub and
skeletons in various implementation languages.
A key component of the CORBA architecture is the Object Request Broker
(ORB). The ORB defines the mechanism and interfaces that enable objects to make
requests and receive respon es in a distributed environment. The ORB provides an
infrastructure allowing objects to communicate independently of ·pecific platform
Chapter Summary • 641

and implementation languages. The basic structure of a CORBA application is shown


in Figure 12.10.
A CORBA application consists of a set of collaborating objects. When a client
requests a service, the ORB will first locate a server that implements the requested
service. The ORB is also responsible for sending the arguments and call information to
the server, invoking the method on the server, and sending the results back to the client.
The client need not be concerned with the server object's location, programming
language, or operating system.
CORBA provides flexibility and interoperability in today's increasingly complex
computing environment. It lets programmers choose the most appropriate operating
system, execution environment, and programming language to use for each compo-
nent that they are constructing. Moreover, CORBA allows the integration of Java
applications with existing applications. In a CORBA-based solution, legacy compo-
nents can be modeled by using IDL. Thus CORBA is a giant step on the road to
interoperability in distributed object-oriented applications.
A complete discussion of CORBA is beyond the scope of this book. For more
information on CORBA see Orfali and Harkey [1998].

Figure 12. 10 Client Server


The basic structure
of a CORBA appli-
cation.
Stub Skeleton
ORB ORB

CHAPTER SUMMARY

■ Distributed applications con i t of component that re ide on different network


hosts.
■ Sockets are the end point of two-way connection bet, een t, o di tributed
components that communicate with each other. A connection mu t be explicitly
e tablished by both partie . There are two kind of ocket : lient . ocket and
erver ocket . A client ocket can be u ed to end and receive data. A server
socket wait for reque ts for connection from client . Server ockets can be
created only by Java app , not b applet . Client ocket. can be created and used
by both Java app and applet .

Remote method invocati n (RMI) make the network ··cran parent." It allows
distributed component to be manipulated (alma t) a if they were all on the same
host. Programmer need not deal at all with interho t communications. They are
64 2 • Distributed Computing

handled implicitly by the run-time environment supporting the remote method


invocation. The key participants of RMI architecture are the following:
Server: An object that provides services to objects residing on remote hosts.
Service contract: An interface that defines the services provided by the server.
Client: An object that uses the services provided by objects residing on remote
hosts.
Stub: An object that resides on the same host as the client and serves as a proxy,
or surrogate, of the remote server.
Skeleton: An object that resides on the same host as the server, receives requests
from the stubs, and dispatches the requests to the server.
Clients and servers are written by programmers, whereas stubs and skeletons are
automatically generated by the RMI compiler from the server code.
■ Java database connectivity (JDBC) allows Java applications to interface with
relational databases, which are widely used today. A wealth of information
is stored in relational databases. Through JDBC, Java applications can access
information stored in existing databases and share information with applications
written in other languages. Using JDBC involves establishing a connection with
a database by means of the DriverManager and Connection classes, using
Statement objects to pass SQL commands to a DBMS, and processing the
results of an SQL query, which are captured as Resul tSet objects.
■ The Common Object Request Broker Architecture (CORBA) is an object-
oriented framework that supports interoperability among objects written in
different languages and running on different platforms. CORBA support is avail-
able for most programming languages, including Java, and platforms used for
industrial applications. CORBA is especially useful in interfacing applications
developed in modem languages, such as Java and C++, with legacy systems.

F.URTHIR RIADINGI

Farley, J. (1998). Java Distributed Computing. O ' Reilly.

Reese, G. (2000). Database Programming with JDBC and Java, 2nd ed., O'Reilly.

f IXIRCIIII

12.1 Use sockets to implement a two-way chat • A Send button for sending outgoing mes-
application consisting of a server and two sages
clients. The client program has a graphical • A Clear outgoing messages button
user interface that consists of the following: • A Clear incoming messages button
• A text area for typing outgoing messages • A Quit button
• Another text area for displaying incoming 12.2 Enhance Exercise J2.1 to support multiple
messages clients.
12.3 Use RMI to implement Exercises 12.1 and 12.2.
Projects • 643

PROJICTI '

12.1 Develop a distributed version of the tic-tac-toe 12.3 Develop a distributed version of the drawing
game in Section 11.3 (p. 571) using sockets or pad using sockets or RMI. Several drawing
RMJ. It should allow two players on different pad clients can run simultaneously on different
hosts to play the game remotely. hosts. Drawings by all the clients should be
12.2 Develop a distributed version of the multiplayer displayed on the same canvas and should be
tic-tac-toe game in Project I I. I [p. 585] using visible to all the clients.
sockets or RMI. It should allow multiple players
on different hosts to play the game remotely.
Summary of the APPLET Tag

The <applet> tag is used to embed a Java applet in an HTML page. The <applet>
tag has the following form:

<applet
(align = alignment]
[alt = alternate-text]
[archive = archived-file]
code = applet-filename
[code base = applet-url]
height = pixel-height
[hspace = horizontal-pixel-space]
[name = applet-name]
[vspace = vertical-pixel-space]
width = pixel-width
>
[<param name = param-name 1 value = param-value, >]

[<par am name = param-11ame11 value = para111-val11e


11 >]
[altemate-html-co11te11t]
</ applet>

645
• Summary of the APPLET Tag

The attributes code, width, and height are required. The rest are optional. The
attributes of the <applet> tag are summarized in the following table:

Attribute Description

codebase Specifies the base URL of the applet. It is a relative path to the
directory containing the current HTML document. The default
value is the same directory as the current HTML document.
code Specifies the class name of the applet. The location of the class
file is specified by the code base attribute.
width Specifies the width of the applet's display area in pixels.
height Specifies the height of the applet's display area in pixels.
alt Specifies alternative text that should be displayed by browsers
that understand the <applet> tag but do not support Java.
name Specifies a name for the applet instance, making it possible for
applets on the same page to communicate with each other.
align Specifies the applet's position with respect to surrounding text.
vspace Specifies the top and bottom margins in pixels.
hspace Specifies the left and right margins in pixels.
param Specifies the parameters to the applet. Each parameter is
specified by a name- value pair. The param attribute may occur
multiple times.
archive Specifies a comma-separated list of Java archives (JAR files)
containing classes and other resources.

The alternate-html-content section specifies alternative text that should be dis-


played by browsers that do not understand the <applet> tag.
Summary of Documentation
Tags

Documentation comments are special comments in Java source code that are delimited
by I** and *I. They are processed by the javadoc utility to generate API documen-
tation. The tags that can be used in the documentation comments are summarized a
follows :

@author name
Adds an Author entry to the generated docs when the - author option of
j avadoc is used. A doc comment may contain multiple Oauthor tags or
multiple names per tag.
@deprecated description
Adds a comment indicating that thi method has been deprecated since the
specified version. The first entence of the Odeprecated de cription should
tell the user when the method was deprecated and what can be used a. a
replacement. An Olink tag should be included that points to the replacement
method.
@exception name description
Adds an exception de cription to the £rceptions ection of the generated API
documentation. An Oexception tag hould be included for each checked
exception declared in the throw clau e of a method. It is synonymous with
the Othrows tag.
647
648 • Summary of Documentation Tags

@link name
Inserts a link that points to the specified name. Multiple link tags can be
used.
Oparam name description
Adds a parameter description to the Parameters section of the generated
API documentation. An @param tag should be included for each parameter
of a method.
Oreturn description
Adds a Returns section to the generated API documentation that describes
the return value of a method. The @return tag should be used for all
methods except methods that return void or constructors.
@see text
Adds a text entry. No link is generated.
@see package. class#member
Adds a link that points to a specific name in the API documentation.
Osee <a href=url>text</a>
Adds a link to the specified URL.
@serial description
Describes the serializable fields .
Osince text
Indicates the release or version number in which the feature was first
introduced.
@throws name description
Synonymous with the @exception tag.
@version text
Adds a Version section for specifying the version of the software that contains
this class or member when the -version option of j avadoc is used. Only
one @version tag is allowed per doc comment.
Summary of Java Naming
Conventions

The following is a ummary of the naming conventions u ed in Java. U ing the e


conventions can help produce more readable code and avoid name conflict .

Package Names
The first identifier of a package name con i ts of two or three letters that name an
Internet domain, such as com, edu, gov, mil, net, org. or a two-letter ISO country
code such as uk or jp. The following are ome example :
com . sun.java.corba
org.npr.pledge.driver
uk.ac.city .rugby .game
When defining package nan1e for local u e. you hould begin the first identifier
with a lowercase letter. The first identifier hould not be j ava, becau ·e it i reserved
for standard Java package .

Class and lnterface Names


De criptive noun or noun phra e, should be used for cl:L - t pe name and interface
type name (adjective that de cribe a behavior can al o be u ed a. interface name.).
In addition, capitalize the fir_t letter of ea h \ ord and u e mixed ca, e. The following
are some example, :
HttpSessionContext
HelloServlet

649
650 • Summary of Java ~aming Conventions

ServletlnputStream
Thread
PrintStream
Runnable
Cloneable

Method Names
Verbs or verb phrases should be used for method names. In addition, method names
should consist of mixed case, with a lowercase first letter. The first letter of any
subsequent words should be capitalized. The following are examples:

write
play
showContent

Other conventions for method names include the following:

■ When using a method to get or set an attribute thought of as variable Var, use
getVar and setVar. Examples are getDate and setDate.
■ When a method tests a boolean condition Cond about an object, it should be
named isCond. An example is isinterrupted.

Field Names
Field names (other than those that are final) should have names that are nouns, noun
phrases, or abbreviations of nouns. They should be in mixed case with a lowercase
first letter and the first letters of subsequent words capitalized. Examples include buf ,
pos, count, and bytesTransferred.

Constant Names
When naming the constants in interface types and the final variables of class types,
you should use a sequence of one or more words (or other appropriate parts of speech),
acronyms, or abbreviations. In addition, components should be all uppercase, and they
should be separated by the underscore character (_). Examples include MIN_ VALUE,
MAX_VALUE, MIN_RADIX, and MAX_RADIX.

Local Variable and Parameter Names


Local variable and parameter names are often short sequences of lowercase letters
that are not complete words, such as

■ an acronym, such as bg, for a variable holding a Color object used for the
background .
■ an abbreviation, such as buf, for a variable referring to a buffer.
■ a mnemonic term, such as i n and out , for input and output streams.
Summary of Jll'la Naming Conventions • 651

One-character local variable or parameter names should be used only for tempo-
rary and loop control variables, or where a variable holds an undistinguished value of
a type. Acceptable one-character names are as follows:

Name Type

b byte
C char
d double
e Exception
f float
i integer
j integer
k integer
1 long
0 Object
s String
V any type

You should not use local variable or parameter names that consi t of only two or
three uppercase letters, as they may conflict with the initial country codes and domain
names of unique package names.

You might also like