(ebook) Sams - Teach Yourself Java In 28 Days

(ebook) Sams - Teach Yourself Java In 28 Days -...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: www.GetPedia.com More than 500,000 articles about almost EVERYTHING !! Click on your interest section for more information : Acne q Advertising q Aerobics & Cardio q Affiliate Revenue q Alternative Medicine q Attraction q Online Auction q Streaming Audio & Online Music q Aviation & Flying q Babies & Toddler q Beauty q Blogging, RSS & Feeds q Book Marketing q Book Reviews q Branding q Breast Cancer q Broadband Internet q Muscle Building & Bodybuilding q Careers, Jobs & Employment q Casino & Gambling q Coaching q Coffee q College & University q Cooking Tips q Copywriting q Crafts & Hobbies q Creativity q Credit q Cruising & Sailing q Currency Trading q Customer Service q Data Recovery & Computer Backup q Dating q Debt Consolidation q Debt Relief q Depression q Diabetes q Divorce q Domain Name q E-Book q E-commerce q Elder Care q Email Marketing q Entrepreneur q Ethics q Exercise & Fitness q Ezine Marketing q Ezine Publishing q Fashion & Style q Fishing q Fitness Equipment q Forums q Game q Goal Setting q Golf q Dealing with Grief & Loss q Hair Loss q Finding Happiness q Computer Hardware q Holiday q Home Improvement q Home Security q Humanities q Humor & Entertainment q Innovation q Inspirational q Insurance q Interior Design & Decorating q Internet Marketing q Investing q Landscaping & Gardening q Language q Leadership q Leases & Leasing q Loan q Mesothelioma & Asbestos Cancer q Business Management q Marketing q Marriage & Wedding q Martial Arts q Medicine q Meditation q Mobile & Cell Phone q Mortgage Refinance q Motivation q Motorcycle q Music & MP3 q Negotiation q Network Marketing q Networking q Nutrition q Get Organized - Organization q Outdoors q Parenting q Personal Finance q Personal Technology q Pet q Philosophy q Photography q Poetry q Political q Positive Attitude Tips q Pay-Per-Click Advertising q Public Relations q Pregnancy q Presentation q Psychology q Public Speaking q Real Estate q Recipes & Food and Drink q Relationship q Religion q Sales q Sales Management q Sales Telemarketing q Sales Training q Satellite TV q Science Articles q Internet Security q Search Engine Optimization (SEO) q Sexuality q Web Site Promotion q Small Business q Software q Spam Blocking q Spirituality q Stocks & Mutual Fund q Strategic Planning q Stress Management q Structured Settlements q Success q Nutritional Supplements q Tax q Team Building q Time Management q Top Quick Tips q Traffic Building q Vacation Rental q Video Conferencing q Video Streaming q VOIP q Wealth Building q Web Design q Web Development q Web Hosting q Weight Loss q Wine & Spirits q Writing q Article Writing q Yoga q CONTENTS Teach Yourself Java in 21 Days Professional Reference Edition by Laura Lemay, Charles L. Perkins and Michael Morrison CONTENTS Introduction Day 1 An Introduction to Java Programming q What Is Java? q Java's Past, Present, and Future q Why Learn Java? r r Java Is Object Oriented r q Java Is Platform Independent Java Is Easy to Learn Getting Started Programming in Java r Getting a Java Development Environment r Installing the JDK and Sample Files r Configuring the JDK r Creating a Java Application r Creating a Java Applet q Troubleshooting q Summary q Q&A Day 2 Object-Oriented Programming and Java q Thinking in Objects: An Analogy q Objects and Classes q Behavior and Attributes file:///G|/ebooks/1575211831/index.htm (1 of 37) [11/06/2000 7:44:37 PM] CONTENTS r r Behavior r q Attributes Creating a Class Inheritance, Interfaces, and Packages r Inheritance r Creating a Class Hierarchy r How Inheritance Works r Single and Multiple Inheritance r Interfaces and Packages r Creating a Subclass q Summary q Q&A Day 3 Java Basics q Statements and Expressions q Variables and Data Types r Declaring Variables r Notes on Variable Names r Variable Types r Assigning Values to Variables q Comments q Literals r r Boolean Literals r Character Literals r q Number Literals String Literals Expressions and Operators r r More About Assignment r Incrementing and Decrementing r Comparisons r Logical Operators r Bitwise Operators r q Arithmetic Operator Precedence String Arithmetic file:///G|/ebooks/1575211831/index.htm (2 of 37) [11/06/2000 7:44:37 PM] CONTENTS q Summary q Q&A Day 4 Working with Objects q Creating New Objects r r What new Does r q Using new A Note on Memory Management Accessing and Setting Class and Instance Variables r r Changing Values r q Getting Values Class Variables Calling Methods r Class Methods q References to Objects q Casting and Converting Objects and Primitive Types r r Casting Objects r q Casting Primitive Types Converting Primitive Types to Objects and Vice Versa Odds and Ends r Comparing Objects r Determining the Class of an Object q Class and Object Reflection (Java 1.1) q The Java Class Library q Summary q Q&A Day 5 Arrays, Conditionals, and Loops q Arrays r Declaring Array Variables r Creating Array Objects r Accessing Array Elements r Changing Array Elements r Multidimensional Arrays file:///G|/ebooks/1575211831/index.htm (3 of 37) [11/06/2000 7:44:37 PM] CONTENTS q Block Statements q if Conditionals r The Conditional Operator q switch Conditionals q for Loops q while and do Loops r r q while Loops do...while Loops Breaking Out of Loops r Labeled Loops q Summary q Q&A Day 6 Creating Classes and Applications in Java q Defining Classes q Creating Instance and Class Variables r r Constants r q Defining Instance Variables Class Variables Creating Methods r r The this Keyword r Variable Scope and Method Definitions r Passing Arguments to Methods r q Defining Methods Class Methods Creating Java Applications r q Helper Classes Java Applications and Command-Line Arguments r Passing Arguments to Java Programs r Handling Arguments in Your Java Program q Summary q Q&A file:///G|/ebooks/1575211831/index.htm (4 of 37) [11/06/2000 7:44:37 PM] CONTENTS Day 7 More About Methods q Creating Methods with the Same Name, Different Arguments q Constructor Methods r r Calling Another Constructor r q Basic Constructors Overloading Constructors Overriding Methods r Creating Methods That Override Existing Methods r Calling the Original Method r Overriding Constructors q Finalizer Methods q Summary q Q&A Day 8 Java Applet Basics q How Applets and Applications Are Different q Creating Applets r r q Major Applet Activities A Simple Applet Including an Applet on a Web Page r r Testing the Result r q The <APPLET> Tag Making Java Applets Available to the Web More About the <APPLET> Tag r ALIGN r HSPACE and VSPACE r CODE and CODEBASE q Java Archives q Passing Parameters to Applets q Summary q Q&A file:///G|/ebooks/1575211831/index.htm (5 of 37) [11/06/2000 7:44:37 PM] CONTENTS Day 9 Graphics, Fonts, and Color q The Graphics Class r q The Graphics Coordinate System Drawing and Filling r r Rectangles r Polygons r Ovals r Arcs r A Simple Graphics Example r q Lines Copying and Clearing Text and Fonts r r Drawing Characters and Strings r q Creating Font Objects Finding Out Information About a Font Color r Using Color Objects r Testing and Setting the Current Colors r A Simple Color Example q Summary q Q&A Day 10 Simple Animation and Threads q Creating Animation in Java r r Starting and Stopping an Applet's Execution r The Missing Link: Threads r q Painting and Repainting Putting It Together Threads: What They Are and Why You Need Them r r q Writing Applets with Threads Another Look at the Digital Clock Reducing Animation Flicker r Flicker and How to Avoid It r How to Override update() file:///G|/ebooks/1575211831/index.htm (6 of 37) [11/06/2000 7:44:37 PM] CONTENTS r Solution One: Don't Clear the Screen r Solution Two: Redraw Only What You Have To q Summary q Q&A Day 11 More Animation, Images, and Sound q Retrieving and Using Images r r Drawing Images r A Note About Image Observers r q Getting Images Modifying Images Creating Animation Using Images r An Example: Neko q Retrieving and Using Sounds q Using Animation Packages r r q Sun's Animator Applet Dimension X's Liquid Motion More About Flicker: Double-Buffering r Creating Applets with Double-Buffering r A Note on Disposing Graphics Contexts r An Example: Checkers Revisited q Summary q Q&A Day 12 Managing Simple Events and Interactivity q Mouse Clicks r r An Example: Spots r q Mouse Down and Mouse Up Events Double-Clicks Mouse Movements r Mouse Drag and Mouse Move Events r Mouse Enter and Mouse Exit Events r An Example: Drawing Lines r Keyboard Events file:///G|/ebooks/1575211831/index.htm (7 of 37) [11/06/2000 7:44:37 PM] CONTENTS r The keyDown() and keyUp() Methods r Default Keys r An Example: Entering, Displaying, and Moving Characters r Testing for Modifier Keys and Multiple Mouse Buttons q The awt Event Handler q Summary q Q&A Day 13 Creating User Interfaces with the awt q An awt Overview q The Basic User Interface Components r r Buttons r Check Boxes r Radio Buttons r Choice Menus r q Labels Text Fields Panels and Layout r Layout Managers: An Overview r The FlowLayout Class r Grid Layouts r Border Layouts r Card Layouts r Grid Bag Layouts r Insets q Handling UI Actions and Events q Nesting Panels and Components r r q Nested Panels Events and Nested Panels More UI Components r r Scrolling Lists r Scrollbars and Sliders r q Text Areas Canvases More UI Events file:///G|/ebooks/1575211831/index.htm (8 of 37) [11/06/2000 7:44:37 PM] CONTENTS q Fun with Components q A Complete Example: RGB-to-HSB Converter r Designing and Creating the Applet Layout r Defining the Subpanels r Handling the Actions r Updating the Result r The Complete Source Code q Up and Coming in Java 1.1 q Summary q Q&A Day 14 Windows, Networking, and Other Tidbits q Windows, Menus, and Dialog Boxes r r Frames r Closing Windows r Menus r Dialog Boxes r Cursors r Window Events r q The awt Window Classes Standalone awt Applications Networking in Java r r Opening Web Connections r openStream() r Sockets r q Creating Links Inside Applets Changes to Sockets for Java 1.1 Other Applet Hints r The showStatus() Method r Applet Information r Communicating Between Applets q Summary q Q&A file:///G|/ebooks/1575211831/index.htm (9 of 37) [11/06/2000 7:44:37 PM] CONTENTS Day 15 Modifiers, Access Control, and Class Design q Modifiers q Controlling Access to Methods and Variables r Why Access Control Is Important r The Four Ps of Protection r Method Protection and Inheritance r Instance Variable Protection and Accessor Methods q Class Variables and Methods q Finalizing Classes, Methods, and Variables r Finalizing Classes r Finalizing Variables r Finalizing Methods q Abstract Classes and Methods q Summary q Q&A Day 16 Packages and Interfaces q Programming in the Large and Programming in the Small q What Are Packages? q Using Packages r r The import Command r Name Conflicts r q Full Package and Class Names A Note About CLASSPATH and Where Classes Are Located Creating Your Own Packages r r Create the Directory Structure r Use package to Add Your Class to a Package r q Pick a Package Name Packages and Class Protection What Are Interfaces? r r Abstract Design and Concrete Implementation r q The Problem of Single Inheritance Interfaces and Classes Implementing and Using Interfaces file:///G|/ebooks/1575211831/index.htm (10 of 37) [11/06/2000 7:44:37 PM] CONTENTS r r Implementing Multiple Interfaces r q The implements Keyword Other Uses of Interfaces Creating and Extending Interfaces r New Interfaces r Methods Inside Interfaces r Extending Interfaces r An Example: Enumerating Linked Lists q Summary q Q&A Day 17 Exceptions q Exceptions, the Old and Confusing Way q Java Exceptions q Managing Exceptions r r Protecting Code and Catching Exceptions r q Exception Consistency Checking The finally Clause Declaring Methods That Might Throw Exceptions r r Which Exceptions Should You Throw? r Passing On Exceptions r q The throws Clause throws and Inheritance Creating and Throwing Your Own Exceptions r r Creating Your Own Exceptions r q Throwing Exceptions Doing It All: Combining throws, try, and throw When and When Not to Use Exceptions r When to Use Exceptions r When Not to Use Exceptions r Bad Style Using Exceptions q Summary q Q&A file:///G|/ebooks/1575211831/index.htm (11 of 37) [11/06/2000 7:44:38 PM] CONTENTS Day 18 Multithreading q Thread Fundamentals q The Problem with Parallelism q Thinking Multithreaded r r q Points About Points Protecting a Class Variable Creating and Using Threads r The Runnable Interface r ThreadTester r NamedThreadTester q Knowing When a Thread Has Stopped q Thread Scheduling r Preemptive Versus Nonpreemptive r Testing Your Scheduler q Summary q Q&A Day 19 Streams and I/O q What Are Streams? q The java.io Package q Input Streams r r ByteArrayInputStream r FileInputStream r FilterInputStream r PipedInputStream r SequenceInputStream r q The Abstract Class InputStream StringBufferInputStream Output Streams r The Abstract Class OutputStream r ByteArrayOutputStream r FileOutputStream r FilterOutputStream r PipedOutputStream file:///G|/ebooks/1575211831/index.htm (12 of 37) [11/06/2000 7:44:38 PM] CONTENTS q Related Classes q Object Serialization (Java 1.1) q Summary q Q&A Day 20 Using Native Methods and Libraries q Why Use Native Methods? r Advantages of Using Native Methods q Disadvantages of Native Methods q The Illusion of Required Efficiency r r Just-in-Time Compilers r q Design First, Efficiency Later Simple Optimization Tricks Writing Native Methods r r Generate Header and Stub Files r Implementing the Native Library r q Write Your Java Code Using Your Library Tools and Techniques for Writing Native Implementations r Names r Accessing Java Objects r Calling Methods r Creating New Java Objects r Handling Exceptions r Dealing with Strings q Summary q Q&A Day 21 Under the Hood q -The Big Picture r q Why It's a Powerful Vision The Java Virtual Machine r An Overview r The Fundamental Parts file:///G|/ebooks/1575211831/index.htm (13 of 37) [11/06/2000 7:44:38 PM] CONTENTS r The Constant Pool r Limitations q The Bytecode Interpreter q Just-in-Time Compilers q The Class File Format q Method Signatures q The Garbage Collector r r The Solution r q The Problem Java's Parallel Garbage Collector The Security Story r Why You Should Worry r Why You Might Not Have To r Java's Applet Security Model r Signed Applets r Coming Up in Java 1.1 q Summary q Q&A Day 22 Java Programming Tools q Overview of the Standard JDK Tools q The Runtime Interpreter r r The OptionsArgument r q Usage The Non-Optimized Interpreter The Compiler r r The OptionsArgument r q Usage The Non-Optimizing Compiler The Applet Viewer r r The OptionsArgument r Commands r q Usage Profiling Java Applets The Debugger file:///G|/ebooks/1575211831/index.htm (14 of 37) [11/06/2000 7:44:38 PM] CONTENTS r r The OptionsArgument r q Usage Commands The Class File Disassembler r r q Usage The OptionsArgument The Header and Stub File Generator r r q Usage The OptionsArgument The Documentation Generator r r The OptionsArgument r q Usage Documentation Tags Visual Development Tools r Sun's Java WorkShop r Symantec Café r Microsoft Visual J++ r Natural Intelligence's Roaster r Rogue Wave Software's JFactory r Penumbra Software's Mojo r Aimtech's Jamba r Kinetix's Hyperwire q Summary q Q&A Day 23 Working with Data Structures in Java q Data Structure Fundamentals q The Standard Java Data Structures r Enumerations r Bit Sets r Vectors r Stacks r Dictionaries r Hash Tables file:///G|/ebooks/1575211831/index.htm (15 of 37) [11/06/2000 7:44:38 PM] CONTENTS q Building Your Own Data Structures r Linked List Basics r Implementing a Linked List q Summary q Q&A Day 24 Advanced Animation and Media q What Is Animation? q Types of Animation r Frame-Based Animation r Cast-Based Animation q Tracking Images q The MediaTracker Class q Implementing Sprite Animation r The Sprite Class r The SpriteVector Class r The Background Classes q Sample Applet: Sharks q Summary q Q&A Day 25 Fun with Image Filters q The Basics of Color q Color Images in Java q Color Models r Direct Color Models r Index Color Models q The Color Model Classes q Image Filters q The Image Filter Classes q Writing Your Own Image Filters r A Color Image Filter r An Alpha Image Filter r A Brightness Image Filter file:///G|/ebooks/1575211831/index.htm (16 of 37) [11/06/2000 7:44:38 PM] CONTENTS q Using Image Filters q Summary q Q&A Day 26 Client/Server Networking in Java q Internet Network Basics r Addresses r Protocols r Ports q The Client/Server Paradigm q Sockets r r q Datagram Sockets Stream Sockets Fortune: A Datagram Client and Server r r Implementing the Fortune Server r Implementing the Fortune Client Applet r q Designing Fortune Running Fortune Trivia: A Stream Client and Server r Designing Trivia r Implementing the Trivia Server r Implementing the Trivia Client Applet r Running Trivia q Summary q Q&A Day 27 The Standard Extension APIs q Java API Overview q The Enterprise API r Java Database Connectivity r Interface Definition Language r Remote Method Invocation q The Commerce API q The Management API file:///G|/ebooks/1575211831/index.htm (17 of 37) [11/06/2000 7:44:38 PM] CONTENTS q The Server API q The Media API q The Security API q The Java Beans API q The Embedded API q Summary q Q&A Day 28 Emerging Technologies q Java Beans r r How Java Beans Relates to Java r q The Goal of Java Beans The Java Beans API JavaOS r r q Overhead Industry Support Java Microprocessors r picoJAVA r microJAVA r UltraJAVA q Summary q Q&A appendix A Language Summary q Reserved Words q Comments q Literals q Variable Declaration q Variable Assignment q Operators q Objects q Arrays q Loops and Conditionals q Class Definitions file:///G|/ebooks/1575211831/index.htm (18 of 37) [11/06/2000 7:44:38 PM] CONTENTS q Method and Constructor Definitions q Packages, Interfaces, and Importing q Exceptions and Guarding appendix B Class Hierarchy Diagrams q About These Diagrams appendix C The Java Class Library q java.lang r r q Interfaces Classes java.util r r q Interfaces Classes java.io r r q Interfaces Classes java.net r r q Interfaces Classes java.awt r r q Interfaces Classes java.awt.image r Interfaces r Classes q java.awt.peer q java.applet r Interfaces r Classes appendix D Bytecodes Reference q The _quick Bytecodes file:///G|/ebooks/1575211831/index.htm (19 of 37) [11/06/2000 7:44:38 PM] CONTENTS appendix E java.applet Package Reference q AppletContext q AppletStub q AudioClip q Applet appendix F java.awt Package Reference q LayoutManager q MenuContainer q BorderLayout q Button q Canvas q CardLayout q Checkbox q CheckboxGroup q CheckboxMenuItem q Choice q Color q Component q Container q Dialog q Dimension q Event q FileDialog q FlowLayout q Font q FontMetrics q Frame q Graphics q GridBagConstraints q GridBagLayout q GridLayout q Image q Insets file:///G|/ebooks/1575211831/index.htm (20 of 37) [11/06/2000 7:44:38 PM] CONTENTS q Label q List q MediaTracker q Menu q MenuBar q MenuComponent q MenuItem q Panel q Point q Polygon q Rectangle q Scrollbar q TextArea q TextComponent q TextField q Toolkit q Window r q awtException awtError appendix G java.awt.image Package Reference q ImageConsumer q ImageObserver q ImageProducer q ColorModel q CropImageFilter q DirectColorModel q FilteredImageSource q ImageFilter q IndexColorModel q MemoryImageSource q PixelGrabber q RGBImageFilter file:///G|/ebooks/1575211831/index.htm (21 of 37) [11/06/2000 7:44:38 PM] CONTENTS appendix H java.awt.peer Package Reference q ButtonPeer q CanvasPeer q CheckboxMenuItemPeer q CheckboxPeer q ChoicePeer q ComponentPeer q ContainerPeer q DialogPeer q FileDialogPeer q FramePeer q LabelPeer q ListPeer q addItem q MenuBarPeer q MenuComponentPeer q MenuItemPeer q MenuPeer q PanelPeer q ScrollbarPeer q TextAreaPeer q TextComponentPeer q TextFieldPeer q WindowPeer appendix I java.io Package Reference q DataInput q DataOutput q FilenameFilter q BufferedInputstream q BufferedOutputStream q ByteArrayInputStream q ByteArrayOutputStream q DataInputStream file:///G|/ebooks/1575211831/index.htm (22 of 37) [11/06/2000 7:44:38 PM] CONTENTS q DataOutputStream q File q FileDescriptor q FileInputStream q FileOutputStream q FilterInputStream q FilterOutputStream q InputStream q LineNumberInputStream q OutputStream q PipedInputStream q PipedOutputStream q PrintStream q PushbackInputStream q RandomAccessFile q SequenceInputStream q StreamTokenizer q StringBufferInputStream q EOFException q FileNotFoundException q IOException q InterruptedIOException q UTFDataFormatException appendix J java.lang Package Reference q Cloneable q Runnable q Boolean q Character q Class q ClassLoader q Compiler q Double q Float file:///G|/ebooks/1575211831/index.htm (23 of 37) [11/06/2000 7:44:38 PM] CONTENTS q Integer q Long q Math q Number q Object q Process q Runtime q SecurityManager q String q StringBuffer q System q Thread q ThreadGroup q Throwable q RuntimeException q ClassNotFoundException q CloneNotSupportedException q Exception q IllegalAccessException q IllegalArgumentException q IllegalMonitorStateException q IllegalThreadStateException q IndexOutOfBoundsException q InstantiationException q InterruptedException q NegativeArraySizeException q NullPointerException q NumberFormatException q RuntimeException q SecurityException q StringIndexOutOfBoundsException q AbstractMethodError q ClassFormatError q Error q IllegalAccessError file:///G|/ebooks/1575211831/index.htm (24 of 37) [11/06/2000 7:44:38 PM] CONTENTS q IncompatibleClassChangeError q InstantiationError q InternalError q LinkageError q NoClassDefFoundError q NoSuchFieldError q NoSuchMethodError q OutOfMemoryError q StackOverflowError q ThreadDeath q UnknownError q UnsatisfiedLinkError q VerifyError q VirtualMachineError appendix K java.net Package Reference q ContentHandlerFactory q SocketImplFactory q URLStreamHandlerFactory q ContentHandler q DatagramPacket q DatagramSocket q InetAddress q ServerSocket q Socket q SocketImpl q URL q URLConnection q URLEncoder q MalformedURLException q ProtocolException q SocketException q UnknownHostException q UnknownServiceException file:///G|/ebooks/1575211831/index.htm (25 of 37) [11/06/2000 7:44:38 PM] CONTENTS appendix L java.util Package Reference q Enumeration q Observer q BitSet q Date q Dictionary q Hashtable q Observable q Properties q Random q Stack q StringTokenizer q Vector q EmptyStackException q NoSuchElementException Credits Copyright © 1996 by Sams.net Publishing All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent liability is assumed with respect to the use of the information contained herein. Although every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Neither is any liability assumed for damages resulting from the use of the information contained herein. For information, address Sams.net Publishing, 201 W. 103rd St., Indianapolis, IN 46290. International Standard Book Number: 1-57521-183-1 HTML conversion by : M/s. LeafWriters (India) Pvt. Ltd. Website : http://leaf.stpn.soft.net e-mail : leafwriters@leaf.stpn.soft.net file:///G|/ebooks/1575211831/index.htm (26 of 37) [11/06/2000 7:44:38 PM] CONTENTS Credits President, Sams Publishing Publishing Manager Managing Editor Director of Marketing Assistant Marketing Managers Acquisitions Editor Software Development Specialist Copy Editors Technical Reviewers Technical Edit Coordinator Cover Designer Copy Writer Production Richard K. Swadley Mark Taber Cindy Morrow John Pierce Kristina Perry, Rachel Wolfe Mark Taber Bob Correll Kimberly K. Hannel, Colleen Williams Brad Birnbaum, Pratip Banerji, Jeff Bankston, Jeff Shockley, Lorraine Schaffer Development Editor Senior Editor Fran Hatton Kitty Wilson Indexer Johnna VanHoose Editorial Coordinator Bill Whitmer Editorial Assistants Carol Ackerman, Andi Richter, Rhonda Tinch-Mize Tim Amrhein Book Designer Gary Adair Peter Fuller Production Team Brad Chinn Supervisor Cynthia Davis, Elizabeth Deeter, Sonja Hart, Lousia Klucznik, Polly Lavrick, Paula Lowell, Andrew Stone Preface to the Professional Reference Edition I first saw Java running in May of 1995, and was immediately struck by what it offered to the Web. What I saw seems almost quaint in this day and age of multimedia Web pages-a small animation of a character doing cartwheels across the screen-but at the time it was a revolution. My friend Jim Graham, a programmer on the Java team, showed me various aspects of the Java language and the HotJava browser, and I sat with my mouth agape, unable to say much of anything except for "that is so cool." At the time, I was just finishing up a book about HTML and looking for something else to do. I immediately knew that this had to be it. I had to write a book on Java. It took somewhat longer to actually produce the book, between needing to finish a number of other projects, having to wait for a new version of Java itself, and coming down with a number of bad cases of the flu, but the book was written and shipped in early 1996. That book was the original Teach Yourself Java in 21 Days. file:///G|/ebooks/1575211831/index.htm (27 of 37) [11/06/2000 7:44:38 PM] CONTENTS While not the first book available on the Java language, it was widely regarded as the first good book and the first one that wasn't either too vague or that assumed too much knowledge of programming. Written for an intermediate programmer, Teach Yourself Java continues to be one of the few books available that offers a basic tutorial in Java, enough to get you started and enough to move beyond the basics. Teach Yourself Java continues to be popular and continues to be recommended as one of the best books on getting started in Java. Which brings us to this hefty tome that you're holding in your hands. Since early 1996 Java itself has not changed overly much. The current 1.0.2 release has added few features since 1.0; for the new features we'll have to wait for 1.1 (due out in late 1996). But given the explosion of tools for building Java applications and the wide variety of things that people are doing with Java out there for the Web and for general-purpose applications, there is no shortage of things to talk about when it comes to Java. This book, therefore, is an extension of the original Teach Yourself Java. It has been greatly expanded and enhanced, with all the original content updated, the weak parts fixed, and more examples added. This book also contains a bonus week that adds further depth and detail about existing topics such as images, animation, and networking; it includes information about tools, debugging, and advanced data structures; and it goes into great detail about upcoming features in Java 1.1 and the extension APIs. With more than 250 pages of reference material, there's little you won't be able to discover using this book. If you haven't yet worked with Java, this is the book to start with. If you have worked with Java but are looking for more information, this is the book to continue with. And even if you've read the original Teach Yourself Java, you'll find enough new in this edition to merit putting aside the original and adding this one to the stack of programming books on your desk. Good luck and enjoy! Laura Lemay August 1996 Acknowledgments From Laura Lemay: To Sun's Java team, for all their hard work on Java, the language, and on the browser, and particularly to Jim Graham, who demonstrated Java and HotJava to me on very short notice in May and planted the idea for this book. To everyone who bought my previous books and liked them: Buy this one, too. From Charles L. Perkins: To Patrick Naughton, who first showed me the power and the promise of Oak (Java) in early 1993. To Mark Taber, who shepherded this lost sheep through his first book. From Mike Morrison: Thanks to Mark Taber for giving me the opportunity to contribute to such a cool project, and to Fran Hatton for being so enormously positive and helpful. file:///G|/ebooks/1575211831/index.htm (28 of 37) [11/06/2000 7:44:38 PM] CONTENTS About the Authors Laura Lemay Laura Lemay is a technical writer and a nerd. After spending six years writing software documentation for various computer companies in Silicon Valley, she decided that writing books would be much more fun (but has still not yet made up her mind). In her spare time she collects computers, e-mail addresses, interesting hair colors, and nonrunning motorcycles. She is also the perpetrator of Teach Yourself Web Publishing with HTML in 14 Days. You can visit her home page at http://www.lne.com/lemay/. Charles L. Perkins Charles L. Perkins is the founder of Virtual Rendezvous, a company building a Java-based service that will foster socially focused, computer-mediated, real-time filtered interactions between people's personas in the virtual environments of the near future. In previous lives, he has evangelized NeXTSTEP, SmallTalk, and UNIX, and has degrees in both physics and computer science. Before attempting this book, he was an amateur columnist and author. He's done research in speech recognition, neural nets, gestural user interfaces, computer graphics, and language theory, but had the most fun working at Thinking Machines and Xerox PARC's SmallTalk group. In his spare time, he reads textbooks for fun. You can reach him via e-mail at virtual@rendezvous.com, or visit his Java page at http://rendezvous.com/java. Michael Morrison Michael Morrison is the author of Teach Yourself Internet Game Programming with Java in 21 Days, and a contributing author to Tricks of the Java Programming Gurus, Java Unleashed, and Game Developer magazine. Michael lives in Scottsdale, Arizona, with his (now legally recognized) female cohort, Mahsheed. In his spare time, Michael enjoys testing his threshold for pain on skateboard ramps. You can reach Michael via e-mail at mmorrison@thetribe.com, or check out his Web site at http://www.thetribe.com. Tell Us What You Think! As a reader, you are the most important critic and commentator of our books. We value your opinion and want to know what we're doing right, what we could do better, what areas you'd like to see us publish in, and any other words of wisdom you're willing to pass our way. You can help us make strong books that meet your needs and give you the computer guidance you require. Do you have access to CompuServe or the World Wide Web? Then check out our CompuServe forum by typing GO SAMS at any prompt. If you prefer the World Wide Web, check out our site at http://www.mcp.com. Note If you have a technical question about this book, call the technical support line at 800-571-5840, ext. 3668. As the team leader of the group that created this book, I welcome your comments. You can fax, e-mail, or write me directly to let me know what you did or didn't like about this book-as well as what we can do to make our books stronger. Here's the information: file:///G|/ebooks/1575211831/index.htm (29 of 37) [11/06/2000 7:44:38 PM] CONTENTS FAX: E-mail: Mail: 317-581-4669 newtech_mgr@sams.mcp.com Mark Taber Publishing Manager Sams.net Publishing 201 W. 103rd Street Indianapolis, IN 46290 Introduction The World Wide Web, for much of its existence, has been a method for distributing passive information to a widely distributed number of people. The Web has, indeed, been exceptionally good for that purpose. With the addition of forms and image maps, Web pages began to become interactive-but the interaction was often simply a new way to get at the same information. The limitations of Web distribution were all too apparent once designers began to try to stretch the boundaries of what the Web can do. Even other innovations, such as Netscape's server push to create dynamic animations, were merely clever tricks layered on top of a framework that wasn't built to support much other than static documents with images and text. Enter Java, and the capability for Web pages to contain Java applets. Applets are small programs that create animations, multimedia presentations, real-time (video) games, multiuser networked games, and real interactivity-in fact, most anything a small program can do, Java applets can. Downloaded over the Net and executed inside a Web page by a browser that supports Java, applets are an enormous step beyond standard Web design. The disadvantage of Java is that to create Java applets right now, you need to write them in the Java language. Java is a programming language, and therefore, creating Java applets is more difficult than creating a Web page or a form using HTML. Soon there will be tools and programs that will make creating Java applets easier-they may be available by the time you read this. For now, however, the only way to delve into Java is to learn the language and start playing with the raw Java code. Even when the tools come out, you may want to do more with Java than the tools can provide, and you're back to learning the language. That's whereTeach Yourself Java in 21 Days comes in. This book teaches you all about the Java language and how to use it to create not only applets, but also applications, which are more general Java programs that don't need to run inside a Web browser. By the time you get through with this book, you'll know enough about Java to do just about anything, inside an applet or out. How This Book Is Organized Teach Yourself Java in 21 Days covers the Java language and its class libraries in 21 days, organized as three separate weeks. In addition, this edition contains a bonus week that's chock full of new and advanced information. Each week covers a different broad area of developing Java applets and applications. In the first week you'll learn about the Java language itself: q Day 1 is the basic introduction: what Java is, why it's cool, and how to get the software. You'll also create your first Java applications and applets. q On Day 2 you'll explore basic object-oriented programming concepts as they apply to Java. q On Day 3 you'll start getting down to details with the basic Java building blocks: data types, variables, file:///G|/ebooks/1575211831/index.htm (30 of 37) [11/06/2000 7:44:38 PM] CONTENTS q q q q and expressions, such as arithmetic and comparisons. Day 4 goes into detail about how to deal with objects in Java: how to create them, how to access their variables and call their methods, and how to compare and copy them. You'll also get your first glance at the Java class libraries. On Day 5 you'll learn more about Java, with arrays, conditional statements, and loops. Day 6 is the best one yet. You'll learn how to create classes, the basic building blocks of any Java program, and how to put together a Java application (a Java program that can run on its own without a Web browser). Day 7 builds on what you learned on Day 6. You'll learn more about how to create and use methods, including overriding and overloading methods and creating constructors. Week 2 is dedicated to applets and the Java class libraries: q Day 8 provides the basics of applets-how they're different from applications, how to create them, and about the most important parts of an applet's life cycle. You'll also learn how to create HTML pages that contain Java applets. q On Day 9 you'll learn about the Java classes for drawing shapes and characters to the screen-in black, white, or any other color. q On Day 10 you'll start animating those shapes you learned about on Day 9, including learning about threads and their uses. q Day 11 covers more detail about animation, adding bitmap images and audio to the soup. q Day 12 delves into interactivity-handling mouse and keyboard clicks from the user in your Java applets. q Day 13 is ambitious; you'll learn about using Java's Abstract Windowing Toolkit to create a user interface in your applet, including menus, buttons, check boxes, and other elements. On Day 14 you'll explore the last of the main Java class libraries for creating applets: windows and dialogs, networking, and a few other tidbits. q Week 3 includes advanced topics for when you start doing larger and more complex Java programs or when you want to learn more: q On Day 15 you'll learn more about the Java language's modifiers-for abstract and final methods and classes as well as for protecting a class's private information from the prying eyes of other classes. q Day 16 covers interfaces and packages, useful for abstracting protocols of methods to aid reuse and for the grouping and categorization of classes. q Day 17 covers exceptions: errors and warnings and other abnormal conditions, generated either by the system or by you in your programs. q Day 18 builds on the thread basics you learned on Day 10 to give a broad overview of multithreading and how to use it to allow different parts of your Java programs to run in parallel. q On Day 19 you'll learn all about the input and output streams in Java's I/O library. q q Day 20 teaches you about native code-how to link C code into your Java programs to provide missing functionality or to gain performance. On Day 21 you'll get an overview of some of the behind-the-scenes technical details of how Java works: the bytecode compiler and interpreter, the techniques Java uses to ensure the integrity and security of your programs, and the Java garbage collector. file:///G|/ebooks/1575211831/index.htm (31 of 37) [11/06/2000 7:44:38 PM] CONTENTS This Professional Reference Edition also includes a bonus week that contains more depth about some of the topics previously mentioned in the book, lots more sample programs, and coverage of the various tools and utilities currently available for writing with Java. It also gives you a preview of the features coming up in Java 1.1: q Day 22 describes tools and utilities for programming in Java, including debugging techniques, Java development environments such as Symantec Café and Visual J++, the javadoc documentation system, and other tips and tricks. q On Day 23 you'll learn about creating structures for modeling various forms of data, both with the classes in the java.util package and by creating new classes. q Day 24 goes into even more detail about animation in Java, building on the simple techniques covered in Week 2. On this day you'll learn about creating sprite-based animation and coordinating image and media loading with your programs. q Sun's java.awt.image package provides a set of classes for working with images. Day 25 covers these classes in detail, explaining the image filter architecture and how you can use it in your own Java programs. q Day 14 gives a very basic introduction to networking in Java. Day 26 takes it further, with extensive examples of networking applets and applications, working with "live" data sent from a server, and connecting to databases from Java applets. q On Day 27 we move into the future and describe what are known as the standard extension APIs. Sun is developing these APIs in conjunction with other interested parties, and many of the features you'll learn about in this chapter will be part of the 1.1 Java API. q Day 28 finishes up with more future topics, including Sun's Java Beans API, Java chips, and the JavaOS. How will these technologies affect how you work in Java and how Java will affect you? Learn about it here as you finish up the book. Preparing for the Future: The Upcoming Java 1.1 Release At the time this book is being written, the current version of Java is known as the 1.0 API (or, more exactly, the 1.0.2 version of the JDK). A new version of Java is on the horizon, one that will add a significant number of new features to Java while still being backward compatible with the original version. This new version of Java, called Java 1.1, is expected to be available in a prerelease form in late 1996. This book covers the Java 1.0 API in intimate detail. Where information about an upcoming feature of 1.1 is available, we have attempted to explain that new feature, how it will affect what you have already learned about the 1.0 API, and where to look for further information. In addition, the last two chapters of this book cover the more advanced features of 1.1 and how they will be used. These notes and comments will help you prepare for when 1.1 is released and help you migrate the code you may have already written quickly and easily to the new API. Features expected to be in the 1.1 JDK include q JDBC (the Java Database Connectivity interface) provides a mechanism for connecting Java applications and applets to SQL databases such as Oracle and Sybase. The JDBC, available in a prerelease form at this time from http://splash.javasoft.com/jdbc/, is covered on Day 27. q RMI (Remote Method Invocation) is the ability to call a Java method from an object running elsewhere (for example, in a different Java environment running on the same machine or on any machine on the network). RMI is closely related to object serialization, which allows objects to be encoded into a stream file:///G|/ebooks/1575211831/index.htm (32 of 37) [11/06/2000 7:44:38 PM] CONTENTS of bytes, which can then be sent over a network or saved to a file. The result can then also be decoded back into a Java object at the other end. Object serialization is an extension of the stream classes discussed on Day 19. RMI is discussed in greater detail on Day 27. Information about both of these topics can be found at http://chatsubo.javasoft.com/current/. q q q q The Java native methods interface is the ability for Java to call system-specific libraries such as DLLs or loadable libraries written in C. Writing native methods is described on Day 20; enhancement in Java 1.1 will include a better API for making sure native method libraries are compatible with every implementation of the Java runtime across platforms. JIT compilers are tools that convert Java bytecode to native machine code. You'll learn about JIT compilers throughout this book, but particularly on Days 21 and 22. In Java 1.1 there will be better specifications for writing your own JIT or other tool that generates native code from Java bytecodes. Changes to the awt. Probably some of the more significant changes to Java will be in the area of the Abstract Windowing Toolkit, or awt, the portion of Java that controls drawing to the screen, creating user interface elements such as buttons and windows, and handling painting and user input between all those elements. This book covers the awt primarily in Week 2. Enhancements to the awt in Java 1.1 include printing, pop-up menus, supports for clipboards (copy and paste), internationalization for fonts, better scrolling capabilities, and delegation-based events. You'll learn more about these changes throughout Week 2. Security enhancements. JDK 1.1 will provide many features for implementing security in Java applications, including signatures, access control, key management, and message digests (MD5 hashes, for example). These additions won't affect much of 1.0 because they are new enhancements. You'll learn all about 1.1's security features on Day 27. q Networking enhancements. Java 1.0 provides the java.net classes, which provide simple network connections, URL management, and simple client and server sockets. Java 1.1 provides more flexibility for the existing socket classes, a new MulticastSocket class, and BSD-style socket options. Learn about all these new features on Day 14 or from the URL http://java.sun.com/products/JDK/1.1/designspecs/net/index.html. q Adapter classes allow you to implement an API defined by an interface or a class and have the flow of control move from the adapter class back to an enclosing object. Java 1.1 provides Java syntax for nesting class definitions inside other class definitions to more easily create adapter classes. You'll learn more on Day 6. q Object reflection is the ability for Java to inspect an object and find out its methods and variables (and call and change them). Object reflection is useful for class browsers or other tools that need to find out information about an object on-the-fly, as well as component object systems that need defined ways of referring to other objects' contents. Java 1.1 provides many features for handling object reflection, including a number of new classes. You'll find out more on Day 4 or from http://java.sun.com/products/JDK/1.1/designspecs/reflection/index.html. q Java 1.1 provides a number of new features for internationalization, particularly language-specific features such as strings, character set conversions, Unicode character display, and support for definable "locales." Java archives (JAR files) provide a mechanism for combining several classes into a single file for faster downloading over the Net. Netscape provides a single archive file mechanism for applets, but JAR files provide a more cross-platform file format, compression, and the ability to include media files in the archive. The capability to store Java classes in JAR files, and to use them with Java-enabled browsers, will be in Java 1.1. The current JAR file format specification is available from q file:///G|/ebooks/1575211831/index.htm (33 of 37) [11/06/2000 7:44:38 PM] CONTENTS http://java.sun.com/security/codesign/jar-format.html. You can learn more about all these features via information throughout this book or from the Java 1.1 preview page at http://www.java.sun.com/products/JDK/1.1/designspecs/. Conventions Used in This Book Text that you type and text that should appear on your screen is presented in monospace type: It will look like this. It mimics the way text looks on your screen. Placeholders for variables and expressions appear in monospace italic. The end of each chapter offers common questions asked about that day's subject matter, with answers from the authors. Sources for Further Information Before, while, and after you read this book, there are several Web sites that may be of interest to you as a Java developer. The official Java Web site is at http://java.sun.com/. At this site, you'll find the Java development software and online documentation for all aspects of the Java language, including the previously mentioned Java 1.1 preview page. It has several mirror sites that it lists online, and you should probably use the site "closest" to you on the Internet for your downloading and Java Web browsing. There is also an excellent site for developer resources, called Gamelan, at http://www.gamelan.com/, which contains an enormous number of applets and applications, with sample code, help, and plenty of information about Java and Java development. This book also has a companion Web site at http://www.lne.com/Web/JavaProf/. Information at that site includes examples, more information, and background for this book, corrections to this book, and other tidbits that are not included here. For discussion about the Java language and the tools to develop in it, check out the Usenet newsgroups for comp.lang.java. This set of newsgroups-which includes comp.lang.java.programming, comp.lang.java.api, comp.lang.java.misc, comp.lang.java.security, and comp.lang.java.tech-is a terrific source for getting questions answered and for keeping up on new Java developments. Praise for Teach Yourself Java "If you get only one Java book, it should be Teach Yourself Java in 21 Days. Authors Laura Lemay and Charles L. Perkins cover all aspects of Java programming in an easy-to-read guide organized around daily lesson plans." -Jay Munro, pc Magazine "…this is where to begin. Java in all its gory details: classes to applets, methods to multithreading." -Thom Gillespie, Library Journal file:///G|/ebooks/1575211831/index.htm (34 of 37) [11/06/2000 7:44:38 PM] CONTENTS "Teach Yourself Java gives a thoughtful treatment to under-the-hood issues of Java's implementation." -Peter Coffee, pc Week "If you buy one book on Java, this is the one to buy. Teach Yourself Java is one of the best introductions to hands-on Java programming. The setup of the book is extremely well thought out." -Scott Sidel, Independent Web Review "This is the best introduction to object-oriented programming ever written. This book does not assume that you know C or C++, but it offers tips for those who do. Laura Lemay is my favorite tech author.…If you can afford only one Java book, then this is the one to get." -David Geary What's New in This Edition Given the explosion of tools for building Java applications and the wide variety of things that people are doing with Java, for the Web and for general-purpose applications, there is no shortage of new things to talk about when it comes to Java. This edition, therefore, is a fully revised and extended edition of the original Teach Yourself Java in 21 Days. It has been greatly expanded and enhanced, with all the original content updated, the weak parts fixed, and more examples added. This edition also contains a bonus week that adds further depth and detail about existing topics such as images, animation, and networking, as well as information about tools, debugging, and advanced data structures. In the bonus week you'll also learn about the following: q Day 22 describes tools and utilities for programming in Java, including debugging techniques, Java development environments such as Symantec Café and Visual J++, the javadoc documentation system, and other tips and tricks. q Day 23 covers creating structures for modeling various forms of data, both with the classes in the java.util package and by creating new classes. q Day 24 goes into even more detail about animation in Java, building on the simple techniques covered in Week 2. q Sun's java.awt.image package provides a set of classes for working with images. Day 25 covers these classes in detail, explaining the image filter architecture and how you can use it in your own Java programs. q Day 26 takes a further look at client/server networking, with extensive examples of networking applets and applications and working with live data sent from a server, as well as connecting to databases from Java applets. q Days 27 and 28 move into the future and describe what are known as the standard extension APIs. Many of the features you'll learn about on Day 27 will be part of Java 1.1. Day 28 finishes up with more future topics, including Sun's Java Beans API, Java chips, and the JavaOS. The bonus week goes into great detail about upcoming features in Java 1.1 and the extension APIs. And with more than 250 pages of reference material in the appendixes, there's little you won't be able to discover using this book. file:///G|/ebooks/1575211831/index.htm (35 of 37) [11/06/2000 7:44:38 PM] CONTENTS About This Book This book teaches you all about the Java language and how to use it to create applets for the World Wide Web, as well as standalone applications. By the time you get through with this book, you'll know enough about Java and about the Java class libraries to do just about anything, inside an applet or out. Who Should Read This Book This book is intended for people with at least some basic programming background, which includes people with years of programming experience and people with only a small amount of experience. If you understand what variables, loops, and functions are, you'll be just fine for this book. The sorts of people who might want to read this book include you, if q You're a real whiz at HTML, understand CGI programming (in Perl, AppleScript, Visual Basic, or some other popular CGI language) pretty well, and want to move on to the next level in Web page design. q You had some BASIC or Pascal in school and you have a basic grasp of what programming is, but you've heard Java is easy to learn, really powerful, and very cool. q You've programmed C and C++ for many years, you've heard this Java thing is becoming really popular, and you're wondering what all the fuss is about. q You've heard that Java is really good for Web-based applets, and you're curious about how good it is for creating more general applications. What if you know programming, but you don't know object-oriented programming? Fear not. This book assumes no background in object-oriented design. If you know object-oriented programming, in fact, the first couple days will be easy for you. What if you're a rank beginner? This book might move a little fast for you. Java is a good language to start with, though, and if you take it slow and work through all the examples, you may still be able to pick up Java and start creating your own applets. How This Book Is Structured This book is intended to be read and absorbed over the course of four weeks. During each week, you'll read seven chapters that present concepts related to the Java language and the creation of applets and applications. Conventions Note A note box presents interesting pieces of information related to the surrounding discussion. Technical Note A technical note presents specific technical information related to the surrounding discussion. Tip A tip box offers advice or teaches an easier way to do something. Warning file:///G|/ebooks/1575211831/index.htm (36 of 37) [11/06/2000 7:44:38 PM] CONTENTS A warning box advises you about potential problems and helps you steer clear of disaster. New terms New terms are introduced in new term boxes, with the new term in italics. Type A type icon identifies some new Java code that you can type in. You can also get the code from the CD-ROM that accompanies this book. Output An output icon shows the output from a Java program. Analysis An analysis icon alerts you to the author's line-by-line analysis. file:///G|/ebooks/1575211831/index.htm (37 of 37) [11/06/2000 7:44:38 PM] Day 1 -- An Introduction to Java Programming Day 1 An Introduction to Java Programming by Laura Lemay CONTENTS q What Is Java? q Java's Past, Present, and Future q Why Learn Java? r r Java Is Object Oriented r q Java Is Platform Independent Java Is Easy to Learn Getting Started Programming in Java r Getting a Java Development Environment r Installing the JDK and Sample Files r Configuring the JDK r Creating a Java Application r Creating a Java Applet q Troubleshooting q Summary q Q&A Hello and welcome to Teach Yourself Java in 21 Days! Starting today and for the next few weeks you'll learn all about the Java language and how to use it to create programs that run inside Web pages (called applets) and programs that can run on their own (called applications). That's the overall goal for the next couple weeks. Today, the goals are somewhat more modest, and you'll learn about the following: q What exactly Java is, and its current status q Why you should learn Java-its various features and advantages over other programming languages q Getting started programming in Java-what you'll need in terms of software and background, as well as some basic terminology q How to create your first Java programs-to close this day, you'll create both a simple Java application and a simple Java applet! file:///G|/ebooks/1575211831/ch1.htm (1 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming What Is Java? Based on the enormous amount of press Java is getting and the amount of excitement it has generated, you may get the impression that Java will save the world-or at least solve all the problems of the Internet. Not so. Java's hype has run far ahead of its capabilities, and while Java is indeed new and interesting, it really is another programming language with which you write programs that run on the Internet. In this respect, Java is closer to popular programming languages such as C, C++, Visual Basic, or Pascal, than it is to a page description language such as HTML, or a very simple scripting language such as JavaScript. More specifically, Java is an object-oriented programming language developed by Sun Microsystems, a company best known for its high-end UNIX workstations. Modeled after C++, the Java language was designed to be small, simple, and portable across platforms and operating systems, both at the source and at the binary level, which means that Java programs (applets and applications) can run on any machine that has the Java virtual machine installed (you'll learn more about this later). Java is usually mentioned in the context of the World Wide Web, where browsers such as Netscape's Navigator and Microsoft's Internet Explorer claim to be "Java enabled." Java enabled means that the browser in question can download and play Java programs, called applets, on the reader's system. Applets appear in a Web page much the same way as images do, but unlike images, applets are dynamic and interactive. Applets can be used to create animation, figures, forms that immediately respond to input from the reader, games, or other interactive effects on the same Web pages among the text and graphics. Figure 1.1 shows an applet running in Netscape 3.0. (This applet, at http://prominence.com/java/poetry/, is an electronic version of the refrigerator magnets that you can move around to create poetry or messages.) Figure 1.1 : Netscape running a Java applet. New Term Applets are programs that are downloaded from the World Wide Web by a Web browser and run inside an HTML Web page. You'll need a Java-enabled browser such as Netscape Navigator or Microsoft's Internet Explorer to run applets. To create an applet, you write it in the Java language, compile it using a Java compiler, and refer to that applet in your HTML Web pages. You put the resulting HTML and Java files on a Web site in the same way that you make ordinary HTML and image files available. Then, when someone using a Java-enabled browser views your page with the embedded applet, that browser downloads the applet to the local system and executes it, allowing your reader to view and interact with your applet in all its glory. (Readers using other browsers may see text, a static graphic, or nothing.) You'll learn more about how applets, browsers, and the World Wide Web work together later in this book. While applets are probably the most popular use of Java, the important thing to understand about Java is that you can do so much more with it than create and use applets. Java was written as a full-fledged general-purpose programming language in which you can accomplish the same sorts of tasks and solve the same sorts of problems that you can in other programming languages, such as C or C++. file:///G|/ebooks/1575211831/ch1.htm (2 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming Java's Past, Present, and Future The Java language was developed at Sun Microsystems in 1991 as part of a research project to develop software for consumer electronics devices-television sets, VCRs, toasters, and the other sorts of machines you can buy at any department store. Java's goals at that time were to be small, fast, efficient, and easily portable to a wide range of hardware devices. Those same goals made Java an ideal language for distributing executable programs via the World Wide Web and also a general-purpose programming language for developing programs that are easily usable and portable across different platforms. The Java language was used in several projects within Sun (under the name Oak), but did not get very much commercial attention until it was paired with HotJava. HotJava, an experimental World Wide Web browser, was written in 1994 in a matter of months, both as a vehicle for downloading and running applets and also as an example of the sort of complex application that can be written in Java. Although HotJava got a lot of attention in the Web community, it wasn't until Netscape incorporated HotJava's ability to play applets into its own browser that Java really took off and started to generate the excitement that it has both on and off the World Wide Web. Java has generated so much excitement, in fact, that inside Sun the Java group spun off into its own subsidiary called JavaSoft. Versions of Java itself, or, as it's most commonly called, the Java API, correspond to versions of Sun's Java Developer's Kit, or JDK. As of this writing, the current version of the JDK is 1.0.2. Previously released versions of the JDK (alphas and betas) did not have all the features or had a number of security-related bugs. Most Java tools and browsers conform to the features in the 1.0.2 JDK, and all the examples in this book run on that version as well. The next major release of the JDK and therefore of the Java API will be 1.1, with a prerelease version available sometime in the later part of 1996. This release will have few changes to the language, but a number of additional capabilities and features added to the class library. Throughout this book, if a feature will change or will be enhanced in 1.1, we'll let you know, and in the last two days of this book you'll find out more about new Java features for 1.1 and for the future. Currently, to program in Java, you'll need a Java development environment of some sort for your platform. Sun's JDK works just fine for this purpose and includes tools for compiling and testing Java applets and applications. In addition, a wide variety of excellent Java development environments have been developed, including Sun's own Java Workshop, Symantec's Café, Microsoft's Visual J++ (which is indeed a Java tool, despite its name), and Natural Intelligence's Roaster, with more development tools appearing all the time. To run and view Java applets, you'll need a Java-enabled browser or other tool. As mentioned before, recent versions of Netscape Navigator (2.0 and higher) and Internet Explorer (3.0) can both run Java applets. (Note that for Windows you'll need the 32-bit version of Netscape, and for Macintosh you'll need Netscape 3.0.) You can also use Sun's own HotJava browser to view applets, as long as you have the 1.0 prebeta version (older versions are not compatible with newer applets, and vice versa). Even if you don't have a Java-enabled browser, many development tools provide simple viewers with which you can run your applets. The JDK comes with one of these; it's called the appletviewer. Note file:///G|/ebooks/1575211831/ch1.htm (3 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming If you're running Windows 3.x as your main system, very few tools exist for you to be able to work with Java. As I write this, the only Java tool available for writing and running Java applets is a version of the JDK from IBM called the ADK. You can write applets using this tool, and view them using the applet viewer that comes with that package (neither Netscape nor Internet Explorer will run Java applets on Windows 3.1). See http://www.alphaWorks.ibm.com/ for more information. What's in store for Java in the future? A number of new developments have been brewing (pardon the pun): q Sun is developing a number of new features for the Java environment, including a number of new class libraries for database integration, multimedia, electronic commerce, and other uses. Sun also has a Java-based Web server, a Java-based hardware chip (with which you can write Java-specific systems), and a Java-based operating system. You'll learn about all these things later in this book. The 1.1 release of the JDK will include many of these features; others will be released as separate packages. q Sun is also developing a framework called Java Beans, which will allow the development of component objects in Java, similarly to Microsoft's ActiveX (OLE) tech-nology. These different components can then be easily combined and interact with each other using standard component assembly tools. You'll learn more about Java Beans later in this book. q Java capabilities will be incorporated into a wide variety of operating systems, including Solaris, Windows 95, and MacOS. This means that Java applications (as opposed to applets) can run nearly anywhere without needing additional software to be installed. q Many companies are working on performance enhancements for Java programs, including the aforementioned Java chip and what are called just-in-time compilers. Why Learn Java? At the moment, probably the most compelling reason to learn Java-and probably the reason you bought this book-is that applets are written in Java. Even if that were not the case, Java as a programming language has significant advantages over other languages and other environments that make it suitable for just about any programming task. This section describes some of those advantages. Java Is Platform Independent Platform independence-that is, the ability of a program to move easily from one computer system to another-is one of the most significant advantages that Java has over other programming languages, particularly if your software needs to run on many different platforms. If you're writing software for the World Wide Web, being able to run the same program on many different systems is crucial to that program's success. Java is platform independent at both the source and the binary level. New Term file:///G|/ebooks/1575211831/ch1.htm (4 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming Platform independence means that a program can run on any computer system. Java programs can run on any system for which a Java virtual machine has been installed. At the source level, Java's primitive data types have consistent sizes across all development platforms. Java's foundation class libraries make it easy to write code that can be moved from platform to platform without the need to rewrite it to work with that platform. When you write a program in Java, you don't need to rely on features of that particular operating system to accomplish basic tasks. Platform independence at the source level means that you can move Java source files from system to system and have them compile and run cleanly on any system. Platform independence in Java doesn't stop at the source level, however. Java compiled binary files are also platform independent and can run on multiple platforms (if they have a Java virtual machine available) without the need to recompile the source. Normally, when you compile a program written in C or in most other languages, the compiler translates your program into machine code or processor instructions. Those instructions are specific to the processor your computer is running-so, for example, if you compile your code on an Intel-based system, the resulting program will run only on other Intel-based systems. If you want to use the same program on another system, you have to go back to your original source code, get a compiler for that system, and recompile your code so that you have a program specific to that system. Figure 1.2 shows the result of this system: multiple executable programs for multiple systems. Figure 1.2 : Traditional compiled programs. Things are different when you write code in Java. The Java development environment actually has two parts: a Java compiler and a Java interpreter. The Java compiler takes your Java program and, instead of generating machine codes from your source files, it generates bytecodes. Bytecodes are instructions that look a lot like machine code, but are not specific to any one processor. To execute a Java program, you run a program called a bytecode interpreter, which in turn reads the bytecodes and executes your Java program (see Figure 1.3). The Java bytecode interpreter is often also called the Java virtual machine or the Java runtime. Figure 1.3 : Java programs. New Term Java bytecodes are a special set of machine instructions that are not specific to any one processor or computer system. A platform-specific bytecode interpreter executes the Java bytecodes. The bytecode interpreter is also called the Java virtual machine or the Java runtime interpreter. Where do you get the bytecode interpreter? For applets, the bytecode interpreter is built into every Java-enabled browser, so you don't have to worry about it-Java applets just automatically run. For more general Java applications, you'll need to have the interpreter installed on your system in order to run that Java program. Right now, you can get the Java interpreter as part of your development environment, or if you buy a Java program, you'll get it with that package. In the future, however, the Java bytecode file:///G|/ebooks/1575211831/ch1.htm (5 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming interpreter will most likely come with every new operating system-buy a Windows machine, and you'll get Java for free. Why go through all the trouble of adding this extra layer of the bytecode interpreter? Having your Java programs in bytecode form means that instead of being specific to any one system, your programs can be run on any platform and any operating or window system as long as the Java interpreter is available. This capability of a single binary file to be executable across platforms is crucial to what makes applets work because the World Wide Web itself is also platform independent. Just as HTML files can be read on any platform, so can applets be executed on any platform that has a Java-enabled browser. The disadvantage of using bytecodes is in execution speed. Because system-specific programs run directly on the hardware for which they are compiled, they run significantly faster than Java bytecodes, which must be processed by the interpreter. For many basic Java programs, speed may not be an issue. If you write programs that require more execution speed than the Java interpreter can provide, you have several solutions available to you, including being able to link native code into your Java program or using special tools (called just-in-time compilers) to convert your Java bytecodes into native code and speed up their execution. Note that by using any of these solutions, you lose the portability that Java bytecodes provide. You'll learn about each of these mechanisms on Day 20, "Using Native Methods and Libraries." Java Is Object Oriented To some, the object-oriented programming (OOP) technique is merely a way of organizing programs, and it can be accomplished using any language. Working with a real object-oriented language and programming environment, however, enables you to take full advantage of object-oriented methodology and its capabilities for creating flexible, modular programs and reusing code. Many of Java's object-oriented concepts are inherited from C++, the language on which it is based, but it borrows many concepts from other object-oriented languages as well. Like most object-oriented programming languages, Java includes a set of class libraries that provide basic data types, system input and output capabilities, and other utility functions. These basic libraries are part of the standard Java environment, which also includes simple libraries, form networking, common Internet protocols, and user interface toolkit functions. Because these class libraries are written in Java, they are portable across platforms as all Java applications are. You'll learn more about object-oriented programming and Java tomorrow. Java Is Easy to Learn In addition to its portability and object orientation, one of Java's initial design goals was to be small and simple, and therefore easier to write, easier to compile, easier to debug, and, best of all, easy to learn. Keeping the language small also makes it more robust because there are fewer chances for programmers to make mistakes that are difficult to fix. Despite its size and simple design, however, Java still has a great deal of power and flexibility. Java is modeled after C and C++, and much of the syntax and object-oriented structure is borrowed from the latter. If you are familiar with C++, learning Java will be particularly easy for you because you have file:///G|/ebooks/1575211831/ch1.htm (6 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming most of the foundation already. (In fact, you may find yourself skipping through the first week of this book fairly rapidly. Go ahead; I won't mind.) Although Java looks similar to C and C++, most of the more complex parts of those languages have been excluded from Java, making the language simpler without sacrificing much of its power. There are no pointers in Java, nor is there pointer arithmetic. Strings and arrays are real objects in Java. Memory management is automatic. To an experienced programmer, these omissions may be difficult to get used to, but to beginners or programmers who have worked in other languages, they make the Java language far easier to learn. However, while Java's design makes it easier to learn than other programming languages, working with a programming language is still a great deal more complicated than, say, working in HTML. If you have no programming language background at all, you may find Java difficult to understand and to grasp. But don't be discouraged! Learning programming is a valuable skill for the Web and for computers in general, and Java is a terrific language to start out with. Getting Started Programming in Java Enough background! For the second half of this day let's actually dive into simple Java programming and create two Java programs: a standalone Java application and an applet that you can view in a Java-enabled browser. Although both these programs are extremely simple, they will give you an idea of what a Java program looks like and how to compile and run it. Getting a Java Development Environment In order to write Java programs, you will, of course, need a Java development environment. (Although browsers such as Netscape allow you to play Java applets, they don't let you write them. For that you'll need a separate tool.) Sun's JDK, which is available for downloading at the JavaSoft Web site (http://www.javasoft.com/) and included on the CD for this book, will do just fine. It runs on Solaris, Windows 95 and NT, and Macintosh. However, despite the JDK's popularity, it is not the easiest development tool to use. If you're used to using a graphical user interface-based development tool with an integrated editor and debugger, you'll most likely find the JDK's command-line interfaces rather primitive. Fortunately, the JDK is not the only tool in town. As mentioned earlier, a number of third-party development environments (called integrated development environments, or IDEs) are also available for developing in Java. These include Sun's Java Workshop for Solaris, Windows NT and Windows 95 (you can get more information about it at http://www.sun.com/developer-products/java/); Symantec's Café for Windows 95, Windows NT, and Macintosh (http://cafe.symantec.com/); Microsoft's Visual J++ for Windows 95 and Windows NT (http://www.microsoft.com/visualj/); and Natural Intelligence's Roaster (http://www.natural.com/pages/products/roaster/index.html). All three are commercial programs, but you might be able to download trial or limited versions of these programs to try them out. You'll learn more about the features and capabilities of the various Java IDEs on Day 22, "Java Programming Tools." file:///G|/ebooks/1575211831/ch1.htm (7 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming Note I find the graphical development environments far easier to use than the standard JDK. If you have the money and the time to invest in one of these tools, I highly recommend you do so. It'll make your Java development experience much more pleasant. Installing the JDK and Sample Files Sun's JDK for Solaris, Windows, and Macintosh is included as part of the CD-ROM that comes with this book. Also on the CD-ROM are all of the code examples from this book-a great help if you don't want to type them all in again. To install either the JDK or the sample files (or both), use one of the following procedures: Note If you don't have access to a CD-ROM drive, you can also get access to these files over the World Wide Web. You can download the JDK itself from http://java.sun.com/products/JDK/1.0.2/ and install it per the instructions on those pages. The sample files from this book are available on the Web site for this book: http://www.lne.com/Web/JavaProf/. If you download the JDK and source files, as opposed to getting them off the CD-ROM, make sure you read the section "Configuring the JDK" to make sure everything is set up right. Windows Sun's JDK runs on Windows 95 and Windows NT. It does not run on Windows 3.x. To install the JDK or the sample files on Windows, run the Setup program on the CD-ROM (double-clicking the CD icon will do this automatically). By default, the package will be installed into C:\Java; you can install it anywhere on your hard disk that you'd like. You'll be given options to install the JDK, the sample files, and various other extra files; choose the options you want and those files will be installed. If you've installed the JDK, note that in the directory JDK\lib there is a file called classes.zip. Do not unzip this file; it needs to remain in zip form for it to work correctly. The file JDK\src.zip contains the source code for many of the JDK libraries; you can unzip this one if you like. Make sure if you do that you have a zip program that supports long filenames, or it will not work correctly! Macintosh Sun's JDK for Macintosh runs on System 7 (MacOS) for 68KB or Power Mac. To install the JDK or the sample files on the Macintosh, double-click the installation program on the file:///G|/ebooks/1575211831/ch1.htm (8 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming CD-ROM. By default, the package will be installed into the folder Java on your hard disk; you can install it anywhere on your disk that you'd like. You'll be given options to install the JDK, the sample files, and various other extra files; choose the options you want and those files will be installed. Solaris Sun's JDK for Solaris runs on Solaris 2.3, 2.4, and 2.5, as well as the x86 version of Solaris. The CD-ROM for this book contains the tarred and zipped JDK in the directory jdk/solaris/jdk1.02.tgz. Using the utilities gunzip and tar, you can extract the contents of that file anywhere on the file system you would like. For example, if you copy the .tgz file to your home directory and use the following commands to extract it, you'll end up with a java directory that contains the full JDK: gunzip ./jdk1.02.tgz tar xvf ./jdk1.02.tar Note that in the directory java\lib there is a file called classes.zip. Do not unzip this file; it needs to remain in zip form for it to work correctly. The file java\src.zip contains the source code for many of the JDK libraries; you can unzip this one if you're interested in the source code. The sample files are also contained on the CD-ROM in authors/authors.tar. Create a directory where the sample files will live (for example, a directory called javasamples in your home directory), copy the authors.tar file there, and then use the tar command to extract it, like this: mkdir ~/javasamples cp /cdrom/authors/authors.tar tar xvf authors.tar Configuring the JDK If you've installed the JDK using the setup programs from the CD-ROM, chances are good that it has been correctly configured for you. However, because most common problems with Java result from configuration errors, I recommend that you double-check your configuration to make sure everything is right. And if you've installed the JDK from a source other than the CD-ROM, you'll definitely want to read this section to make sure you're all set up. Windows The JDK needs two important modifications to your autoexec.bat file in order to work correctly: The JDK\bin directory must be in your execution path, and you must have the CLASSPATH variable set up. Edit your autoexec.bat file using your favorite editor (Notepad will do just fine). Look for a line that looks something like this: file:///G|/ebooks/1575211831/ch1.htm (9 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming PATH C:\WINDOWS;C:\WINDOWS\COMMAND;C:\DOS; ... Somewhere in that line you should see an entry for the JDK; if you installed the JDK from CD-ROM, it'll look something like this (the dots are there to indicate that there may be other stuff on this line): PATH C:\WINDOWS; ... C:\TEAchY~1\JDK\BIN; ... If you cannot find any reference to JDK\BIN or JAVA\BIN in your PATH, you'll need to add it. Simply include the full pathname to your JDK installation to the end of that line, starting with C: and ending with BIN; for example, C:\JAVA\BIN or C:\Java\JDK\BIN. Note The directories Teach Yourself Java and TEAchY~1 are actually the same thing; the former is how the directory appears in Windows 95, and the latter is how it appears in DOS. Either one will work fine; there's no need to change it if one or the other appears. Note, however, that if the pathname contains spaces, it must be in quotes. The second thing you'll need to add to the autoexec.bat file (if it isn't already there) is a CLASSPATH variable. Look for a line that looks something like this: SET CLASSPATH=C:\TEAchY~1\JDK\lib\classes.zip;.; The CLASSPATH variable may also have other entries in it for Netscape or Internet Explorer, but the one you're most interested in is a reference to the classes.zip file in the JDK, and to the current directory (.). If your autoexec.bat file does not include either of these locations, add a line to the file that contains both these things (the line shown above will work just fine). After saving your autoexec.bat file, you'll need to restart Windows for the changes to take effect. Macintosh The JDK for Macintosh should need no further configuration after installation. Solaris To configure the JDK for Solaris, all you need to do is add the java/bin or jdk/bin directory to your execution path. Usually a line something like this in your .cshrc, .login, or .profile files will work: set path= (~/java/bin/ $path) This line assumes that you've installed the JDK (as the directory java) into your home directory; if you've installed it somewhere else, you'll want to substitute that pathname. Make sure you use the source command with the name of the appropriate file to make sure the changes take effect (or log out and log back in again): file:///G|/ebooks/1575211831/ch1.htm (10 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming source ~/.login Creating a Java Application Now let's actually get to work. We'll start by creating a simple Java application: the classic Hello World example that many programming language books use to begin. Java applications are different from Java applets. Applets, as you have learned, are Java programs that are downloaded over the World Wide Web and executed by a Web browser on the reader's machine. Applets depend on a Java-enabled browser in order to run. New Term Java applications, however, are more general programs written in the Java language. Java applications don't require a browser to run; in fact, Java can be used to create all the kinds of applications that you would normally use a more conventional programming language to create. Java applications are standalone Java programs that do not require a Web browser to run. Java applications are more general-purpose programs such as you'd find on any computer. A single Java program can be an applet or an application, or both, depending on how you write that program and the capabilities that program uses. Throughout this first week as you learn the Java language, you'll be writing mostly applications; then you'll apply what you've learned to write applets in Week 2. If you're eager to get started with applets, be patient. Everything that you learn while you're creating simple Java applications will apply to creating applets, and it's easier to start with the basics before moving onto the hard stuff. You'll be creating plenty of applets in Week 2. Creating the Source File As with all programming languages, your Java source files are created in a plain text editor, or in an editor that can save files in plain ASCII without any formatting characters. On UNIX, emacs, pico, and vi will work; on Windows, Notepad or DOS Edit are both text editors that will work (although I prefer to use the shareware TextPad). On the Macintosh, SimpleText (which came with your Mac) or the shareware BBedit will work. If you're using a development environment like Café or Roaster, it'll have its own built-in text editor you can use. Note If you're using Windows to do your Java development, you may have to make sure Windows understands the .java file extension before you start; otherwise, your text editor may insist on giving all your files a .txt extension. The easiest way to do this is to go to any Windows Explorer window, choose View|Options|File Types, choose New Type, and add Java Source File and .java to the Description of Type and Associated Extension boxes, respectively. file:///G|/ebooks/1575211831/ch1.htm (11 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming Fire up your editor of choice and enter the Java program shown in Listing 1.1. Type this program, as shown, in your text editor. Be careful that all the parentheses, braces, and quotes are there, and that you've used all the correct upper- and lowercase letters. Note You can also find the code for these examples on the CD-ROM as part of the sample code. However, it's a good idea to actually type these first few short examples in so that you get a feel for what Java code actually looks like. Listing 1.1. Your first Java application. 1: class HelloWorld { 2: public static void main (String args) { 3: System.out.println("Hello World!"); 4: } 5: } Warning The number before each line is part of the listing and not part of the program; the numbers are there so I can refer to specific line numbers when I explain what's going on in the program. Do not include them in your own file. After you've finished typing in the program, save the file somewhere on your disk with the name HelloWorld.java. This is very important. Java source files must have the same name as the class they define (including the same upper- and lowercase letters), and they must have the extension .java. Here, the class definition has the name HelloWorld, so the filename must be HelloWorld.java. If you name your file something else (even something like helloworld.java or Helloworld.java), you won't be able to compile it. Make absolutely certain the name is HelloWorld.java. You can save your Java files anywhere you like on your disk, but I like to have a central directory or folder to keep them all in. For the examples in this chapter, I've put my files into a directory called TYJtests (short for Teach Yourself Java Tests). Compiling and Running the Source File Now it's time to compile the file. If you're using the JDK, you can use the instructions for your computer system contained in the next few pages. If you're using a graphical development environment, there will most likely be a button or option to compile the file (check with the documentation that came with your program). Windows file:///G|/ebooks/1575211831/ch1.htm (12 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming To compile the Java source file, you'll use the command-line Java compiler that comes with the JDK. To run the compiler, you'll need to first start up a DOS shell. In Windows 95, the DOS shell is under the Programs menu (it's called MS-DOS Prompt). From inside DOS, change directories to the location where you've saved your HelloWorld.java file. I put mine into the directory TYJtests, so to change directories I'd use this command: CD C:\TYJtests Once you've changed to the right directory, use the javac command as follows, with the name of the file as you saved it in Windows (javac stands for Java compiler). Note that you have to make sure you type all the same upper- and lowercase here as well: javac HelloWorld.java Note The reason that I've emphasized using the original filename is that once you're inside the DOS shell, you might notice that your nice long filenames have been truncated to old-style 8.3 names and that, in fact, HelloWorld.java actually shows up as HELLOW~1.jav. Don't panic; this is simply a side effect of Windows 95 and how it manages long filenames. Ignore the fact that the file appears to be HELLOW~1.jav and just use the filename you originally used when you saved the file. Figure 1.4 shows what I've done in the DOS shell so you can make sure you're following along. Figure 1.4 : Compiling Java in the DOS shell. If all goes well, you'll end up with a file called HelloWorld.class (or at least that's what it'll be called if you look at it outside the DOS shell; from inside DOS its called HELLOW~1.cla). That's your Java bytecode file. If you get any errors, go back to your original source file and make sure you typed it exactly as it appears in Listing 1.1 with the same upper- and lowercase. Also make sure the filename has exactly the same upper- and lowercase as the name of the class (that is, both should be HelloWorld). Once you have a class file, you can run that file using the Java bytecode interpreter. The Java interpreter is called simply java, and you run it from the DOS shell as you did javac. Run your Hello World program like this from the command line, with all the same upper- and lowercase (and note that the argument to the java program does not have a .class extension): java HelloWorld If your program was typed and compiled correctly, you should get the phrase Hello World! printed to your screen as a response. Figure 1.5 shows how I did it. file:///G|/ebooks/1575211831/ch1.htm (13 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming Figure 1.5 : Running Java applications in the DOS shell. Note Remember, the Java compiler and the Java interpreter are different things. You use the Java compiler (javac) for your Java source files to create .class files, and you use the Java interpreter (java) to actually run your class files. Macintosh The JDK for the Mac comes with an application called Java Compiler. To compile your Java source file, simply drag and drop it on top of the Java Compiler icon. The program will compile your Java file and, if there are no errors, create a file called HelloWorld.class in the same folder as your original source file. Tip Putting an alias for Java Compiler on the desktop makes it easy to drag and drop Java source files. If you get any errors, go back to your original source file and make sure you typed it exactly as it appears in Listing 1.1, with the same upper- and lowercase. Also make sure the filename has exactly the same upper- and lowercase as the name of the class (that is, both should be HelloWorld). Once you've successfully generated a HelloWorld.class file, simply double-click it to run it. The application Java Runner, part of the Mac JDK, will start, and the program will ask you for command-line arguments. Leave that screen blank and click OK. A window labeled stdout will appear with the message Hello World!. Figure 1.6 shows that window. Figure 1.6 : Running Java applications on the Mac using Java Runner. That's it! Keep in mind as you work that you use the Java Compiler application to compile your .java files into .class files, which you can then run using Java Runner. To compile the Java source file in Solaris, you'll use the command-line Java compiler that comes with the JDK. From a UNIX command line, cd to the directory that contains your Java source file. I put mine in the directory TYJtests, so to change directories I'd use this command: cd ~/TYJtests Once you're in the right directory, use the javac command with the name of the file, like this: javac HelloWorld.java If all goes well, you'll end up with a file called HelloWorld.class in the same directory as your source file. That's your Java bytecode file. If you get any errors, go back to your original source file and make sure you typed it exactly as it appears in Listing 1.1, with the same upper- and lowercase letters. Also make sure the filename has exactly the same upper- and lowercase letters as the name of the class file:///G|/ebooks/1575211831/ch1.htm (14 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming (that is, both should be HelloWorld). Once you have a class file, you can run that file using the Java bytecode interpreter. The Java interpreter is called simply java, and you run it from the command line as you did javac, like this (and note that the argument to the java program does not have a .class extension): java HelloWorld If your program was typed and compiled correctly, you should get the phrase Hello World! printed to your screen as a response. Figure 1.7 shows a listing of all the commands I used to get to this point (the part with [desire]~[1] is my system prompt). Figure 1.7 : Compiling and running a Java application on Solaris. Note Remember that the Java compiler and the Java interpreter are different things. You use the Java compiler (javac) for your Java source files to create .class files, and you use the Java interpreter (java) to actually run your class files. Creating a Java Applet Creating applets is different from creating a simple application. Java applets run and are displayed inside a Web page with other page elements, and therefore have special rules for how they behave. Because of these special rules for applets, creating an applet may in many cases be more complex than creating an application. For example, to create a simple Hello World applet, instead of merely being able to print a message as a set of characters, you have to make space for your message on the Web pages and then use special font and graphics operations to paint the message to the screen. Note Actually, you can run a plain Java application as an applet, but the Hello World message will print to a special window or to a log file, depending on how the browser has its output set up. You'll learn more about this next week. Creating the Source File In this example, you'll create a simple Hello World applet, place it inside a Web page, and view the result. As with the Hello World application, you'll first create the source file in a plain text editor. Listing 1.2 shows the code for the example. Listing 1.2. The Hello World applet. 1: import java.awt.Graphics; 2: file:///G|/ebooks/1575211831/ch1.htm (15 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming 3: public class HelloWorldApplet extends java.applet.Applet { 4: 5: public void paint(Graphics g) { 6: g.drawString("Hello world!", 5, 25); 7: } 8:} Save that file just as you did the Hello World application, with the filename exactly the same as the name of the class. In this case the class name is HelloWorldApplet, so the filename you save it to would be HelloWorldApplet.java. As with the application, I put the file in a directory called TYJch01, but you can save it anywhere you like. Compiling the Source File The next step is to compile the Java applet file. Despite the fact that this is an applet, you compile the file exactly the same way you did the Java application, using one of the following procedures: javac HelloWorldApplet.java javac HelloWorldApplet.java Windows From inside a DOS shell, cd to the directory containing your applet source file, and use the javac command to compile it (watch those upper- and lowercase letters): Macintosh Drag and drop the HelloWorldApplet.java file onto the Java Compiler icon. Salaris From a command line, cd to the directory containing your applet source file and use the javac command to compile it: Including the Applet in a Web Page If you've typed the file correctly, you should end up with a file called HelloWorldApplet.class in the same directory as your source file. That's your Java applet file; to have the applet run inside a Web page you must refer to that class file inside the HTML code for that page using the <APPLET> tag. Listing 1.3 shows a simple HTML file you can use. Listing 1.3. The HTML with the applet in it. 1: <HTML> 2: <HEAD> file:///G|/ebooks/1575211831/ch1.htm (16 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming 3: 4: 5: 6: 7: 8: 9: <TITLE>Hello to Everyone!</TITLE> </HEAD><BODY> <P>My Java applet says: <APPLET CODE="HelloWorldApplet.class" WIDTH=150 HEIGHT=25> </APPLET> </BODY> </HTML> You'll learn more about <APPLET> later in this book, but here are two things to note about it: q Use the CODE attribute to indicate the name of the class that contains your applet, here HelloWorldApplet.Class. q Use the WIDTH and HEIGHT attributes to indicate the size of the applet on the page. The browser uses these values to know how big a chunk of space to leave for the applet on the page. Here, a box 150 pixels wide and 25 pixels high is created. Save the HTML file in the same directory as your class file, with a descriptive name and an .html extension (for example, you might name your HTML file the same name as your applet-HelloWorldApplet.html). Note As mentioned earlier with the Java source files, your text editor may insist on naming your HTML files with a .txt extension if Windows does not understand what the .html extension is used for. Select View|Options|File Types from any Windows Explorer window to add a new file type for HTML files to solve this problem. Now you're ready for the final test-actually viewing the result of running your applet. To view the applet, you need one of the following: q A browser that supports Java applets, such as Netscape 2.0 or Internet Explorer 3.0. If you're running on the Macintosh, you'll need Netscape 3.0 or later. If you're running on Windows 95 or NT, you'll need the 32-bit version of Netscape. And if you're using Internet Explorer, you'll need the 3.0 beta 5 or later (the final version will do just fine). q The appletviewer application, which is part of the JDK. The appletviewer is not a Web browser and won't let you to see the entire Web page, but it's acceptable for testing to see how an applet will look and behave if there is nothing else available. q An applet viewer or runner tool that comes with your development environment. If you're using a Java-enabled browser such as Netscape to view your applet files, you can use the Open File... item under the File menu to navigate to the HTML file containing the applet (make sure you open the HTML file and not the class file). In Internet Explorer, select File|Open and then Browse to find the file on your disk. You don't need to install anything on a Web server yet; all this works on your local system. Note that the Java applet may take a while to start up after the page appears to be done loading; be patient. Figure 1.8 shows the result of running the applet in Netscape. Figure 1.8 : The applet running in Netscape. file:///G|/ebooks/1575211831/ch1.htm (17 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming If you don't have a Web browser with Java capabilities built into it, you can use the JDK's appletviewer program to view your Java applet. appletviewer HTML/HelloWorldApplet.html Windows or Solaris To run the appletviewer in Windows or Solaris versions of the JDK, cd to the directory where your HTML and class files are contained and use the appletviewer command with the name of the HTML file you just created: The appletviewer will show you only the applet itself, not the HTML text around the applet. Although the appletviewer is a good way to do simple tests of Java applets, it's a better idea to get a Java-enabled browser so that you can see your applet on its page in its full glory. Troubleshooting If you've run into any problems with the previous examples, this section can help. Here are some of the most common problems and how to fix them: q Bad command or filename or Command not found These errors result when you do not have the JDK's bin directory in your execution path, or the path to that directory is wrong. On Windows, double-check your autoexec.bat file; on UNIX, check the system file with your path commands in it (.cshrc, .login, .profile, or some similar file). q javac: invalid argument Make sure the name of the file you're giving to the javac command is exactly the same name as the file. In particular, in the DOS shell you want to use the Windows filename with a .java extension, not the DOS equivalent (HELLOW~1.jav, for example). q Warning: public class HelloWorldApplet must be defined in a file called HelloWorldApplet.java This error most often happens if there is a mismatch between the name of the class as defined in the Java file itself (the name following the word class) and the name of the java source file. Both the filenames must match, including upper- and lowercase letters (this particular error implies that the filename had lowercase letters). Rename either the filename or the class name, and this error will go away. q Insufficient-memory errors The JDK is not the most efficient user of memory. If you're getting errors about memory, consider closing larger programs before running Java compiles, turn on virtual memory, or install more RAM. q Other code errors If you're unable to compile the Java source files because of other errors I haven't mentioned here, be sure that you've typed them in exactly as they appear, including all upper- and lowercase letters. Java is case sensitive, meaning that upper- and lowercase letters are treated differently, so you will need to make sure that everything is capitalized correctly. If all else fails, try comparing your file:///G|/ebooks/1575211831/ch1.htm (18 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming source files to the files on the CD-ROM. Summary Today you've gotten a basic introduction to the Java language and its goals and features. Java is a programming language, similar to C or C++, in which you can develop a wide range of programs. The most common use of Java at the moment is in creating applets for HotJava, an advanced World Wide Web browser also written in Java. Applets are Java programs that are downloaded and run as part of a Web page. Applets can create animation, games, interactive programs, and other multimedia effects on Web pages. Java's strengths lie in its portability-both at the source and at the binary level, in its object-oriented design-and in its simplicity. Each of these features helps make applets possible, but they also make Java an excellent language for writing more general-purpose programs that do not require a Java-enabled browser to run. These general-purpose Java programs are called applications. To end this day, you experimented with an example of an applet and an example of an application, getting a feel for the differences between the two and how to create, compile, and run Java programs-or, in the case of applets, how to include them in Web pages. From here, you now have the foundation to create more complex applications and applets. Onward to Day 2, "Object-Oriented Programming and Java"! Q&A Q: A: Q: A: Q: I know a lot about HTML, but not much about computer programming. Can I still write Java programs? If you have no programming experience whatsoever, you most likely will find programming Java significantly more difficult than HTML. However, Java is an excellent language to learn programming with, and if you patiently work through the examples and the exercises in this book, you should be able to learn enough to get started with Java. What's the relationship between JavaScript and Java? They have the same first four letters. A common misconception in the Web world today is that Java and JavaScript have more in common than they actually do. Java is the general-purpose programming language that you'll learn about in this book; you use it to create applets. JavaScript is a Netscape-invented scripting language that looks sort of like Java; with it you can do various nifty things in Web pages. They are independent languages, used for different purposes. If you're interested in JavaScript programming, you'll want to pick up another book, such as Teach Yourself JavaScript in a Week or Laura Lemay's Web Workshop: JavaScript, both also available from Sams.net Publishing. According to today's lesson, Java applets are downloaded via a Java-enabled browser such as Netscape and run on the reader's system. Isn't that an enormous security hole? What stops someone from writing an applet that compromises the security of my system-or worse, that damages my system? file:///G|/ebooks/1575211831/ch1.htm (19 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming A: Sun's Java team has thought a great deal about the security of applets within Java-enabled browsers and has implemented several checks to make sure applets cannot do nasty things: q Java applets cannot read or write to the disk on the local system. q Java applets cannot execute any programs on the local system. q Java applets cannot connect to any machines on the Web except for the server from which they are originally downloaded. Note that some of these restrictions may be allowed in some browsers or may be turned on in the browser configuration. However, you cannot expect any of these capabilities to be available. In addition, the Java compiler and interpreter check both the Java source code and the Java bytecodes to make sure that the Java programmer has not tried any sneaky tricks (for example, overrunning buffers or stack frames). Q: A: Q: A: These checks obviously cannot stop every potential security hole (no system can promise that!), but they can significantly reduce the potential for hostile applets. You'll learn more about security issues for applets on Day 8, "Java Applet Basics," and in greater detail on Day 21, "Under the Hood." I followed all the directions you gave for creating a Java applet. I loaded it into HotJava, but Hello World didn't show up. What did I do wrong? Don't use HotJava to view applets you've created in this book; get a more up-to-date browser such as Netscape or Internet Explorer. HotJava was an experimental browser and has not been updated since soon after its original release. The steps you take to define and write an applet have changed since then, and the applets you write now will not run on HotJava. You've mentioned Solaris, Windows, and Macintosh in this chapter. What about other operating systems? If you use a flavor of UNIX other than Solaris, chances are good that the JDK has been ported to your system. Here are some examples: q SGI's version of the JDK can be found at http://www.sgi.com/Products/cosmo/cosmo_instructions.html. q q IBM has ported the JDK to OS/2 and AIX. Find out more from http://www.ncc.hurley.ibm.com/javainfo/. q Q: A: Information about Java for Linux can be found at http://www.blackdown.org/java-linux/. OSF is porting the JDK to HP/UX, Unixware, Sony NEWS, and Digital UNIX. See http://www.osf.org/mall/web/javaport.htm. (Thanks to Elliote Rusty Harold's Java FAQ at http://www.sunsite.unc.edu/javafaq/javafaq/html for this information.) Why doesn't Java run on Windows 3.1? Technical limitations in Windows 3.1 make porting Java to Windows 3.1 particularly difficult. Rumor has it that both IBM and Microsoft are working on ports, but no real information is forthcoming. file:///G|/ebooks/1575211831/ch1.htm (20 of 21) [11/06/2000 7:44:43 PM] Day 1 -- An Introduction to Java Programming Q: A: Q: A: I'm using Notepad on Windows to edit my Java files. The program insists on adding a .txt extension to all my files, regardless of what I name them (so I always end up with files like HelloWorld.java.txt). Short of renaming them before I compile them, what else can I do to fix this? Although you can rename the files just before you compile them, that can get to be a pain, particularly when you have a lot of files. The problem here is that Windows doesn't understand the .java extension (you may also have this problem with HTML's .html extension as well). To fix this, go into any Windows Explorer window and select View|Options|File Types. From that panel, select New Type. Enter Java Source Files in the Description of Type box and .java into the Associated Extension box. Then click OK. Do the same with HTML files if you need to, and click OK again. You should now be able to use Notepad (or any other text editor) to create and save Java and HTML files. Where can I learn more about Java and find applets and applications to play with? You can read the rest of this book! Here are some other places to look for Java information and Java applets: q The Java home page at http://www.java.sun.com/ is the official source for Java information, including information about the JDK, about the upcoming 1.1 release, and about developer tools such as the Java Workshop, as well as extensive documentation. q Gamelan, at http://www.gamelan.com/, is a repository of applets and Java information, organized into categories. If you want to play with applets or applications, this is the place to look. q For Java discussion, check out the comp.lang.java newsgroups, including comp.lang.java.programmer, comp.lang.java.tech, comp.lang.java.advocacy, and so on. (You'll need a Usenet newsreader to access these newsgroups.) file:///G|/ebooks/1575211831/ch1.htm (21 of 21) [11/06/2000 7:44:43 PM] file:///G|/ebooks/1575211831/f1-1.gif file:///G|/ebooks/1575211831/f1-1.gif [11/06/2000 7:44:45 PM] file:///G|/ebooks/1575211831/f1-2.gif file:///G|/ebooks/1575211831/f1-2.gif [11/06/2000 7:44:45 PM] file:///G|/ebooks/1575211831/f1-3.gif file:///G|/ebooks/1575211831/f1-3.gif [11/06/2000 7:44:45 PM] Day 20 -- Using Native Methods and Libraries Day 20 Using Native Methods and Libraries by Laura Lemay and Charles L. Perkins CONTENTS q Why Use Native Methods? r Advantages of Using Native Methods q Disadvantages of Native Methods q The Illusion of Required Efficiency r r Just-in-Time Compilers r q Design First, Efficiency Later Simple Optimization Tricks Writing Native Methods r r Generate Header and Stub Files r Implementing the Native Library r q Write Your Java Code Using Your Library Tools and Techniques for Writing Native Implementations r Names r Accessing Java Objects r Calling Methods r Creating New Java Objects r Handling Exceptions r Dealing with Strings q Summary q Q&A Up to this point in the book you've been learning specifically about programming in the Java language and with the Java class libraries. That's why this book is called Teach Yourself Java, after all. Today I'm going to digress a little bit and talk about native methods and libraries. Native methods and libraries are bits of executable code that are written in the traditional way: They are written in a language such as C or C++ and compiled into a platform-specific library such as a DLL or a shared library. Inside your Java applications you can gain access to the functions inside those libraries, allowing you to create a sort of hybrid Java and native code application. Although using native methods can give you some extra benefits Java does not provide (such as faster execution or access to a large body of existing code), there are significant disadvantages in using native methods as well. New Term file:///G|/ebooks/1575211831/ch20.htm (1 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries Native methods and native libraries are bits of platform-specific executable code (written in languages such as C or C++) contained in libraries or DLLs. You can create a hybrid Java application that has access to those native libraries. Today's lesson covers various topics relating to native methods, including the following: q The advantages and disadvantages of using native methods q Why using native methods for speed or efficiency is often unnecessary q The steps for creating native methods, header and stub files, and native implementations, and linking it all together q Various functions and utilities for mapping between Java and C and C++ Note In today's lesson you'll learn the basic techniques for writing native methods in the current version of Java. For the Java 1.1 release, Sun will publish further guidelines for writing native methods to help make sure that native implementations will work between different versions of the Java runtime. These guidelines will be in addition to the technique you will learn in today's lesson, and will build on the skills you learn here. Why Use Native Methods? Before I get into the nitty-gritty details of creating native methods, you should first be aware of what native methods give you-and what they take away. Although native methods provide some advantages, those advantages may not appear too exciting when viewed in light of native methods' disadvantages. This section describes both. Advantages of Using Native Methods There are several reasons that you might want to consider using native methods in your own Java programs. By far the best of these reasons are q Gaining access to special capabilities of your computer or operating system q Needing the extra speed that native methods provide q Needing access to a large body of existing code The first, and by far the best, reason to implement native methods is because you need to utilize a special capability of your computer or operating system that the Java class library does not already provide for you. Such capabilities include interfacing to new peripheral devices or plug-in cards, accessing a different type of networking, or using a unique, but valuable feature of your particular operating system. Two more concrete examples are acquiring real-time audio input from a microphone or using 3D "accelerator" hardware in a 3D library. Neither of these is provided to you by the current Java environment, so you must implement them outside Java, in some other language (currently C or any language that can link with C). The second, and often illusory, reason to use native methods is speed. The argument has been made that because interpreted bytecode is terribly slow in comparison to how quickly native code runs (and it is far slower, as much as 25 times slower), Java code is unsuitable for most applications. In many cases this simply isn't true, or you may be able to extract a fair amount of speed out of your Java program without resorting to native methods (as we'll explore in greater detail later in today's lesson). If, however, your Java application uses very processor-intensive calculations (for example, number crunching or 3D rendering), using native methods for the speed-critical functions and Java for the more general interfaces creates a system with more benefits than a system written in either pure native code or pure Java. In fact, the Java class library uses this approach for many critical system classes to raise the overall level of efficiency in the system. As a user of the Java environment, you don't even know (or see) any side effects of this (except, perhaps, a few classes or methods that are final that might not be otherwise). file:///G|/ebooks/1575211831/ch20.htm (2 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries The third reason to use native classes is if your project has a large body of existing code (what's called legacy code, which may be hundreds of lines of code written and maintained by other people over the years). As a good Java programmer and advocate you would, of course, want to port this large body of code to Java. However, real-life considerations of time and resources often don't allow this option. Native methods allow you to write a single interface to that code through Java and link into the existing code as it's needed. Disadvantages of Native Methods After reading the advantages of using native methods, you may be all set to jump to the section on how to use them and skip this section. Don't. For every good thing native methods provide in your Java code, they take away a benefit that Java provides in the first place: the ability for your code to run anywhere and be easily ported from one system to another. Using pure Java, an application or applet can be run on any Java environment in the world by downloading it via the Web or by simply loading the class file on that system. Any new architectures created-or new operating systems written-are irrelevant to your code. All you need is that the (tiny) Java Virtual Machine (or a browser that has one inside it) be available, and it can run anywhere, anytime-now and in the future. With a hybrid Java and native method program, however, you've given up that cross-platform capability. First of all, Java programs that use native methods cannot be applets. Period. For security reasons, applets cannot load native code. So if you use native methods, you've just removed the enormous number of users on the World Wide Web from your market. Even if you're just creating a Java application, however, and don't intend your code to be run on the Web, using native methods also negates the capability of your program to run on any platform. Native code is, by definition, platform specific. The native code must exist on the platform your Java program is running on for that program to work. For your program to work on different platforms, you'll have to port your native code to that specific platform-which may not be a trivial task. And as new systems or new versions of operating systems appear, you may have to update or re-release new versions of that native code for every system. The write-it-once-run-it-everywhere advantage of Java ceases to exist when you use native methods. The Illusion of Required Efficiency Let's digress for a moment and talk about the concept of speed and efficiency of Java programs-or the supposed lack thereof, which may drive you to using native code in your Java programs. Java bytecode has acquired the reputation of being extraordinarily slow to run in comparison with native executable code. And, examining the benchmarks, Java bytecode is indeed very much slower-as much as 25 times slower. However, that doesn't necessarily make a Java program unbearable to use. Simple applets or applications that rely on user interface elements will appear to run just as fast as their native equivalents. Button clicks are just as fast in Java as they are in native code, and your users are very slow compared to modern computers. It's only in the case of very processor-intensive operations that Java starts to come up short in comparison to native code. At any rate, worrying over the speed of your Java programs before you write them is often a rathole that can distract you from the larger issues. In this section I'll look at both those larger issues and at the solutions that can make your Java programs run faster. Design First, Efficiency Later When you design your program, all your energy and creativity should be directed at the design of a tight, concise, minimal set of classes and methods that are maximally general, abstract, and reusable. (If you think that is easy, look around for a few years and see how bad most software is.) If you spend most of your programming time on thinking and rethinking these fundamental goals and how to achieve them, you are preparing for the future-a future where software is assembled as needed from small components swimming in a sea of network facilities, and anyone can write a component seen by millions (and reused in their programs) in minutes. If, instead, you spend your energy worrying about the speed your software will run right now on some computer, your work will be irrelevant after the 18 to 36 months it will take hardware to be fast enough to hide that minor inefficiency in your program. file:///G|/ebooks/1575211831/ch20.htm (3 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries So you should ignore efficiency altogether? Of course not! Some of the great algorithms of computer science deal with solving hard or "impossible" problems in reasonable amounts of time-and writing your programs carelessly can lead to remarkably slow results. Carelessness, however, can as easily lead to incorrect, fragile, or nonreusable results. If you correct all these latter problems first, the resulting software will be clean, will naturally reflect the structure of the problem you're trying to solve, and thus will be amenable to "speeding up" later. Note There are always cases where you must be fanatical about efficiency in many parts of a set of classes. The Java class library itself is such a case, as is anything that must run in real-time for some critical real-world application (such as flying a plane). Such applications are rare, however. When speaking of a new kind of programming that must soon emerge, Bill Joy likes to invoke the four S's of Java: small, simple, safe, and secure. The "feel" of the Java language itself encourages the pursuit of clarity and the reduction of complexity. The intense pursuit of efficiency, which increases complexity and reduces clarity, is antithetical to these goals. Once you build a solid foundation, debug your classes, and your program (or applet) works as you'd like it to, then it's time to begin optimizing it. Just-in-Time Compilers The first thing to keep in mind about the execution speed of Java is that lots of people are working on fixing it. And the most promising of these technical advancements is the just-in-time (JIT) compiler. Just-in-time compilers translate Java bytecode into native machine code on-the-fly as the bytecode is running. Depending on how good the JIT compiler is, you can often get very close to native execution speeds out of a standard Java program-without needing to use native code and without needing to make any modifications to your Java program-it just works. The disadvantage, however, is that to get the speed increase your Java program must be run on a platform that has a JIT compiler installed. At the time of this writing, JIT compilers are still new. Many companies are working on JIT compilers, however, and most of them have versions working or bundled in with development tools so you can experiment with their power. Microsoft's Internet Explorer Web browser, for example, has a JIT compiler built into it. (You'll learn more about the available JIT compilers are expected on Day 22, "Java Programming Tools.") JIT compilers are expected to become much more popular and widespread over the next year. Simple Optimization Tricks In addition to relying on JIT technology to speed up your Java programs, there are usually simple optimization tricks you can do to make your programs run more efficiently. Your development environment may even provide a profiler, which tells you where the slowest or more frequently run portions of your program are occurring. Even if you don't have a profiler, you can often use debugging tools to find the bottlenecks in your programs and begin to make targeted changes to your classes. Whole books have been written for optimizing various bits of code in any language, and they can describe it much better than we can. But there are a few simple tricks you can try for the first pass. First, identify the crucial few methods that take most of the time (there are almost always just a few, and often just one, that take up the majority of your program's time). If those methods contain loops, examine the inner loops to see whether they q Call methods that can be made final q Call a group of methods that can be collapsed into a single method q Create objects that can be reused rather than created anew for each loop If you notice that a long chain of, for example, four or more method calls is needed to reach a destination method's code, and this execution path is in one of the critical sections of the program, you can "short-circuit" directly to that destination method file:///G|/ebooks/1575211831/ch20.htm (4 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries in the topmost method. This may require adding a new instance variable to reference the object for that method call directly. This quite often violates layering or encapsulation constraints. This violation, and any added complexity, is the price you pay for efficiency. Writing Native Methods If, after all these tricks, your Java code is still just too slow, it's time to consider using native methods. In this section you'll learn the steps you must take to write your Java code so that it uses native methods, how to write the native code to implement those native methods, and how to compile and link it all together so it works. This involves four basic steps: q Write your Java code so that the methods that will be native have special declarations using the native modifier. q Compile your Java code and use the javah program to generate special header and stub files, which make up the starting point for your native code. q Write your native implementations of the native methods. q Compile all the native files into a shared library or DLL and run your Java program. Note This discussion-and, in fact, the JDK itself-assumes that you'll be writing your native code in C and C++. Other Java development environments may support other languages. Write Your Java Code The first step to implementing native methods is to decide which methods in which classes of your Java program will be native. The mapping between Java and native libraries is through methods (functions), so designing your Java code and keeping track of which methods are native is the most important first step. To declare that a method will be native inside your Java code, you add the native modifier to that method signature, like this: public native void goNative(int x, int y); Note The native modifier can be used with many of the modifiers you learned about on Day 15, "Modifiers, Access Control, and Class Design," including public, private, protected, final, and so on. It cannot be used with abstract because abstract methods do not have definitions, native or otherwise. Note also that the native method in your Java code has no method body. Because this is a native method, its implementation will be provided by the native code, not by Java. Just add a semicolon to the end of the line. The other change you'll have to make to your Java code is to explicitly load the native library that will contain the native code for these methods. To do this, you add the following boilerplate code to your Java class: static { System.loadLibrary("libmynativelibrary.so"); } This bit of code, called a static initializer, is used to run code only once when the class is first loaded into the system. In this case, the static initializer executes the System.loadLibrary() method to load in your native library as the class itself is being loaded. If the native library fails to load for some reason, the loading of the Java class fails as well, guaranteeing that no half-set-up version of the class can ever be created. file:///G|/ebooks/1575211831/ch20.htm (5 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries You can pick any name you want for your native library-here we've used the UNIX convention that libraries start with the word lib and end with the extension .so. For Windows systems, libraries typically end with the extension .DLL. You can also use the System.load() method to load your native libraries. The difference is that the single argument to load() is the complete pathname to your native library, whereas the argument to loadLibrary() is just the library name, and Java uses the standard way of finding libraries for your system to locate that library (usually environment variables such as LD_LIBRARY_PATH). The latter is more flexible and general-purpose, so it's recommended you use it instead. And that's all you need to do in your Java code to create native methods and libraries. Subclasses of any class containing your new native methods can still override them, and these new (Java) methods are called for instances of the new subclasses (just as you'd expect). Listing 20.1 shows an example of a Java program called SimpleFile that was written to use native methods. This program might be used in a version of the Java environment that does not provide file input or output (I/O). Because file I/O is typically system-dependent, native methods must be used to implement those operations. Note This example combines simplified versions of two actual Java library classes, java.io.File and java.io.RandomAccessFile. Listing 20.1. SimpleFile, a Java program that uses native methods. 1: public class SimpleFile { 2: public static final char separatorChar = '>'; 3: protected String path; 4: protected int fd; 5: 6: public SimpleFile(String s) { 7: path = s; 8: } 9: 10: public String getFileName() { 11: int index = path.lastIndexOf(separatorChar); 12: 13: return (index < 0) ? path : path.substring(index + 1); 14: } 15: 16: public String getPath() { 17: return path; 18: } 19: 20: public native boolean open(); 21: public native void close(); 22: public native int read(byte buffer, int length); 23: public native int write(byte buffer, int length); 24: 25: static { 26: System.loadLibrary("simple"); // runs when class first loaded 27: } 28: } The first thing you notice about SimpleFile's implementation is how unremarkable the first two-thirds of its Java code is! It looks just like any other class, with a class and an instance variable, a constructor, and two normal method implementations (getFileName() and getPath()). Then, in lines 20 through 23, there are four native method declarations, which are file:///G|/ebooks/1575211831/ch20.htm (6 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries just normal method declarations with the code block replaced by a semicolon and the modifier native added. These are the methods you have to implement in C code later. Finally, note the call to System.loadLibrary() in line 26, which loads a native library called simple. (We've intentionally violated library-naming standards here to make this example simpler.) Note The unusual separatorChar ('>') is used simply to demonstrate what an implementation might look like on some strange computer whose file system didn't use any of the more common path-separator conventions. Early Xerox computers used '>' as a separator, and several existing computer systems still use strange separators today, so this is not all that farfetched. After you write the native part of your Java program, SimpleFile objects can be created and used in the usual way: SimpleFile f = new SimpleFile(">some>path>and>fileName"); f.open(); f.read(...); f.write(...); f.close(); Generate Header and Stub Files The second step to implementing native code is to generate a special set of header and stub files for use by your C or C++ files that implement those native methods. To generate these header and stub files, you use the javah program, which is part of the JDK (it's called JavaH in the Mac JDK). First, you'll need to compile your Java program as you would any other Java program, using the Java compiler. Header Files To generate the headers you need for your native methods, use the javah program. For the SimpleFile class listed in the previous section, use one of the following: To generate header files for a class, use the javah program with the name of the class file, minus the .class extension. For example, to generate the header file for the SimpleFile class, use this command line: javah SimpleFile To generate the header file for the SimpleFile class, drag-and-drop the class file onto the JavaH icon. The file SimpleFile.h will be created in the same directory as the SimpleFile.class file. Note that if the class you've given to javah is inside a package, javah prepends the package's full name to the header filename (and to the structure names it generates inside that file) with all the dots (.) replaced by underscores (_). If SimpleFile had been contained in a hypothetical package called acme.widgets.files, javah would have generated a header file named acme_widgets_files_SimpleFile.h, and the various names within it would have been renamed in a similar manner. Listing 20.2 shows the header file that is generated by javah. Listing 20.2. SimpleFile.h (a header file). 1: #include <native.h> 2: /* Header for class SimpleFile */ file:///G|/ebooks/1575211831/ch20.htm (7 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: #ifndef _Included_SimpleFile #define _Included_SimpleFile struct Hjava_lang_String; typedef struct ClassSimpleFile { #define SimpleFile_separatorChar 62L struct Hjava_lang_String *path; long fd; } ClassSimpleFile; HandleTo(SimpleFile); #ifdef extern #endif extern extern extern extern #ifdef } #endif #endif __cplusplus "C" { /*boolean*/ long SimpleFile_open(struct HSimpleFile *); void SimpleFile_close(struct HSimpleFile *); long SimpleFile_read(struct HSimpleFile *,HArrayOfByte *,long); long SimpleFile_write(struct HSimpleFile *,HArrayOfByte *,long); __cplusplus There are a few things to note about this header file. First, note the struct ClassSimpleFile, which contains variables that parallel the instance variables inside your class. Second, note the method signatures at the end of the file; these are the function definitions you'll use in your C or C++ file to implement the actual native methods in the Java code. Stub Files To "run interference" between the Java world of objects, arrays, and other high-level constructs and the lower-level world of C, you need stubs, which translate arguments and return values between Java and C. Stubs are pieces of "glue" code that tie together Java and C. Stubs translate arguments and values and convert the various constructs in each language to something that can be understood in the other. Stubs can be automatically generated by javah, just like headers. There isn't much you need to know about the stub file, just that it has to be compiled and linked with the C code you write to allow it to interface properly with Java. To create stub files, you also use the javah program: Use the javah program with the -stubs option to create the stub file: javah -stubs SimpleFile The file SimpleFile.c will be generated in the same directory as the class file. The stub file was generated at the same time you created the header file. Listing 20.3 shows the result of the stub file for the SimpleFile class. Listing 20.3. SimpleFile.c (a stub file). 1:/* DO NOT EDIT THIS FILE - it is machine generated */ 2:#include <StubPreamble.h> 3: file:///G|/ebooks/1575211831/ch20.htm (8 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries 4:/* Stubs for class SimpleFile */ 5:/* SYMBOL: "SimpleFile/open()Z", Java_SimpleFile_open_stub */ 6:__declspec(dllexport) stack_item *Java_SimpleFile_open_stub(stack_item *_P_, 7: struct execenv *_EE_) { 8: extern long SimpleFile_open(void *); 9: _P_[0].i = (SimpleFile_open(_P_[0].p) ? TRUE : FALSE); 10: return _P_ + 1; 11:} 12:/* SYMBOL: "SimpleFile/close()V", Java_SimpleFile_close_stub */ 13:__declspec(dllexport) stack_item *Java_SimpleFile_close_stub(stack_item *_P_, 14: struct execenv *_EE_) { 15: extern void SimpleFile_close(void *); 16: (void) SimpleFile_close(_P_[0].p); 17: return _P_; 18:} 19:/* SYMBOL: "SimpleFile/read([BI)I", Java_SimpleFile_read_stub */ 20:__declspec(dllexport) stack_item *Java_SimpleFile_read_stub(stack_item *_P_, 21: struct execenv *_EE_) { 22: extern long SimpleFile_read(void *,void *,long); 23: _P_[0].i = SimpleFile_read(_P_[0].p,((_P_[1].p)),((_P_[2].i))); 24: return _P_ + 1; 25:} 26:/* SYMBOL: "SimpleFile/write([BI)I", Java_SimpleFile_write_stub */ 27:__declspec(dllexport) stack_item *Java_SimpleFile_write_stub(stack_item *_P_, 28: struct execenv *_EE_) { 29: extern long SimpleFile_write(void *,void *,long); 30: _P_[0].i = SimpleFile_write(_P_[0].p,((_P_[1].p)),((_P_[2].i))); 31: return _P_ + 1; 32:} Implementing the Native Library The last step, and the most difficult, is to write the C code for your native methods. The header file generated by javah gives you the prototypes of the functions you need to implement to make your native code complete. You then write some C code that implements those functions and provides the native facilities that your Java class needs (in the case of SimpleFile, some low-level file I/O routines). You'll want to include your header file as part of the initial includes for your native implementation: #include <SimpleFile.h> Note This description glosses over a lot of what you might want to do to actually implement those methods. In particular, Java provides several utility functions that help your native methods interact with Java methods and classes and help map C and C++ constructs to their Java equivalents. We'll describe several of these functions later on in today's lesson in the section "Tools and Techniques for Writing Native Implementations." Listing 20.4 shows the native implementation of the methods from the SimpleFile class. file:///G|/ebooks/1575211831/ch20.htm (9 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries Listing 20.4. SimpleFileNative.c, a C implementation of a native method from SimpleFile. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: #include "SimpleFile.h" /* for unhand(), among other things */ #include <sys/param.h> #include <fcntl.h> /* for MAXPATHLEN */ /* for O_RDWR and O_CREAT */ #define LOCAL_PATH_SEPARATOR '/' /* UNIX */ static void fixSeparators(char *p) { for (; *p != '\0'; ++p) if (*p == SimpleFile_separatorChar) *p = LOCAL_PATH_SEPARATOR; } long SimpleFile_open(struct HSimpleFile int fd; char buffer[MAXPATHLEN]; *this) { javaString2CString(unhand(this)->path, buffer, sizeof(buffer)); fixSeparators(buffer); if ((fd = open(buffer, O_RDWR | O_CREAT, 0664)) < 0) /* UNIX open */ return(FALSE); /* or, SignalError() could "throw" an exception */ unhand(this)->fd = fd; /* save fd in the Java world */ return(TRUE); } void SimpleFile_close(struct HSimpleFile close(unhand(this)->fd); unhand(this)->fd = -1; *this) { } long SimpleFile_read(struct HSimpleFile *this, HArrayOfByte *buffer, _ long count) { char *data = unhand(buffer)->body; /* get array data */ int len = obj_length(buffer); /* get array length */ int numBytes = (len < count ? len : count); if ((numBytes = read(unhand(this)->fd, data, numBytes)) == 0) return(-1); return(numBytes); /* the number of bytes actually read */ } long SimpleFile_write(struct HSimpleFile *this, HArrayOfByte *buffer,_ long count) { char *data = unhand(buffer)->body; int len = obj_length(buffer); return(write(unhand(this)->fd, data, (len < count ? len : count))); } Compile Everything into a Shared Library The final step is to compile all the .c files, including the stub file and your native method files. Use your favorite C compiler file:///G|/ebooks/1575211831/ch20.htm (10 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries to compile and link those two files into a shared library (a DLL on Windows). On some systems, you may need to specify special compilation flags that mean "make it relocatable and dynamically linkable." (Those flags, if they are required, may vary from system to system; check with your compiler documentation for details.) Note If you have several classes with native methods, you can include all their stubs in the same .c file, if you like. Of course you might want to name it something else, such as Stubs.c, in that case. The resulting library should be the same name as you gave in your original Java class file as the argument to System.loadLibrary(). In the SimpleFile class, that library was called libmynativelibrary.so. You'll want to name the library that same name and install it wherever your particular system needs libraries to be installed. Using Your Library With all the code written and compiled and installed in the right place, all you have to do is run your Java program using the Java bytecode interpreter. When the Java class is loaded, it will also try to load the native library automatically; if it succeeds you should be able to use the classes in your Java class, and they will transparently run the native libraries as they are needed. If you get an error that the library was not found, the most likely problem is that you do not have your environment set up correctly or that you have not installed your library in the right place. DLL files are located according to the standard Windows algorithm: the directory the application was located in, the current directory, the System directory in Windows 95 (System32 in NT), the System directory in NT, the Windows directory, and then directories listed in the PATH environment variable. UNIX systems use the environment variable LD_LIBRARY_PATH to search for libraries. This environment variable should include the standard places shared libraries are stored, as well as the current directory (.). After LD_LIBRARY_PATH has been set, Java will be able to find your library. Shared libraries for Java must be stored in the folder System Folder: Extensions:JavaSoft Folder. Rather than copying your native library there, you can also just create an alias to your native library and put it in that folder. Tools and Techniques for Writing Native Implementations When writing the code for native implementations, a whole set of useful macros and functions is available for mapping between C and C++ and Java, and for accessing Java runtime structures. (Several of them were used in SimpleFileNative.c.) In addition, there are several rules and techniques for dealing with the conversion between Java and C. In this section you'll learn about those functions and techniques to make writing your native code easier. Names Java names for classes, methods, and variables can be used inside native methods with the following changes (if needed): q Any Unicode characters in names are converted to _0dddd, where the ds represent the Unicode number for that character. For example, the Unicode registered trademark symbol, which is Unicode 00ae, would be represented in C as _000ae. q Package names are included with all names, with the dots replaced by underscores (_). So, for example, java.Math.pi would be java_Math_pi from the native side. q Slashes in package names, if any, are replaced by underscores. q Class names are renamed with the word Class prepended to the full name (including package names) For example, the Java class SimpleFile would be ClassSimpleFile (usually, however, you'll refer to classes through handles, which are explained in the next section). file:///G|/ebooks/1575211831/ch20.htm (11 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries Accessing Java Objects Java objects are passed to native methods using handles to structures. The handle name is the name of the object (including any package names), prepended with the letter H. So, for example, the class SimpleFile would have a handle called HSimpleFile. The class java.lang.String would convert to Hjava_lang.String (remember, class names have package names included, with underscores to separate them). Handles are references to structures that represent Java objects. Each handle has the same name as the class it references, with the letter H prepended. Each native function automatically gets passed at least one handle in its parameter list. This is called the automatic parameter, and it's a handle to the class that contained the original native method. Even if the original name method has no arguments, the C equivalent for that method is passed a handle to the class so it can reference other parts of that object or pass data back to it. In fact, because the handle to the original class behaves as if it were the this object, it's often called this in the native code's method signature as well. The automatic parameter is a handle to the original Java class that called the native method. Because it is roughly equivalent to this in Java, the automatic parameter is also often called this. Note the native method signature for the open() method in SimpleFileNative.c, which shows the automatic parameter: long SimpleFile_open(struct HSimpleFile *this) To get to the methods or variables inside a class, you must dereference that class's handle. To do this, you can use the macro unhand() (as in "Unhand that object!"). The unhand() macro returns a pointer to a struct. So, for example, to get at the variables inside the this handle, you'd reference it like this: unhand(this); After the handle is dereferenced, you can access its variables as if they were normal struct elements: unhand(this)->path; References to arrays are slightly different than references to objects, although both are passed as handles, and you can reference their elements by "unhanding" them as well. In the case of arrays, however, the name of the handle includes the words ArrayOf prepended to the type of the array, and the letter H prepended to that. So, for example, an array of integers, declared like this in Java: int lotsOfInts; would look like this on the native side: HArrayOfInt *lotsOfInts; Calling Methods In the previous section you learned how to deal with references to Java objects as handles. Using unhand(), you can dereference those handles and get to the object's variables. But what about methods? From your native code, you can call methods inside Java objects using several utility functions for just that purpose. In addition, as you pass data back and forth between the Java side and the native side, you'll need to know how data types convert and how to deal with those types in either side. Functions for Executing Methods To call methods inside Java objects from within native code, you use special utility functions. To call a regular Java method, file:///G|/ebooks/1575211831/ch20.htm (12 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries use the function execute_java_dynamic_method(). To call a class method, use the function execute_java_static_method(). Here's the signature for these functions (from the Java include file interpreter.h, which defines things like this): long execute_java_dynamic_method(ExecEnv *env, HObject *obj, char *method_name, char *signature, ...); long execute_java_static_method(ExecEnv *env, ClassClass *cb, char *method_name, char *signature, ...); Both functions take at least four arguments: q An ExecEnv structure, which defines the current execution environment. Right now the only possible value for this argument is 0, which refers to the current execution environment. q For dynamic methods, a reference to the object in which the method you're calling is defined. This would be the left side of the dot in normal Java dot notation. Here, it's a handle to that object. q For static (class) methods, a reference to the class structure in which the method is defined. You can get a hold of a reference to a class using the FindClass() and FindClassFromClass() functions, described later on in this section. q The method name (as a string). q The method signature. Any remaining arguments to the execute_java_static_method() and execute_java_dynamic_method() functions are arguments to the method itself. Method signatures can be complex, because in this case they are not simply the list of arguments and the return types. Method signatures, for this function, are strings with a set of parentheses containing an argument list, and a return type just after the closing parentheses. Both the argument list and the return type are letters or strings that represent a type. For the primitive types, use single-letter codes for the argument list and the return type (B is byte, I is int, V is void, and Z is boolean). For arrays, use an open square bracket before the type (for example, [B denotes a byte array). More letter codes for different types are contained in the Java include file signature.h. So, for example, a method that has no arguments and returns void would have a signature of ()V. One that take three integer arguments and returns an integer would have a signature of (III)V. For object arguments, the code is the letter L, then the class name (including the package, with all elements separated by slashes), followed by a semicolon. So, for example, a reference to a String object would be Ljava/lang/String;. Got all that? Here are a few examples: execute_java_dynamic_method(0, this, "close", "()Z" execute_java_static_method(0, MyClass, "reverseString", "(Ljava/lang/String;)Ljava/lang/String;", "This is my string"); execute_java_dynamic_method(0, this, "open_speaker()", "(Lcom/lne/audio/Device;)Z", theDevice); The FindClass() and FindClassFromClass() functions can be used to get a reference to a class structure (a pointer of type ClassClass) for use with the execute_java_static_method() function. Here are their signatures: ClassClass *FindClass(ExecEnv *env, char *className, bool_t resolve); ClassClass *FindClassFromClass(ExecEnv *env, char *className, bool_t resolve, ClassClass *from); As with the functions for calling methods, the first argument should be 0 to indicate that this function is to be run in the current environment. The second argument is the class name to find. The resolve argument is a boolean which, if TRUE or 1, indicates that the resolve Class() method should be called on that class (class resolution is a function of the class file:///G|/ebooks/1575211831/ch20.htm (13 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries loader; it's probably safe to use TRUE for this argument in most cases). In the case of FindClassFromClass, the fourth argument is an already existing class; the class loader that loaded that class will also be used to find and load the new class. Passing Parameters Back and Forth To pass parameters to Java methods from native code or vice versa, you have to understand how data types convert between the two sides of the process. The primitive data types in Java convert to their nearest equivalents in C. All the Java integer types (char, byte, short, int) and boolean convert to C long types; long converts to int64_t, and float and double remain floats and doubles. Keep in mind that because of these conversions, your original native method definitions may need return types that reflect the values sent back from the C side of the native method (for example, all methods that return integer types must actually return long). Object types are passed as handles to structures, as you learned earlier, and must be dereferenced using unhand() in order to be used. Creating New Java Objects Because you can access Java objects and call methods from inside your native code, the one thing left is the capability to create new objects. You can do this too, using the execute_class_constructor() function. This function is very similar to the functions for calling methods; in fact, it has the same set of arguments that execute_java_static_method() does: HObject *execute_java_constructor(ExecEnv *, char *classname, ClassClass *cb, char *signature, ...); The execute_java_static_method() function has four arguments, but can have more. The four required arguments are q 0, for the current environment (the only value of this argument currently supported). q A string representing the class name that defines this constructor. q A class handle such as the one you'd get from FindClass(). If you use a class name, this argument should be NULL; if you use a class object, the class name should be NULL (use one or the other, not both). Using class references over class names can be more efficient if you expect to create lots of objects with the same class, because you can just use the same class reference over and over again (class names must be looked up each time). q The signature of the constructor which, as with the functions to execute Java methods, is a string representing the arguments to the method (constructors don't have a return type). As with the functions to call methods, [T is array of type T, B is byte, I is int, and Z is boolean. Other types are defined in signature.h (part of the standard Java include files). q Any other arguments to the constructor are added onto the end of the parameter list. Here are some examples: execute_java_constructor(0, "MyClass", NULL, "()"); execute_java_constructor(0, "MyOtherClass", NULL, "(II)", 10, 12); The first example creates an instance of the MyClass class, using the constructor with no arguments. The second creates an instance of MyOtherClass, in which the constructor has two integer arguments. Those arguments, 10 and 12, are included at the end of the parameter list. Handling Exceptions To handle errors, Java has exceptions. In your native C code, you can set up a Java exception using SignalError, like this: SignalError(0, JAVAPKG "ExceptionClassName", "message"); file:///G|/ebooks/1575211831/ch20.htm (14 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries Here, the exception class name is the name of a Java exception class, including its package name, with the separation of package names delineated with a slash rather than a period as in Java. So, for example, the class java.io.IOException would be "java/io/IOException" when used inside SignalError. The exception will be thrown in Java when your native method returns (which it should immediately after the SignalError). Note that just like regular methods, native methods that throw exceptions must be declared to throw those exceptions using the throw keyword. Dealing with Strings Several functions and macros are available in the include file javaString.h to help manage strings. To gain access to these functions, include that header as part of your native code: #include <javaString.h> The makeJavaString() function creates a Java String object out of a C string. To convert a Java String object into a C string, you can use makeCString() or allocCString() (where the former allocates the string from temporary storage and the latter from the heap). Here are their signatures: Hjava_lang_String char char *makeJavaString(char *string, int length) *makeCString(Hjava_lang_String *s) *allocCString(Hjava_lang_String *s) To copy Java Strings into preexisting Unicode or ASCII C buffers, you can use javaString2unicode() and javaString2CString(): unicode char *javaString2unicode(Hjava_lang_String *s, unicode *javaString2CString(Hjava_lang_String *s, char *buf, int *buf, int len) len) Finally, the javaStringPrint() function prints a Java String object (just like System.out.print()), and the javaStringLength() function gets its length: void int javaStringPrint(Hjava_lang_String *s) javaStringLength(Hjava_lang_String *s) Summary Today you have learned about the advantages and disadvantages of using native methods, about the many ways that Java (and you) can make your programs run faster, and also about the often illusory need for efficiency. Finally, you learned the procedure for creating native methods, from both the Java and the C sides, in detail-by generating header files and stubs, and by compiling and linking a full example. After working your way through today's difficult material, you've mastered one of the most complex parts of the Java language. As a reward, tomorrow we'll look "under the hood" to see some of the hidden power of Java, and you can just sit back and enjoy the ride. Q&A Q: A: Your descriptions here are somewhat sparse. What can I use to supplement what I've learned here? Look at Sun's Java tutorial (online or on the CD-ROM included with this book) for a more detailed version of how to work with native methods. file:///G|/ebooks/1575211831/ch20.htm (15 of 16) [11/06/2000 7:44:48 PM] Day 20 -- Using Native Methods and Libraries Q: A: Q: A: Does the Java class library need to call System.loadLibrary() to load the built-in classes? No, you won't see any loadLibrary() calls in the implementation of any classes in the Java class library. That's because the Java team had the luxury of being able to statically link most of their code into the Java environment, something that really makes sense only when you're in the unique position of providing an entire system, as they are. Your classes must dynamically link their libraries into an already-running copy of the Java system. This is, by the way, more flexible than static linking; it allows you to unlink old and relink new versions of your classes at any time, making updating them trivial. Can I statically link my own classes into Java like the Java team did? Yes. You can, if you like, ask Sun Microsystems for the sources to the Java runtime environment itself, and, as long as you obey the (relatively straightforward) legal restrictions on using that code, you can relink the entire Java system plus your classes. Your classes are then statically linked into the system, but you have to give everyone who wants to use your program this special version of the Java environment. Sometimes, if you have strong enough requirements, this is the only way to go, but most of the time, dynamic linking is not only good enough, but preferable. file:///G|/ebooks/1575211831/ch20.htm (16 of 16) [11/06/2000 7:44:48 PM] Day 22 -- Java Programming Tools Day 22 Java Programming Tools by Michael Morrison CONTENTS q Overview of the Standard JDK Tools q The Runtime Interpreter r r The OptionsArgument r q Usage The Non-Optimized Interpreter The Compiler r r The OptionsArgument r q Usage The Non-Optimizing Compiler The Applet Viewer r r The OptionsArgument r Commands r q Usage Profiling Java Applets The Debugger r r The OptionsArgument r q Usage Commands The Class File Disassembler r r q Usage The OptionsArgument The Header and Stub File Generator r r q Usage The OptionsArgument The Documentation Generator r Usage r The OptionsArgument file:///G|/ebooks/1575211831/ch22.htm (1 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools r q Documentation Tags Visual Development Tools r Sun's Java WorkShop r Symantec Café r Microsoft Visual J++ r Natural Intelligence's Roaster r Rogue Wave Software's JFactory r Penumbra Software's Mojo r Aimtech's Jamba r Kinetix's Hyperwire q Summary q Q&A Trying to perform any craft without the proper tools is a daunting task at best. Java programming is indeed a craft, and like woodworking or engraving, your level of programming success largely depends on your choice of tools as well as your skill in using the tools. You begin this bonus week by looking inside the standard Java programming tools included with the Java Developer's Kit (JDK). Today's lesson isn't just a cursory glance at the Java tools, however. You actually dig into the details of using the tools, including some hidden features and capabilities that seem to have been glossed over in much of the Java documentation. After learning the ins and outs of the standard JDK tools, you'll finish up the lesson by taking a look at some of the more popular Java visual development tools. Today's lesson covers the following major topics: q The tools included with the JDK and where to get the latest versions q Executing programs with the Java runtime interpreter q Compiling source files with the Java compiler q Debugging programs with the Java debugger q Visual development tools By the end of today's lesson, you will be well acquainted with the standard JDK tools and how they work. This insight into the standard tools will allow you to use them more effectively in your own projects. Even if you decide to use one of the visual tools highlighted toward the end of the lesson, such as Symantec Café or Visual J++, you may still sometimes find the JDK tools invaluable in certain situations. Overview of the Standard JDK Tools The JDK provides a core set of tools necessary for developing programs in Java. Even though the JDK tools aren't particularly fancy in their implementation, they are guaranteed to work with the latest Java release because updated JDK tools are written in Java and are a part of each release. And although third-party add-ons and development environments promise to make Java development smoother and easier, the JDK provides all the essential tools and information necessary to write professional Java applets immediately and file:///G|/ebooks/1575211831/ch22.htm (2 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools at no cost. Because the JDK is Sun's official development kit for Java, you can always count on it to provide the most extensive Java support. Following is a complete list of the tools that are standard with the JDK: q The runtime interpreter q The compiler q The applet viewer q The debugger q The class file disassembler q The header and stub file generator q The documentation generator You'll learn about each of these tools in detail in today's lesson. Before you get started, however, it's important to make sure you have the latest version of the JDK. As of this writing, the latest version of the JDK is version 1.02, which is included on the accompanying CD-ROM. This version will probably be around for a while, so you should be okay using it. Just to be sure, you can check Sun's Java Web site at http://www.javasoft.com to see what the latest version is. This Web site provides all the latest news and information regarding Java, including the latest release of the JDK. Keep in mind that Java is a new technology that is still in a state of rapid change. Be sure to keep an eye on the Java Web site for the latest information. The Runtime Interpreter The Java runtime interpreter is a standalone version of the Java interpreter built into Java-compatible Web browsers, such as Netscape Navigator 3.0 and Microsoft Internet Explorer 3.0. The runtime interpreter provides the support to run Java executable programs in the compiled bytecode class format. Since the interpreter doesn't directly provide any means to view graphical output, you are limited to using it to execute purely textual Java programs and applications that manage their own graphics. If you want to run graphical Java applets, you need to use either the Java applet viewer or a Java-compatible Web browser. You can think of the runtime interpreter as exposing the bare essentials of the Java runtime system. Even though I use the term bare essentials, the interpreter actually lets you do quite a lot. Essentially, you can run any Java programs that don't rely on the Applet class. In fact, the statement earlier about not being able to run graphical programs isn't entirely true; you can run graphical Java applications, but you just can't run Java applets. The difference between a Java application and a Java applet is that an application is responsible for creating and maintaining its own window should it require the need for graphical output, whereas an applet relies on a Web browser to provide a window on which to display graphics. So the Java interpreter is capable of executing both textual Java programs and graphical Java applications, but not applets. Usage The runtime interpreter is a command-line tool for running Java programs and applications; Java applets require the graphics and display support of a Web browser. New Term file:///G|/ebooks/1575211831/ch22.htm (3 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools A command-line tool is a tool that is executed at a command prompt, such as a DOS or UNIX shell prompt, with a specified list of arguments. The syntax for using the Java runtime interpreter follows: java Options Classname Arguments The Classname argument specifies the name of the class you want to execute. If the class resides in a package, you must fully qualify the name. For example, if you want to run a class called SolveIt that is located in a package called Equations, you would execute it in the interpreter like this: java Equations.SolveIt When the Java interpreter executes a class, what it is really doing is executing the main method of the class. The interpreter exits when the main method and any threads created by it are finished executing. The main method accepts a list of arguments that can be used to control the program. Following is the definition of the main method as specified by the Java language: class DoIt { public static void main(String argv) { // do something } } Notice that main has a single parameter, argv, which is an array of String objects. This brings us to the Arguments argument for the runtime interpreter, which specifies the arguments passed into the main method. Any arguments passed to the runtime interpreter via Arguments are accessible from the argv parameter in main. The following interpreter call passes two numeric arguments to the main method in the DoIt class: java DoIt 8 24 Technical Note The fact that the Java runtime interpreter actually executes the main method when running a class should give you an idea about one of the reasons why you can't run applets using the runtime interpreter. Give up? The answer is that applets don't even have a main method, so there is no way for the runtime interpreter to know how to begin executing an applet. The OptionsArgument The Options argument specifies options related to how the runtime interpreter executes the Java program. Following is a list of the most common runtime interpreter options: file:///G|/ebooks/1575211831/ch22.htm (4 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools -debug -checksource or -cs -classpath Path -mx x -ms x -noasyncgc -noverify -prof -ss x -oss x -t -verbose or -v -verbosegc -verify -verifyremote -DPropertyName=NewValue The -debug option starts the interpreter in debugging mode, which allows you to use the Java debugger (jdb) in conjunction with the interpreter. You'll learn more about using the Java debugger a little later in today's lesson. The -checksource option causes the interpreter to compare the modification dates of the source code files and executable class files. If the source file is more recent, the class is automatically recompiled and the new bytecode executable is loaded. The Java interpreter uses an environment variable, CLASSPATH, to determine where to look for user-defined classes. The CLASSPATH variable contains a semicolon-delimited list of system paths to user-defined Java classes. Actually, most of the Java tools use the CLASSPATH variable to know where to find user-defined classes. The -classpath option informs the runtime interpreter to override CLASSPATH with the path specified by Path. The -mx x option allows you to modify the maximum size of the memory allocation pool, or garbage collection heap, used by the interpreter. By default, the pool has a maximum size of 16MB (-mx 16m). x specifies the new maximum size of the pool and is measured in bytes by default. You can also specify x in either kilobytes or megabytes by appending the letter k or m (respectively) onto the value. Also, x must be greater than 1000 bytes, meaning that the pool must have a maximum size of at least 1000 bytes. The -ms x option is similar to the -mx option, except it allows you to modify the initial size of the memory allocation pool rather than the maximum size. By default, the size of the pool is initially set to 1MB (-ms 1m). x specifies the new initial pool size, and is measured in bytes by default. Similar to the -mx option, you can also specify x in either kilobytes or megabytes by appending the letter k or m (respectively) onto the value. Additionally, x must be greater than 1000 bytes. The Java runtime system typically performs garbage collection automatically to make sure unneeded memory stays freed up. This takes place in an asynchronous thread that runs alongside other threads in the runtime system. The -noasyncgc option alters this behavior by turning off asynchronous garbage collection. The result is that no garbage collection takes place unless it is explicitly called on or the Java program runs out of memory. Technical Note file:///G|/ebooks/1575211831/ch22.htm (5 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools You can force an explicit garbage collection by calling the gc method in the System class. The -noverify option turns all code verification off, meaning that no bytecodes are processed by the bytecode verifier. Typically, the verifier verifies code loaded into the system using a class loader. The runtime interpreter includes a built-in profiler, which is invoked using the -prof option. The profiler's job is to report on the amount of time spent in each section of code as a program is executing, which can often be used to find performance bottlenecks in the code. The built-in profiler writes the profile information to a file called java.prof, which is a text file. The profile information consists of how many times each method was called and the relative amount of time spent in the method during each call. The larger the latter number is, the more costly the method in terms of processor overhead. You can easily use this information as a guide to determine the code on which to focus your code optimization efforts. Note Since the runtime interpreter, and therefore the built-in profiler, can only be used with textual Java programs and standalone applications, you may be wondering how to profile Java applets. Fortunately, you can use the profiler in the runtime interpreter in conjunction with the Java applet viewer. You'll learn how to do this a little later today when you find out about the applet viewer. Every thread in the Java runtime system is given two stacks: one for Java code and one for C/C++ code. The presence of two stacks reflects the native code support in Java. The -ss x option allows you to alter the maximum stack size used by C code in a thread. The default C stack size is 128KB (-ss 128k). The x parameter specifies the new maximum size in bytes of the C stack, which must be greater than 1000 bytes. You can also specify x in either kilobytes or megabytes by appending the letter k or m (respectively) onto the value. Keep in mind that this option applies to all threads created during program execution. Similar to the -ss x option, the -oss option allows you to set the maximum stack size that can be used by the Java code in a thread. The default Java code stack size is 400KB (-oss 400k). The x parameter specifies the new maximum size in bytes of the Java stack, which must be greater than 1000 bytes. The -t option prints a trace of the bytecode instructions executed. This option only works with the non-optimized version of the Java interpreter, java_g. (You'll learn about the non-optimized interpreter in a moment.) The -t option generates a great deal of information that can give you a lot of insight into what is happening within a program, provided you are good at following raw bytecodes! The -verbose option causes the interpreter to print a message to standard output each time a Java class is loaded. Similarly, the -verbosegc option causes the interpreter to print a message each time a garbage collection is performed. A garbage collection is performed by the runtime system to clean up unneeded objects and to free memory. The opposite of the -noverify option, the -verify option causes the interpreter to run the bytecode verifier on all code loaded into the runtime environment. The default function of the verifier is to only verify code loaded into the system using a class loader. This default behavior can also be explicitly specified using the -verifyremote option. The -D option allows you to redefine system property values. PropertyName specifies the name of the system property you want to change, and NewValue specifies the new value you want to assign to it. file:///G|/ebooks/1575211831/ch22.htm (6 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools New Term System properties are global system variables that reflect the state of the Java runtime system. For example, the version of the Java runtime system is stored in the java.version system property. The Non-Optimized Interpreter Some distributions of the Java Developer's Kit include an alternate Java interpreter called java_g. This is a non-optimized version of the Java interpreter that executes Java bytecodes in a manner more suitable for debugging. If this interpreter is in your JDK distribution, be sure to use it when you are executing code within the Java debugger. The Compiler The Java compiler (javac) is used to compile Java source code files into executable Java bytecode classes. In Java, source code files have the extension .java. As you've seen throughout this book, Java source code files are standard ASCII text files, much like the source code files for other popular programming languages like C++. It is the job of the Java compiler to process Java source code files and create executable Java bytecode classes from them. Executable bytecode class files have the extension .class and represent a Java class in its usable form. Java class files are generated on a one-to-one basis with the classes defined in the source code. In other words, the Java compiler generates exactly one .class file for each class you create. Since it is technically possible to define more than one class in a single source file, it is therefore possible for the compiler to generate multiple class files from a single source file. When this happens, it means that the source file contains multiple class definitions. You may have heard something about just-in-time compilers in relationship to Java. It's important not to get these compilers confused with the Java compiler and the role it plays. The Java compiler is responsible for turning Java source code into Java bytecodes that can be executed within the Java runtime system. The Java virtual machine, which is a component of the runtime system, is responsible for interpreting the bytecodes and making the appropriate system level calls to the native platform. It is at this point where platform independence is achieved by Java; the bytecodes are in a generic form that is only converted to a native form when processed by the virtual machine. Just-in-time compilers remove the role of the runtime interpreter by converting Java bytecodes to native code on-the-fly before executing a Java program. In this way, just-in-time Java compilers work more like the back end of traditional language compilers in that they generate code for a native platform. Similarly, the Java compiler works more like the front end of a traditional compiler in that it parses Java source code and generates internally useful bytecode classes. Note Both Netscape Navigator 3.0 and Microsoft Internet Explorer 3.0 include just-in-time Java compilers. Keep in mind that Java executables are still centered around the bytecode class format. Even with just-in-time compilers in the picture, all you must be concerned with as a developer is generating the appropriate bytecode classes using the Java compiler. If no just-in-time compiler is present on a user's system, the file:///G|/ebooks/1575211831/ch22.htm (7 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools bytecode classes will be processed and executed by the runtime interpreter. On the other hand, if a just-in-time compiler happens to exist on the system, the bytecode classes will be converted to native code and then executed. Either way, the key to executing Java programs is the bytecode classes, which are created by the Java compiler. Usage The Java compiler is a command-line tool whose syntax follows: javac Options Filename The Filename argument specifies the name of the source code file you want to compile. The compiler will generate bytecode classes for all classes defined in this file. Likewise, the compiler will also generate bytecode classes for any dependent classes that haven't been compiled yet. In other words, if you are compiling class A, which is derived from class B, and class B has not yet been compiled, the compiler will notice the dependency and go ahead and compile both classes. The OptionsArgument The Options compiler argument specifies options related to how the compiler creates the executable Java classes. Following is a list of the compiler options: -classpath Path -d Dir -g -nowarn -O -verbose The -classpath option tells the compiler to override the CLASSPATH environment variable with the path specified by Path. This causes the compiler to look for user-defined classes in the path specified by Path. Path is a colon-delimited list of directory paths taking the following form: .;YourPath An example of a specific usage of -classpath follows: javac -classpath .;\dev\animate\classes;\dev\render\classes A.java In this case, the compiler is using a user-defined class path to access any classes it needs while compiling the source code file A.java. The -classpath option is sometimes useful when you want to try compiling something without taking the trouble to modify the CLASSPATH environment variable. The -d option determines the root directory where compiled classes are stored. This is important because many times classes are organized in a hierarchical directory structure. With the -d option, the directory structure will be created beneath the directory specified by Dir. The -g compiler option causes the compiler to generate debugging tables for the Java classes. Debugging file:///G|/ebooks/1575211831/ch22.htm (8 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools tables are used by the Java debugger and contain information such as local variables and line numbers. The default action of the compiler is to only generate line numbers. New Term A debugging table is a collection of information about a program that is used internally by a debugger. Debugging tables are built directly into executable classes during compilation. Warning If you are going to be using the Java debugger to debug the classes generated by the compiler, you must use the -g option. Additionally, for debugging make sure you don't use the -O option, which optimizes the code. The -nowarn option turns off compiler warnings. Warnings are printed to standard output during compilation to inform you of potential problems with the source code. It is generally a good idea to keep warnings enabled because they often signal problem areas in your code. However, you may run into a situation where warnings are getting in the way, in which case the -nowarn option might be useful. The -O option causes the compiler to optimize the compiled code. In this case, optimization simply means that static, final, and private methods are compiled inline. When a method is compiled inline, it means that the entire body of the method is included in place of each call to the method. This speeds up execution because it eliminates the method call overhead. Optimized classes are usually larger in size to accommodate the duplicate code. The -O optimization option also suppresses the default creation of line numbers by the compiler. Keep in mind that the -O option should not be used when you plan on debugging the compiled code using the Java debugger. New Term Method inlining is the process of replacing each call to a method with the actual method code. Inlining often increases the size of the resulting class file, but it can help improve performance. The -verbose option has somewhat of an opposite effect as the -nowarn option-it prints out extra information about the compilation process. You can use -verbose to see exactly what source files are being compiled and what class files are being loaded. The Non-Optimizing Compiler Some distributions of the Java Developer's Kit include an alternate Java compiler called javac_g. This version of the Java compiler generates code without some of the internal optimizations performed by the standard javac compiler. If this compiler is in your JDK distribution, be sure to use it when you are compiling code for debugging. Otherwise, stick with the javac compiler for all release code. The Applet Viewer The typical method of executing a Java applet is from within a Web browser that has a Web page loaded containing the applet. This is the typical scenario in which most Web users come into contact with Java applets. As a Java developer, you have another option for running Java applets that doesn't involve the use of file:///G|/ebooks/1575211831/ch22.htm (9 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools a Web browser. This option is the Java applet viewer, which serves as a minimal test bed for Java applets. At times you may not want to hassle with using a full-blown Web browser to test an applet, in which case the applet viewer is an ideal alternative. Even though the applet viewer logically takes the place of a Web browser, it functions very differently from a Web browser. The applet viewer operates on HTML documents, but it only looks for embedded applet tags; it ignores any other HTML code in the document. Each time the applet viewer encounters an applet tag in an HTML document, it launches a separate applet viewer window containing the respective applet. The only drawback to using the applet viewer is that it doesn't show you how an applet will run within the confines of a real Web setting. Since the applet viewer ignores all HTML codes except applet tags, it doesn't even attempt to display any other information contained in the HTML document. So once you've tested your applet using the applet viewer, be sure to also test it using a Web browser just to make sure it works in the context of a real Web page. Usage The Java applet viewer is a command-line tool, meaning that it is invoked from a command prompt. The syntax for the applet viewer follows: appletviewer Options URL The URL argument specifies a document URL containing an HTML page with an embedded Java applet. The applet viewer launches a separate window for each applet embedded in the HTML document. If the document doesn't contain any embedded applets, the applet viewer will simply exit. Figure 22.1 shows the applet viewer in action. Figure 22.1 shows the Animator demo applet, which comes with the Java Developer's Kit, running in the applet viewer. You run the applet by changing to the directory containing the Animator bytecode class and embedded HTML file and then executing the following statement at the command prompt: Figure 22.1 : The Animator applet running in the Java applet viewer. appletviewer example1.html example1.html is the HTML file containing the embedded Java applet. As you can see, there's nothing complicated about running Java applets using the applet viewer. The applet viewer is a useful and easy-to-use tool for testing Java applets in a simple environment. The OptionsArgument The Options argument to the applet viewer specifies how to run the Java applet. There is currently only one option supported by the applet viewer, -debug. The -debug option starts the applet viewer in the Java debugger, which allows you to debug applets. You'll learn more about using the Java debugger a little later in today's lesson. file:///G|/ebooks/1575211831/ch22.htm (10 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools Commands The applet viewer has a drop-down menu called Applet containing a group of commands, as shown in Figure 22.2. Figure 22.2 : The Java applet viewer with commands available in the drop-down menu. The Restart command restarts the currently loaded applet, resulting in a call to the start method for the applet. The Restart command does not reload the applet, however. Similar to Restart, the Reload command reloads the applet and then starts it. Reload is often a better command to use to restart applets as it ensures that an applet is completely reinitialized. The Clone command launches another instance of the applet viewer executing the same applet. This command is useful when you want to run multiple copies of an applet. For example, a multiuser network applet might support multiple instances that can communicate with each other. You could load one instance of the applet and then use the Clone command to start other instances. The Tag command displays a window showing the HTML applet tag for the executing applet. The Applet HTML Tag window is shown in Figure 22.3. Figure 22.3 : The Applet HTML Tag window displayed by the Tag command. The Info command displays a window showing information about the executing applet, including general applet information and information relating to the parameters used by the applet. This information is returned by the getAppletInfo and getParameterInfo methods of the Applet class. The Applet Info window is shown in Figure 22.4. Figure 22.4 : The Applet Info window displayed by the Info command. The Edit command is disabled in the current release of the applet viewer. It will presumably be activated in a future release of the applet viewer, in which case it will probably provide a way to alter the applet parameters in the HTML document containing the applet tag. The Properties command displays a window with access options relating to HTTP and firewall proxies and servers, along with network and class access options. The AppletViewer Properties window is shown in Figure 22.5. Figure 22.5 : The Applet Viewer Properties window displayed by the Properties command. Finally, the Close and Quit commands perform the same function, which is shutting down the applet viewer. It's not clear why there are two different commands for closing the applet viewer-it's presumably an oversight. Profiling Java Applets You learned a little earlier today about the profiler built into the Java runtime interpreter. You learned that you can't profile applets using the runtime interpreter alone because you can't even run applets using the interpreter. However, you can profile applets by running the interpreter's profiler in conjunction with the applet viewer. In this case, the applet viewer is launched from within the runtime interpreter, like this: java -prof sun.applet.AppletViewer URL file:///G|/ebooks/1575211831/ch22.htm (11 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools URL specifies the name of the HTML file containing an applet tag (or tags). Notice that the applet viewer is referenced using its fully qualified class name, AppletViewer. When you finish running the applet, the interpreter writes a text file named java.prof to the current directory. This file contains profile information for the applet you just ran. Refer to the earlier discussion of the profiler in the section "The Runtime Interpreter" for information regarding the meaning of the contents of this file. The Debugger The Java debugger (jdb) is a command-line utility that enables you to debug Java programs. The Java debugger uses the Java Debugger API to provide debugging support within the Java runtime interpreter. Although the debugger is a command-line tool, it still provides a wide range of standard debugging features such as setting breakpoints and single-stepping through code. New Term A breakpoint is a line of code you specify that halts the execution of a program. New Term Single-stepping is the process of executing your code one line at a time (in single steps). Before you can use jdb, you must compile your code so that it includes debugging information. The Java compiler switch for doing this is -g, which causes the compiler to generate debugging tables containing information about line numbers and variables. Note Some distributions of the JDK also include an alternative Java compiler called javac_g. If you have this compiler in your distribution (look in the java/bin directory), use it, because it compiles code without using some of the internal optimizations performed by the javac compiler. Because debugging is a very broad subject, I've tried to keep this discussion focused on the Java debugger and the basics of how it is used. For a more hands-on look at Java debugging, you may want to check out Sun's online Java debugger tutorials, which are located on Sun's Java Web site at http://www.javasoft.com/products/JDK/debugging/. Usage The syntax for using the Java debugger follows: jdb Options <Classname> The Classname argument is optional and specifies the name of the class you want to execute. The fact that Classname is optional brings up an interesting point regarding the usage of the debugger: There are two different ways to go about using the debugger, depending on whether you are debugging an application or an applet. For applications, you simply execute jdb directly and provide the name of the main class in the file:///G|/ebooks/1575211831/ch22.htm (12 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools Classname argument, as the previous syntax shows. If you are debugging an applet, however, you must execute the debugger within the applet viewer, like this: appletviewer -debug URL In this case, URL refers to a document URL containing an HTML page with the applet to be debugged. Instead of directly executing the class, the applet viewer launches the debugger and allows you to debug the applet. Technically, there are three ways to use the Java debugger. The third technique involves attaching the debugger to an application that is already running in the interpreter. You'll learn a little more about this debugging approach in the next section. The OptionsArgument The Options argument is used to specify different settings regarding how a debugging session is started. Following is a list of the debugging options: -host Hostname -password Password The -host option is used to specify the name of the host machine where an existing Java interpreter is running. In this case, the debugger attaches itself to the interpreter so the currently executing application can be debugged. You specify the name of the host machine in the Hostname argument. The -password option is also used when attaching the debugger to an existing interpreter session. When the interpreter is started with the -debug option, a password is displayed that must be used when initiating the debugging session. You specify this password to the debugger via the -password option and the Password argument. Commands When the debugger is up and running, you control it through commands that are entered at a command-line prompt. The debugger command-line prompt is a > prompt by default, similar to DOS or UNIX shell prompts. This prompt specifies that there is no default thread running. The thread that is currently executing in the debugger is displayed in the command prompt itself, so the > prompt signifies that no thread is currently being debugged. When you are debugging a thread, the command prompt changes to a thread name followed by the current position of the stack frame, which is enclosed in square brackets. An example of a thread prompt is main[1], which signifies that the main thread is running and you are at the topmost position (1) in the stack frame. Following is a list of some of the most useful debugging commands: help locals print Object dump Object methods Class classes stop in Classname.Methodname file:///G|/ebooks/1575211831/ch22.htm (13 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools stop at Classname.LineNumber step cont clear <Classname.LineNumber> Possibly the most important command in jdb is the help command, which prints out a listing of all the available commands and what they do. The next three commands are all related to printing information about objects. The locals command displays the current value of all the objects in the current scope (stack frame). The print and dump commands are both used on objects independent of the current scope. The print command is used to print both entire objects and individual member variables; you simply specify the name of the object or member variable in the Object argument. Similar to print, the dump command also prints objects or member variables, but it prints more detailed information such as an object's inheritance. The methods command is used to list all the methods defined in the class specified by Class. The classes command lists all the classes that are currently loaded into memory. The list generated by the classes command is often pretty large since many different classes end up being loaded behind the scenes even in simple Java programs. Now that you have an idea how to look at the values of different things in the debugger, let's move on to some commands that are a little more exciting. The stop in and stop at commands are used to set breakpoints in methods and at specific lines of source code, respectively. For example, to set a breakpoint in the mouseDown method of an applet called Groovy, you would type the following command at the debugger command line: stop in Groovy.mouseDown When you click the mouse button in the applet window, the debugger will halt the applet at the beginning of the mouseDown method. To begin single-stepping through the method, you use the step command. The debugger executes one line of code for each step command issued. When you find out the information you need and are ready to get things running at full speed again, you use the cont command, which continues the normal execution of the program. Likewise, you can clear any breakpoints you set with the clear command. That sums up the basics of using the Java debugger. Like any powerful tool, you'll gain confidence with the debugger by simply tinkering with it. I suggest running the debugger on a simple program and getting acquainted with some of the commands before trying to take on a serious debugging project. The Class File Disassembler The Java class file disassembler (javap) is used to disassemble a class file, which means the executable class file is resolved into a list of public data, methods, or raw bytecode instructions. The disassembler's default output consists of the public data and methods for a class. The class file disassembler is useful in cases where you don't have the source code for a class but you'd like to know something about how it is implemented. file:///G|/ebooks/1575211831/ch22.htm (14 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools Usage The syntax for the disassembler follows: javap Options ClassNames The ClassNames argument specifies the names of one or more classes to be disassembled. The OptionsArgument The Options argument specifies how the classes are to be disassembled. The disassembler supports the following options: -c -p -h -classpath Path -verify -version The -c option tells the disassembler to output the actual bytecodes for each method. The -p option tells the disassembler to also include private variables and methods in its output. Without this option, the disassembler only outputs the public member variables and methods. The -h option specifies that information be created that can be used in C header files. This is useful when you are attempting to interface C code to a Java class for which you don't have the source code. The -classpath option informs the disassembler to override CLASSPATH with the path specified by Path when looking for the input class or classes. The -verify option tells the disassembler to run the verifier on the class and output debugging information. Finally, the -version option causes the disassembler to print its version number. The Header and Stub File Generator The Java header and stub file generator (javah) is used to generate C header and source files for implementing Java methods in C. The files generated can be used to access member variables of an object from C code. The header and stub file generator accomplishes this by generating a C structure whose layout matches that of the corresponding Java class. Note You learned how to use the javah header and stub file generator on Day 20, "Using Native Methods and Libraries." You can think of today's coverage as more of a reference for the javah tool itself since you learn about all the options supported by javah. file:///G|/ebooks/1575211831/ch22.htm (15 of 22) [11/06/2000 7:44:52 PM] Day 22 -- Java Programming Tools Usage The syntax for using the header and stub file generator follows: javah Options ClassName The ClassName argument is the name of the class to generate C source files from. The OptionsArgument The Options argument specifies how the source files are to be generated. Following are the options supported by the stub file generator: -o OutputFile -d Dir -td Dir -stubs -verbose -classpath Path The -o option is used to concatenate the resulting header and source files when multiple classes are being operated on. When used, the -o option results in the concatenated information being stored in the file specified by OutputFile. The -d option determines the root directory where the generated header and source files are stored. Along with writing the header and source files, the header and stub file generator also writes its own temporary files. The -td option specifies the directory where these temporary files are stored. By default, temporary files are stored in the directory specified by the %TEMP% environment variable; the -td option overrides this directory with Dir. The -stubs option is probably the most important option supported by the header and stub file generator. The -stubs option causes C declarations to be generated from the specified Java class or classes. Without the -stubs option, only header files are generated. When you use the -stubs option, the header and stub file generator creates both header and stub files, which are both typically required to incorporate native C code with Java. The -verbose option causes the header and stub file generator to print a message to standard output regarding the status of files as they are being generated. Finally, the -classpath option informs the header and stub file generator to override CLASSPATH with the path specified by Path when looking for the input class. The Documentation Generator The Java documentation generator (javadoc) is a useful tool for generating programming documentation directly from Java source code. The documentation generator parses through Java source files and generates HTML pages based on the declarations and comments. Sun's online Java API documentation was created using the documentation generator, which attests to the practicality of this tool. file:///G|/ebooks/1575211831/ch22.htm (16 of 22) [11/06/2000 7:44:53 PM] Day 22 -- Java Programming Tools Usage The syntax for using the documentation generator follows: javadoc Options FileName The FileName argument specifies either a package or a Java source code file. For source code files, the documentation generator creates HTML pages based on the special documentation comments (/** and */) used throughout the code. The documentation generator reformats and includes all public and protected declarations for classes, interfaces, methods, and variables. You can include special documentation tags within the documentation comments that allow you a little more power and flexibility over the resulting documentation. You'll learn about these tags in a moment. The FileName parameter to the documentation generator can also refer to a package name, in which case documentation is created for all the classes contained in the package. This is an easy way to crank out documentation for a large set of classes with one easy command. The OptionsArgument The Options argument enables you to change the default behavior of javadoc. Following are the options supported by the documentation generator: -d Dir -classpath Path The -d option specifies where the generated HTML documents are stored. The -classpath option informs the documentation generator to override CLASSPATH with the path specified by Path when looking for the Java source files. Documentation Tags The documentation generator supports special tags for adding extra information to the generated HTML documents. All the tags begin with an @ symbol and must appear at the beginning of a line. Following are the tags related to the generation of class documentation: @see Classname @see FullyQualifiedClassname @see Classname.Methodname @version Version @author AuthorName The @see tags all add a "see also" hyperlink to the HTML document that refers to a class or method within a class. This is an easy way to provide associations between classes in the documentation. Sun's Java API makes great use of the @see tag to provide cross-references between classes. The @version tag allows you to include version information with the class, as specified by Version. Version can contain any text you choose relating to the version of the code. The @author tag lets you provide the name of the author or authors of the source code, as specified by AuthorName. file:///G|/ebooks/1575211831/ch22.htm (17 of 22) [11/06/2000 7:44:53 PM] Day 22 -- Java Programming Tools Following is an example of source code making use of the class documentation tags: /** * A class for modeling precious gems. * * @see Object * @see gemology.Rock * @version 2.0 Dec 5, 1996 * @author Brett Weir */ class Gem extends Rock { // class definition } Notice that the class documentation comment and tags appear just before the class definition. This is important because the documentation generator associates this comment with the Gem class. You can also associate comments with variables and methods in a similar way. For variables, you are limited to using the @see tag. For methods, however, you can use a few other tags: @param ParamName Description @return Description @exception Classname Description The @param tag is used to add the method's parameters to the Parameters section generated in the HTML document. The Parameters section is an HTML section that lists the parameters required of a method. ParamName refers to the name of the parameter as defined by the method, and Description is a text description of the parameter. The @return tag adds a Returns section to the HTML document that brings attention to the return value of the method. You simply provide a description of the return value in Description. Finally, the @exception tag adds a Throws section to the HTML document, which lists the exceptions potentially thrown by the method. You specify the exception in Classname along with a description of what circumstances result in the exception being thrown in Description. You can use multiple exception tags. The documentation generator automatically creates a hyperlink to the documentation for the exception class referenced. Following is an example of source code that uses the method tags: /** * Determines an estimate of the gem's value. * * @param weight The weight of the gem in carats. * @param color The color of the gem (0 -> 1.0). * @param clarity The clarity of the gem (0 -> 1.0). * @return The estimated value of the gem. * @exception NumberFormatException When the color or clarity isn't * in the range 0 -> 1.0. file:///G|/ebooks/1575211831/ch22.htm (18 of 22) [11/06/2000 7:44:53 PM] Day 22 -- Java Programming Tools */ public int estimateValue(float weight, float color, float clarity) { // method definition } Visual Development Tools Even though the JDK tools are powerful and certainly adequate for serious Java programming, few people will argue the benefits of using visual development tools. Along with providing feature-packed source code editors, most visual tools combine many of the standard Java programming tools within one environment. For example, from one development environment you can typically edit, compile, run, and debug Java programs. This seemingly simple merger of tools can really help save precious development time. Although providing visual versions of the standard Java command-line tools is a benefit in and of itself, visual development tools rarely stop there. Most visual tools also include sophisticated project-management facilities as well as code-generation tools for creating applet templates with complete source code that performs a certain type of core functionality. Some visual tools even go a step further and eliminate much of the programming. These tools focus on harnessing prebuilt components that allow you to develop Java programs without actually writing Java code. These types of tools are typically a little more limited because of their high-level design, but they can save enormous amounts of time and energy in certain cases. The rest of today's lesson focuses on some of the more popular Java visual development tools that are currently available. My intention isn't to rate the tools or persuade you to try one over another. My goal is simply to let you know what's out there so you can investigate what type of tool might suit your needs. Many of the tools have evaluation versions that you can download for free from an associated Web site, so you can very easily try them out for yourself and come to your own conclusions. Have fun! Sun's Java WorkShop Sun's Java WorkShop is a visual development tool written entirely in Java. This is an interesting tool because its design is very Web-centric, meaning that much of the tool itself is comprised of Java applets embedded in HTML pages. Java WorkShop is currently available for Windows and Solaris systems. You can check it out at http://www.sun.com/sunsoft/Developer-products/java/, which is Sun's Java WorkShop Web site (see Figure 22.6). Figure 22.6 : Sun's Java WorkShop Web site. Symantec Café Symantec Café is a visual Java development environment based on Symantec's popular C++ development environment. It was one of the first visual Java tools available and currently supports both Windows and Macintosh platforms. You can get the latest information about Café at Symantec's Café Web site (see Figure 22.7), which is located at http://cafe.symantec.com/. Figure 22.7 : The Symantec CafeWeb site. file:///G|/ebooks/1575211831/ch22.htm (19 of 22) [11/06/2000 7:44:53 PM] Day 22 -- Java Programming Tools Microsoft Visual J++ Microsoft finally decided to enter the Java development foray in full force with Visual J++, which is a visual Java tool similar to their popular Visual C++ development environment. Visual J++ currently is available only for the Windows platform. You can check out Visual J++ at Microsoft's Visual J++ Web site (see Figure 22.8), which is located at http://198.105.232.5/visualj/. Figure 22.8 : The Microsoft Visual J++ Web site. Natural Intelligence's Roaster Natural Intelligence's Roaster is the first Java development environment targeted specifically for the Macintosh platform. For information about Roaster, check out Natural Intelligence's Roaster Web site (see Figure 22.9) at http://www.natural.com/pages/products/roaster/. Figure 22.9 : Natural Intelligence's Roaster Web site. Rogue Wave Software's JFactory Rogue Wave Software's JFactory Java development tool is aimed more at rapid application development with a minimal amount of programming. This visual application generator is currently available for the Windows platform. You can get more information about JFactory from Rogue Wave Software's JFactory Web site (see Figure 22.10), which is located at http://www.roguewave.com/products/jfactory/jfactory.html. Figure 22.10: Rogue Wave Software's JFactory Web site. Penumbra Software's Mojo Penumbra Software's Mojo development tool offers a programming environment based largely on reusable components. Granted, this is a trend common among many of the visual tools, but Mojo makes a big attempt to minimize custom coding whenever possible. Mojo is currently available for the Windows platform. You can check out Mojo at Penumbra Soft-ware's Web site (see Figure 22.11), which is located at http://www.penumbrasoftware.com/. Figure 22.11: Penumbra Software's Mojo Web site. Aimtech's Jamba Aimtech's Jamba is one of the first offerings in the area of high-level visual Java tools. Jamba is aimed at Internet developers who want to harness the power of Java without any programming or scripting. Jamba is currently available for the Windows platform. You can get the scoop on Jamba by taking a stroll through Aimtech's Jamba Web site (see Figure 22.12), which is located at http://www.aimtech.com/prodjahome.html. Figure 22.12: Animtech's Jamba Web site. file:///G|/ebooks/1575211831/ch22.htm (20 of 22) [11/06/2000 7:44:53 PM] Day 22 -- Java Programming Tools Kinetix's Hyperwire The last of the visual tools is Kinetix's Hyperwire, which is another high-level tool somewhat similar to Jamba. Unlike Jamba, however, Hyperwire's emphasis is largely placed on creating highly graphical Java applets, including 3D graphics. Hyperwire is currently available for the Windows platform. You can get more information about Hyperwire from Kinetix's Hyperwire Web site (see Figure 22.13), which is located at http://www.ktx.com/products/hyperwire/. Figure 22.13: Kinetix's Hyperwire Web site. Note High-level tools such as Jamba and Hyperwire are sometimes referred to as authoring tools because they involve little or no programming. Summary Even though Java is easier to use than some other programming languages, becoming a proficient Java programmer still presents a number of hurdles to most of us. One way to lower these hurdles a little is to become well acquainted with the development tools you are using. Possibly even more important is your initial choice of development tools, which can greatly affect your effectiveness as a Java programmer. Today's lesson addresses both of these concerns by presenting you with an in-depth look at the standard JDK tools, along with showing you some other options in the form of visual development tools. Ultimately, your selection of a development tool or tools will depend on your level of expertise and your development style. Regardless of what type of tool you gravitate toward, be sure to take the time to learn all you can about it. If you are still unsure about what kind of development tool to use, stick with the JDK for a while, since it is guaranteed to meet the basic requirements necessary to build Java programs. Besides, understanding Java programming from the perspective of the standard JDK tools will ultimately give you more insight into the "big picture" of Java development. You're probably tired of hearing me ramble on about tools by now. That's OK, because tomorrow you shift gears and head straight back into programming by learning about data structures in Java. I'm sure you can't wait! Q&A Q: A: Q: A: What is the significance of the profiler built into the runtime interpreter? The profiler is useful in assessing the relative execution times of different parts of a Java program, which is crucial in situations in which you are trying to improve the performance of a Java program. With the information generated by the profiler, you can target specific sections of code to focus optimization efforts. How do I compile multiple classes within a single source code file? I keep getting compiler errors! Even though you can compile multiple classes that are defined in one source code file, only one of the classes can be public. Furthermore, the source file must be named after the class that is public. The purpose here is to allow you to include support classes in the same file with public classes. However, you are only allowed one public class per source code file. file:///G|/ebooks/1575211831/ch22.htm (21 of 22) [11/06/2000 7:44:53 PM] Day 22 -- Java Programming Tools Q: A: Q: A: When do I use the class file disassembler? The class file disassembler, although powerful in its own right, isn't necessarily a tool you will find yourself using a lot. Its primary purpose is dissecting Java classes for which you don't have the source code. Using the disassembler, you can look at all the public methods and member variables for a class, which can help a lot when you're trying to figure out how the class works. If you have a knack for details and a lot of Tylenol, you can also use the disassembler to look at the raw bytecodes for classes. I still don't quite understand the distinction between development environments and authoring tools. What's the deal? Both of these types of tools qualify as visual tools, but there is a distinct difference between them. Development environments essentially replace the standard command-line JDK tools with integrated visual versions, while sometimes also adding some extra features like project management and simple code generation. Authoring tools, on the other hand, completely move away from the idea of writing source code by providing you with a means to build programs purely by assembling preexisting components. The main difference, then, is that development environments target Java programmers, while authoring tools are readily accessible to nonprogrammers as well. file:///G|/ebooks/1575211831/ch22.htm (22 of 22) [11/06/2000 7:44:53 PM] file:///G|/ebooks/1575211831/f22-1.gif file:///G|/ebooks/1575211831/f22-1.gif [11/06/2000 7:44:53 PM] file:///G|/ebooks/1575211831/f22-2.gif file:///G|/ebooks/1575211831/f22-2.gif [11/06/2000 7:44:54 PM] file:///G|/ebooks/1575211831/f22-3.gif file:///G|/ebooks/1575211831/f22-3.gif [11/06/2000 7:44:54 PM] file:///G|/ebooks/1575211831/f22-4.gif file:///G|/ebooks/1575211831/f22-4.gif [11/06/2000 7:44:55 PM] file:///G|/ebooks/1575211831/f22-5.gif file:///G|/ebooks/1575211831/f22-5.gif [11/06/2000 7:44:55 PM] file:///G|/ebooks/1575211831/f22-6.gif file:///G|/ebooks/1575211831/f22-6.gif [11/06/2000 7:44:55 PM] file:///G|/ebooks/1575211831/f22-7.gif file:///G|/ebooks/1575211831/f22-7.gif [11/06/2000 7:44:56 PM] file:///G|/ebooks/1575211831/f22-8.gif file:///G|/ebooks/1575211831/f22-8.gif [11/06/2000 7:44:57 PM] file:///G|/ebooks/1575211831/f22-9.gif file:///G|/ebooks/1575211831/f22-9.gif [11/06/2000 7:44:57 PM] file:///G|/ebooks/1575211831/f22-10.gif file:///G|/ebooks/1575211831/f22-10.gif [11/06/2000 7:44:58 PM] file:///G|/ebooks/1575211831/f22-11.gif file:///G|/ebooks/1575211831/f22-11.gif [11/06/2000 7:44:59 PM] file:///G|/ebooks/1575211831/f22-12.gif file:///G|/ebooks/1575211831/f22-12.gif [11/06/2000 7:45:00 PM] file:///G|/ebooks/1575211831/f22-13.gif file:///G|/ebooks/1575211831/f22-13.gif [11/06/2000 7:45:00 PM] Day 21 -- Under the Hood Day 21 Under the Hood by Charles L. Perkins and Laura Lemay CONTENTS q The Big Picture r q Why It's a Powerful Vision The Java Virtual Machine r An Overview r The Fundamental Parts r The Constant Pool r Limitations q The Bytecode Interpreter q Just-in-Time Compilers q The Class File Format q Method Signatures q The Garbage Collector r r The Solution r q The Problem Java's Parallel Garbage Collector The Security Story r Why You Should Worry r Why You Might Not Have To r Java's Applet Security Model r Signed Applets r Coming Up in Java 1.1 q Summary q Q&A Today the inner workings of the Java system will be revealed. You'll find out all about Java's vision, Java's virtual machine, those bytecodes you've heard so much about, that mysterious garbage collector, and why you might worry about security but don't have to. Note The title of this chapter well describes its content; the discussion in today's lesson is quite technical and assumes that you know something about low-level languages (assembly) and compiler/interpreter design concepts. file:///G|/ebooks/1575211831/ch21.htm (1 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood Let's begin, however, with the big picture. The Big Picture The Java team is very ambitious. Their ultimate goal is nothing less than to revolutionize the way software is written and distributed. They've started with the Internet, where they believe much of the interesting software of the future will live. To achieve such an ambitious goal, a large portion of the Internet programming community itself must be marshaled behind a similar goal and given the tools to help achieve it. The Java language, with its four S's (small, simple, safe, secure) and its flexible, Net-oriented environment, hopes to become the focal point for the rallying of this new legion of programmers. To this end, Sun Microsystems has done something rather gutsy. What was originally a secret, tens-of-millions-of-dollars research-and-development project, and 100 percent proprietary, has become a free, open, and relatively unencumbered technology standard upon which anyone can build. They are literally giving it away and reserving only the rights they need to maintain and grow the standard. Any truly open standard must be supported by at least one excellent, freely available "demonstration" implementation. Sun has already shipped the 1.0 version of Java as part of the JDK, and has published specifications for the language itself and for the virtual machine and bytecode compilers. In parallel, several universities, companies, and individuals have already expressed their intention to duplicate the Java environment based on the open API that Sun has created. In addition, the Java runtime environment is being incorporated into a wide variety of operating systems and environments on different platforms. Microsoft and Apple have licensed Java to include the runtime in Windows and the MacOS. A Java runtime will be available on IBM systems (OS/2 and AIX) as well as on nearly every commercial flavor of UNIX. What this means is that applications written in Java will be automatically executable on these systems, without any other software needing to be installed. These steps have been significant in making Java ubiquitous as not only the language for the Internet but also the language for future software development. Note Throughout this book, the Java runtime and the Java virtual machine are referred to interchangeably. While there are some slight differences between the two, equating them highlights the single environment that must be created to support Java. Several other languages are even contemplating compiling down to Java bytecodes, to help support them in becoming a more robust and widespread standard for moving executable content around on the Net. Why It's a Powerful Vision One of the reasons this brilliant move on Sun's part has a real chance of success is the pent-up frustration of literally a whole generation of programmers who desperately want to share their code with one another. Right now, the computer science world is balkanized into factions at universities and companies all over the world, with hundreds of languages, dozens of them widely used, dividing and separating us all. It's the worst sort of Tower of Babel. Java hopes to build some bridges and help tear down that tower. Because it is so simple, because it's so useful for programming over the Internet, and because the Internet is so "hot" right now-this confluence of forces should help propel Java onto center stage. It deserves to be there. It is the natural outgrowth of ideas that, since the early 1970s inside the Smalltalk group at Xerox PARC, have lain relatively dormant in the mainstream. Smalltalk, in fact, invented the first object-oriented bytecode interpreter and pioneered many of the deep ideas that Java builds on today. Those efforts were not embraced over the intervening decades as a solution to the general problems of software, however. Today, with those problems becoming so much more obvious, and with the Net crying out for a new kind of programming, the soil is fertile to grow something stronger from those old roots, something that just might spread like wildfire. (Is it a coincidence that Java's previous internal names were Green and OAK?) This new vision of software is one in which the Net becomes an ocean of objects, classes, and the open APIs between them. Traditional applications have vanished, replaced by skeletal frameworks like the Eiffel Tower into which can be fitted any file:///G|/ebooks/1575211831/ch21.htm (2 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood parts from this ocean, on demand, to suit any purpose. User interfaces will be mixed and matched, built in pieces and constructed to taste, whenever the need arises, by their own users. Menus of choices will be filled by dynamic lists of all the choices available for that function, at that exact moment, across the entire ocean (of the Net). In such a world, software distribution is no longer an issue. Software will be everywhere and will be paid for via a plethora of new micro-accounting models, which charge tiny fractions of cents for the parts as they are assembled and used. Frameworks will come into existence to support entertainment, business, and the social (cyber-)spaces of the near future. This is a dream that many of us have waited all our lives to be a part of. There are tremendous challenges to making it all come true, but the powerful winds of change we all feel must stir us into action because, at last, there is a base on which to build that dream-Java. The Java Virtual Machine To make visions like this possible, Java must be ubiquitous. It must be able to run on any computer and any operating system-now and in the future. In order to achieve this level of portability, Java must be very precise not only about the language itself, but about the environment in which the language lives. You've seen throughout this book that the Java environment includes a generally useful set of packages of classes and a freely available implementation of them. This takes care of a part of what is needed, but it is crucial also to specify exactly how the runtime environment of Java behaves. This final requirement is what has stymied many attempts at ubiquity in the past. If you base your system on any assumptions about what is beneath the runtime system, you lose. If you depend in any way on the computer or operating system below, you lose. Java solves this problem by inventing an abstract computer of its own and running on that. This virtual machine, as it's called, and which you've used throughout this book as the Java bytecode interpreter, runs a special set of instructions, called bytecodes, that are simply a stream of formatted bytes, each of which has a precise specification of exactly what each bytecode does to this virtual machine. The virtual machine is also responsible for certain fundamental capabilities of Java, such as object creation and garbage collection. Finally, in order to be able to move bytecodes safely across the Internet, you need a bulletproof model of security-and how to maintain it-and a precise format for how this stream of bytecodes can be sent from one virtual machine to another. Each of these requirements is addressed in today's lesson. Note Much of the following description is based closely on the latest "Virtual Machine Specifications" documents (and the 1.0 bytecodes), so if you delve more deeply into the details online, you should cover some familiar ground. An Overview It is worth quoting the introduction to the Java virtual machine documentation here, because it is so relevant to the vision outlined earlier: The Java virtual machine specification has a purpose that is both like and unlike equivalent documents for other languages and abstract machines. It is intended to present an abstract, logical machine design free from the distraction of inconsequential details of any implementation. It does not anticipate an implementation technology or an implementation host. At the same time it gives a reader sufficient information to allow implementation of the abstract design in a range of technologies. However, the intent of the [...] Java project is to create a language [...] that will allow the interchange over the Internet of "executable content," which will be embodied by compiled Java code. The project specifically does not want Java to be a proprietary language and does not want to be the sole purveyor of Java language implementations. Rather, we hope to make documents like this one, and source code for our implementation, freely available for people to use as they choose. This vision [...] can be achieved only if the executable content can be reliably shared between different Java file:///G|/ebooks/1575211831/ch21.htm (3 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood implementations. These intentions prohibit the definition of the Java virtual machine from being fully abstract. Rather, relevant logical elements of the design have to be made sufficiently concrete to allow the interchange of compiled Java code. This does not collapse the Java virtual machine specification to a description of a Java implementation; elements of the design that do not play a part in the interchange of executable content remain abstract. But it does force us to specify, in addition to the abstract machine design, a concrete interchange format for compiled Java code. The Java virtual machine specification consists of the following: q The bytecode syntax, including opcode and operand sizes, values, and types, and their alignment and endian-ness q The values of any identifiers (for example, type identifiers) in bytecodes or in supporting structures q The layout of the supporting structures that appear in compiled Java code (for example, the constant pool) q The Java .class file format Each of these is covered today. Despite this degree of specificity, there are still several elements of the design that remain (purposely) abstract, including the following: q The layout and management of the runtime data areas q The particular garbage-collection algorithms, strategies, and constraints used q The compiler, development environment, and runtime extensions (apart from the need to generate and read valid Java bytecodes) q Any optimizations performed when valid bytecodes are received These places are where the creativity of a virtual machine implementor has full rein. The Fundamental Parts The Java virtual machine can be divided into five fundamental pieces: q A set of registers q A bytecode instruction set q A stack q A garbage-collected heap q An area for storing methods Some of these might be implemented by using an interpreter, a native binary code compiler, or even a hardware chip-but all these logical, abstract components of the virtual machine must be supplied in some form in every Java system. Note `The memory areas used by the Java virtual machine are not required to be at any particular place in memory, to be in any particular order, or even to use contiguous memory. However, all but the method area must be able to represent aligned 32-bit values (for example, the Java stack is 32 bits wide). The virtual machine, and its supporting code, is often referred to as the runtime environment, and when this book refers to something being done at runtime, the virtual machine is what's doing it. Java Bytecodes The Java virtual machine instruction set is optimized to be small and compact. It is designed to travel across the Net, and so has traded off speed-of-interpretation for space. (Given that both Net bandwidth and mass storage speeds increase less rapidly than CPU speed, this seems like an appropriate trade-off.) file:///G|/ebooks/1575211831/ch21.htm (4 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood As mentioned, Java source code is "compiled" into bytecodes and stored in a .class file. On Sun's Java system, this is performed using the Java compiler (javac). The Java compiler is not exactly a traditional "compiler," because it translates source code into bytecodes, a lower-level format that cannot be run directly but must be further interpreted by each computer. Of course, it is exactly this level of indirection that buys you the power, flexibility, and extreme portability of Java code. Note Quotation marks are used around the word "compiler" when talking about the Java compiler because later today you will also learn about the "just-in-time" compiler, which acts more like the back end of a traditional compiler. The use of the same word "compiler" for these two different pieces of Java technology is unfortunate, but somewhat reasonable, because each is really one-half (either the front or the back end) of a more traditional compiler. A bytecode instruction consists of a one-byte opcode that serves to identify the instruction involved and zero or more operands, each of which may be more than one byte long, that encode the parameters the opcode requires. Note When operands are more than one byte long, they are stored in big-endian order, high-order byte first. These operands must be assembled from the byte stream at runtime. For example, a 16-bit parameter appears in the stream as two bytes so that its value is first_byte * 256 + second_byte. The bytecode instruction stream is only byte-aligned, and alignment of any larger quantities is not guaranteed (except inside the special bytecodes lookupswitch and tableswitch, which have special alignment rules of their own). Bytecodes interpret data in the runtime memory areas as belonging to a fixed set of types: the primitive types you've seen several times before, consisting of several signed integer types (8-bit byte, 16-bit short, 32-bit int, 64-bit long), one unsigned integer type (16-bit char), and two signed floating-point types (32-bit float, 64-bit double), plus the type "reference to an object" (a 32-bit pointer-like type). Some special bytecodes (for example, the dup instructions) treat runtime memory areas as raw data, without regard to type. This is the exception, however-not the rule. These primitive types are distinguished and managed by the Java compiler, not by the Java runtime environment. These types are not identified in memory, and therefore cannot be distinguished at runtime. Different bytecodes are designed to handle each of the various primitive types uniquely, and the compiler carefully chooses from this palette based on its knowledge of the actual types stored in the various memory areas. For example, when adding two integers, the compiler generates an iadd bytecode; for adding two floats, fadd is generated. Specifics about the Java bytecodes themselves are contained in appendix D, "Bytecodes Reference." Registers The registers of the Java virtual machine are just like the registers inside a real computer. New Terms Registers are used to temporarily store data. In the Java vritual machine registers hold the machine's state, affect its operation, and are updated after each bytecode is executed. The following are the Java registers: q pc, the program counter, which indicates what bytecode is being executed q optop, a pointer to the top of the operand stack, which is used to evaluate all arithmetic expressions file:///G|/ebooks/1575211831/ch21.htm (5 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood q q frame, a pointer to the execution environment of the current method, which includes an activation record for this method call and any associated debugging information vars, a pointer to the first local variable of the currently executing method The virtual machine defines these registers to be 32 bits wide. Note Because the virtual machine is primarily stack-based, it does not use any registers for passing or receiving arguments. This is a conscious choice skewed toward bytecode simplicity and compactness. It also aids efficient implementation on computer systems with fewer registers. By the way, the pc register is also used when the runtime handles exceptions; catch clauses are (ultimately) associated with ranges of the pc within a method's bytecodes. The Stack The Java virtual machine is stack-based. A Java stack frame is similar to the stack frame of a conventional programming language-it holds the state for a single method call. Frames for nested method calls are stacked on top of this frame. New Term The stack is used to supply parameters to bytecodes and methods, and to receive results back from them. Each stack frame contains three (possibly empty) sets of data: the local variables for the method call, its execution environment, and its operand stack. The sizes of the first two are fixed at the start of a method call, but the operand stack varies in size as bytecodes are executed in the method. Local variables are stored in an array of 32-bit slots, indexed by the register vars. Most types take up one slot in the array, but the long and double types each take up two slots. Note Long and double values, stored or referenced via an index N, take up the (32-bit) slots [N] and [N]+1. These 64-bit values are therefore not guaranteed to be 64-bit-aligned. Implementors are free to decide the appropriate way to divide these values between the two slots. The execution environment in a stack frame helps to maintain the stack itself. It contains a pointer to the previous stack frame, a pointer to the local variables of the method call, and pointers to the stack's current "base" and "top." Additional debugging information can also be placed into the execution environment. The operand stack, a 32-bit first-in-first-out (FIFO) stack, is used to store the parameters and return values of most bytecode instructions. For example, the iadd bytecode expects two integers to be stored on the top of the stack. It pops them, adds them together, and pushes the resulting sum back onto the stack. Each primitive data type has unique instructions that know how to extract, operate, and push back operands of that type. For example, long and double operands take two positions on the stack, and the special bytecodes that handle these operands take this into account. It is illegal for the types on the stack and the instruction operating on them to be incompatible (the Java compiler outputs bytecodes that always obey this rule). Note The top of the operand stack and the top of the overall Java stack are almost always the same. Thus, "the stack" refers to both stacks, collectively. file:///G|/ebooks/1575211831/ch21.htm (6 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood The Heap The heap is that part of memory from which newly created instances (objects) are allocated. The heap is often assigned a large, fixed size when the Java runtime system is started, but on systems that support virtual memory, it can grow as needed, in a nearly unbounded fashion. Because objects are automatically garbage-collected in Java, programmers do not have to (and, in fact, cannot) manually free the memory allocated to an object when they are finished using it. Java objects are referenced indirectly in the runtime via handles, which are a kind of pointer into the heap. Because objects are never referenced directly, parallel garbage collectors can be written that operate independently of your program, moving around objects in the heap at will. You'll learn more about garbage collection in the section "The Garbage Collector," later in this lesson. The Method Area Like the compiled code areas of conventional programming language environments, or the TEXT segment in a UNIX process, the method area stores the Java bytecodes that implement almost every method in the Java system. (Remember that some methods might be declared native, and thus implemented, for example, in C.) The method area also stores the symbol tables needed for dynamic linking as well as any other additional information debuggers or development environments that might want to associate with each method's implementation. Because bytecodes are stored as byte streams, the method area is aligned on byte boundaries. (The other areas are all aligned on 32-bit word boundaries.) The Constant Pool In the heap, each class has an array of constants, called a constant pool, available to it. Usually created by the Java compiler, these constants encode all the names (of variables, methods, and so forth) used by any method in a class. The class contains a count of how many constants there are and an offset that specifies how far into the class description itself the array of constants begins. These constants are typed via specially coded bytes and have a precisely defined format when they appear in the .class file for a class. Later today, a little of this file format is covered, but everything is fully specified by the virtual machine specifications in your Java release. Limitations The virtual machine, as currently defined, places some restrictions on legal Java programs by virtue of the choices it has made (some were previously described, and more will be detailed later today). These limitations and their implications are q 32-bit pointers, which imply that the virtual machine can address only 4GB of memory (this may be relaxed in later releases) q Unsigned 16-bit indices into the exception, line number, and local variable tables, which limits the size of a method's bytecode implementation to 64KB (this limitation may be eliminated in the final release) q Unsigned 16-bit indices into the constant pool, which limits the number of constants in a class to 64KB (a limit on the complexity of a class) In addition, Sun's implementation of the virtual machine uses so-called _quick bytecodes, which further limit the system. Unsigned 8-bit offsets into objects may limit the number of methods in a class to 256 (this limit may not exist in the final release), and unsigned 8-bit argument counts limit the size of the argument list to 255 32-bit words. (Although this means that you can have up to 255 arguments of most types, you can have only 127 of them if they're all long or double.) file:///G|/ebooks/1575211831/ch21.htm (7 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood The Bytecode Interpreter A bytecode interpreter examines each opcode byte (bytecode) in a method's bytecode stream, in turn, and executes a unique action for that bytecode. This might consume further bytes for the operands of the bytecode and might affect which bytecode will be examined next. It operates like the hardware CPU in a computer, which examines memory for instructions to carry out in exactly the same manner. It is the software CPU of the Java virtual machine. Your first, naive attempt to write such a bytecode interpreter will almost certainly be disastrously slow. The inner loop, which dispatches one bytecode each time through the loop, is notoriously difficult to optimize. In fact, smart people have been thinking about this problem, in one form or another, for more than 20 years. Luckily, they've gotten results, all of which can be applied to Java. The final result is that the interpreter shipped in the current release of Java has an extremely fast inner loop. In fact, on even a relatively slow computer, this interpreter can perform more than 590,000 bytecodes per second! This is really quite good-the CPU in that computer does only about 30 times better, and it has the advantage of using the hardware to do it. This interpreter is fast enough for most Java programs (and for those requiring more speed, they can always use native methods-see yesterday's discussion), but what if a smart implementor wants to do better? Just-in-Time Compilers About a decade ago, a really clever trick was discovered by Peter Deutsch while trying to make Smalltalk run faster. He called it dynamic translation during interpretation. Sun calls it "just-in-time" (or JIT) compiling, which, effectively, means converting the relatively slow interpreted bytecode into native machine code just before running it-and therefore getting very close to native performance out of cross-platform Java bytecode. The trick is to notice that the really fast interpreter you've just written-in C, for example-already has a useful sequence of native binary code for each bytecode that it interprets: the binary code that the interpreter itself is executing. Because the interpreter has already been compiled from C into native binary code, for each bytecode that it interprets, it passes through a sequence of native code instructions for the hardware CPU on which it is running. By saving a copy of each binary instruction as it's executed, the interpreter can keep a running log of the binary code it itself has run to interpret a bytecode. It can just as easily keep a log of the set of bytecodes that it ran to interpret an entire method. You take that log of instructions and "peephole-optimize" it, just as a smart compiler does (peephole optimization involves taking a short sequence on instructions and replacing them with a shorter or faster set of instructions). This eliminates redundant or unnecessary instructions from the log, and makes it look just like the optimized binary code that a good compiler might have produced. Note This is where the name compiler comes from, in "just-in-time" compiler, but it's really only the back end of a traditional compiler-the part that does code generation. By the way, the front end here is the Java compiler (javac). Here's where the trick comes in. The next time that method is run (in exactly the same way), the interpreter can now simply execute directly the stored log of binary native code. Because this optimizes the inner-loop overhead of each bytecode, as well as any other redundancies between the bytecodes in a method, it can gain a factor of 10 or more in speed. In fact, an experimental version of this technology at Sun has shown that Java programs using it can run as fast as compiled C programs. Note file:///G|/ebooks/1575211831/ch21.htm (8 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood The parenthetical qualifier in the last paragraph is needed because if anything is different about the input to the method, it takes a different path through the interpreter and must be relogged. (There are sophisticated versions of this technology that solve this, and other, difficulties.) The cache of native code for a method must be invalidated whenever the method has changed, and the interpreter must pay a small cost up front each time a method is run for the first time. However, these small bookkeeping costs are far outweighed by the amazing gains in speed possible. Just-in-time compilers, often called just JIT compilers, are becoming increasingly popular, and many major vendors (including Microsoft and Symantec) are competing in this realm. Microsoft's Internet Explorer 3.0 ships with a JIT compiler already. You'll learn more about the various JIT compilers available (or soon to be) on Day 28, "Emerging Technologies." The Class File Format A Java class file is the file generated by the Java compiler with a .class extension. You won't be given the entire .class file format here, only a taste of what it's like. (You can read all about it in the release documentation.) It's mentioned here because it is one of the parts of Java that needs to be specified carefully if all Java implementations are to be compatible with one another, and if Java bytecodes are expected to travel across arbitrary networks-to and from arbitrary computers and operating systems-and yet arrive safely. The rest of this section paraphrases, and extensively condenses, the latest release of the class file documentation. Java class files are used to hold the compiled versions of both Java classes and Java interfaces. Compliant Java interpreters must be capable of dealing with all class files that conform to the following specification. A Java class file consists of a stream of 8-bit bytes. All 16-bit and 32-bit quantities are constructed by reading in two or four 8-bit bytes, respectively. The bytes are joined together in big-endian order. (Use java.io.DataInput and java.io.DataOutput to read and write class files.) The class file format is presented below as a series of C-struct-like structures. However, unlike a C struct, there is no padding or alignment between pieces of the structure. Each field of the structure may be of variable size, and an array may be of variable size (in this case, some field prior to the array gives the array's dimension). The types u1, u2, and u4 represent an unsigned 1-, 2-, or 4-byte quantity, respectively. Attributes are used at several different places in the class file format. All attributes have the following format: GenericAttribute_info { u2 attribute_name; u4 attribute_length; u1 info[attribute_length]; } The attribute_name is a 16-bit index into the class's constant pool; the value of constant_pool[attribute_name] is a string giving the name of the attribute. The field attribute_length gives the length of the subsequent information in bytes. This length does not include the 6 bytes needed to store attribute_name and attribute_length. In the examples in the rest of this section, whenever an attribute is required, names of all the attributes that are currently understood are listed. In the future, more attributes will be added. Class file readers are expected to skip over and ignore the information in any attributes they do not understand. The following pseudo-structure gives a top-level description of the format of a class file: ClassFile { u4 magic; file:///G|/ebooks/1575211831/ch21.htm (9 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood u2 minor_version u2 major_version u2 constant_pool_count; cp_info constant_pool[constant_pool_count - 1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attribute_count]; } Here's one of the smaller structures used: method_info { u2 access_flags; u2 name_index; u2 signature_index; u2 attributes_count; attribute_info attributes[attribute_count]; } Finally, here's a sample of one of the later structures in the class file description: Code_attribute { u2 attribute_name_index; u2 attribute_length; u1 max_stack; u1 max_locals; u2 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attribute_count]; } None of this is meant to be completely comprehensible (although you might be able to guess at what a lot of the structure members are for), but just suggestive of the sort of structures that live inside class files. Because the compiler and runtime sources are available, you can always begin with them if you actually have to read or write class files yourself. Therefore, you don't need to have a deep understanding of the details, even in that case. file:///G|/ebooks/1575211831/ch21.htm (10 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood Method Signatures Because method signatures are used in class files, now is an appropriate time to explore them in the detail promised on earlier days-but they're probably most useful to you when writing the native methods of yesterday's lesson. New Terms The method signature, in this instance, is a string representing the type of method, field, or array. A field signature represents the value of an argument to a method or the value of a variable and is a series of bytes in the following grammar: <field signature> <field type> <base_type> <object_type> <array_type> <optional_size> := := := := := := <field_type> <base_type> | <object_type> | <array_type> B|C|D|F|I|J|S|Z L <full.ClassName> ; [ <optional_size> <field_type> [0-9]* Here are the meanings of the base types: B (byte), C (char), D (double), F (float), I (int), J (long), S (short), and Z (boolean). A return-type signature represents the return value from a method and is a series of bytes in the following grammar: <return signature> := <field type> | V The character V (void) indicates that the method returns no value. Otherwise, the signature indicates the type of the return value. An argument signature represents an argument passed to a method: <argument signature> := <field type> Finally, a method signature represents the arguments that the method expects and the value that it returns: <method_signature> := (<arguments signature>) <return signature> <arguments signature> := <argument signature>* Let's try out the new rules: A method called complexMethod() in the class my.package.name.ComplexClass takes three arguments-a long, a boolean, and a two-dimensional array of shorts-and returns this. Then its method signature is (JZ[[S)Lmy.package.name.ComplexClass;. A method signature is often prefixed by the name of the method, or by its full package (using an underscore in the place of dots) and its class name followed by a slash (/) and the name of the method, to form a complete method signature. (You saw several of these generated in stub comments yesterday.) Now, at last, you have the full story! Thus, the following: my_package_name_ComplexClass/complexMethod(JZ[[S)Lmy.package.name.ComplexClass; is the full, complete method signature of complexMethod(). (Phew!) The Garbage Collector Decades ago, programmers in both the Lisp and Smalltalk communities realized how extremely valuable it is to be able to ignore memory deallocation. They realized that, although allocation is fundamental, deallocation is forced on the programmer by the laziness of the system-it should be able to figure out what is no longer useful, and get rid of it. In relative obscurity, these pioneering programmers developed a whole series of garbage collectors to perform this job, each getting file:///G|/ebooks/1575211831/ch21.htm (11 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood more sophisticated and efficient as the years went by. Finally, now that the mainstream programming community has begun to recognize the value of this automated technique, Java can become the first really widespread application of the technology those pioneers developed. The Problem Imagine that you're a programmer in a C-like language (probably not too difficult for you, because these languages are the dominant ones right now). Each time you create something, anything, dynamically in such a language, you are completely responsible for tracking the life of that object throughout your program and mentally deciding when it will be safe to deallocate it. This can be quite a difficult (sometimes impossible) task, because any of the other libraries or methods you've called might have "squirreled away" a pointer to the object, unbeknownst to you. When it becomes impossible to know, you simply choose never to deallocate the object, or at least to wait until every library and method call involved has completed, which could be nearly as long. The uneasy feeling you get when writing such code is a natural, healthy response to what is inherently an unsafe and unreliable style of programming. If you have tremendous discipline-and so does everyone who writes every library and method you call-you can, in principle, survive this responsibility without too many mishaps. But aren't you human? Aren't they? There must be some small slips in this perfect discipline due to error. What's worse, such errors are virtually undetectable, as anyone who's tried to hunt down a stray pointer problem in C will tell you. What about the thousands of programmers who don't have that sort of discipline? Another way to ask this question is: Why should any programmers be forced to have this discipline when it is entirely possible for the system to remove this heavy burden from their shoulders? Software engineering estimates have recently shown that for every 55 lines of production C-like code in the world, there is one bug. This means that your electric razor has about 80 bugs, and your TV, 400. Soon they will have even more, because the size of this kind of embedded computer software is growing exponentially. When you begin to think of how much C-like code is in your car's engine, it should give you pause. Many of these errors are due to the misuse of pointers, by misunderstanding or by accident, and to the early, incorrect freeing of allocated objects in memory. Java addresses both of these-the former by eliminating explicit pointers from the Java language altogether, and the latter by including, in every Java system, a garbage collector that solves the problem. The Solution Imagine a runtime system that tracks each object you create, notices when the last reference to it has vanished, and frees the object for you. How could such a thing actually work? One brute-force approach, tried early in the days of garbage collecting, is to attach a reference counter to every object. When the object is created, the counter is set to 1. Each time a new reference to the object is made, the counter is incremented, and each time such a reference disappears, the counter is decremented. Because all such references are controlled by the language-as variables and assignments, for example-the compiler can tell whenever an object reference might be created or destroyed, just as it does in handling the scoping of local variables, and thus it can assist with this task. The system itself maintains a set of root objects that are considered too important to be freed. The class Object is one example of such a V.I.P. object. (V.I.O.?) Finally, all that's needed is to test, after each decrement, whether the counter has hit 0. If it has, the object is freed. If you think carefully about this approach, you will soon convince yourself that it is definitely correct when it decides to free anything. It is so simple that you can immediately tell that it will work. The low-level hacker in you might also feel that if it's that simple, it's probably not fast enough to run at the lowest level of the system-and you'd be right. Think about all the stack frames, local variables, method arguments, return values, and local variables created in the course of even a few hundred milliseconds of a program's life. For each of these tiny, nano-steps in the program, an extra increment (at best) or decrement, test, and deallocation (at worst) will be added to the running time of the program. In fact, the first garbage collectors were slow enough that many predicted they could never be used at all! Luckily, a whole generation of smart programmers has invented a big bag of tricks to solve these overhead problems. One file:///G|/ebooks/1575211831/ch21.htm (12 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood trick is to introduce special "transient object" areas that don't need to be reference counted. The best of these generational scavenging garbage collectors today can take less than 3 percent of the total time of your program-a remarkable feat if you realize that many other language features, such as loop overheads, can be as large or larger! There are other problems with garbage collection. If you are constantly freeing and reclaiming space in a program, won't the heap of objects soon become fragmented, with small holes everywhere and no room to create new, large objects? Because the programmer is now free from the chains of manual deallocation, won't he create even more objects than usual? What's worse, there is another way that this simple reference counting scheme is inefficient: in space rather than time. If a long chain of object references eventually comes full circle, back to the starting object, each object's reference count remains at least 1 forever. None of these objects will ever be freed! Together, these problems imply that a good garbage collector must, every once in a while, step back to compact or clean up wasted memory. Memory compaction occurs when a garbage collector steps back and reorganizes memory, eliminating the holes created by fragmentation. Compacting memory is simply a matter of repositioning objects one by one into a new, compact grouping that places them all in a row, leaving all the free memory in the heap in one big piece. Cleaning up the circular garbage still lying around after reference counting is called marking and sweeping. A mark-and-sweep of memory involves first marking every root object in the system and then following all the object references inside those objects to new objects to mark, and so on, recursively. Then, when you have no more references to follow, you "sweep away" all the unmarked objects and compact memory as before. The good news is that this solves the space problems you were having. The bad news is that when the garbage collector steps back and does these operations, a nontrivial amount of time passes during which your program is unable to run-all its objects are being marked, swept, rearranged, and so forth, in what seems like an uninterruptible procedure. Your first hint to a solution is the word "seems." Garbage collecting can actually be done a little at a time, between or in parallel with normal program execution, thus dividing up the large amount of time needed to step back into the numerous so-small-you-don't-notice-them chunks of time that happen between the cracks. (Of course, years of smart thinking went into the abstruse algorithms that make all this possible!) One final problem that might worry you a little has to do with these object references. Aren't these references scattered throughout your program and not just buried in objects? Even if they're only in objects, don't they have to be changed whenever the object they point to is moved by these procedures? The answer to both of these questions is a resounding yes, and overcoming them is the final hurdle to making an efficient garbage collector. There are really only two choices. The first, brute force, assumes that all the memory containing object references needs to be searched on a regular basis, and whenever the object references found by this search match objects that have moved, the old reference is changed. This assumes that there are "hard" pointers in the heap's memory-ones that point directly to other objects. By introducing various kinds of "soft" pointers, including pointers that are like forwarding addresses, the algorithm improves greatly. Although these brute-force approaches sound slow, it turns out that modern computers can do them fast enough to be useful. Note You might wonder how the brute-force techniques identify object references. In early systems, references were specially tagged with a pointer bit so they could be unambiguously located. Now, so-called conservative garbage collectors simply assume that if it looks like an object reference, it is-at least for the purposes of the mark-and-sweep. Later, when actually trying to update it, they can find out whether it really is an object reference. The final approach to handling object references, and the one Java currently uses, is also one of the very first ones tried. It involves using 100 percent soft pointers. An object reference is actually a handle, sometimes called an OOP, to the real pointer, and a large object table exists to map these handles into the actual object reference. Although this does introduce file:///G|/ebooks/1575211831/ch21.htm (13 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood extra overhead on almost every object reference (some of which can be eliminated by clever tricks, as you might guess), it's not too high a price to pay for this incredibly valuable level of indirection. This indirection allows the garbage collector, for example, to mark, sweep, move, or examine one object at a time. Each object can be independently moved out from under a running Java program by changing only the object table entries. This not only allows the step-back phase to happen in the tiniest steps, but it makes a garbage collector that runs literally in parallel with your program much easier to write. This is what the Java garbage collector does. Warning You need to be very careful about garbage collection when you're doing critical, real-time programs (such as those mentioned yesterday that legitimately require native methods)-but how often will your Java code be flying a commercial airliner in real time, anyway? Java's Parallel Garbage Collector Java applies almost all these advanced techniques to give you a fast, efficient, parallel garbage collector. Running in a separate thread, it cleans up the Java environment of almost all trash (it is conservative), silently and in the background; is efficient in both space and time; and never steps back for more than a small amount of time. You should never need to know it's there. By the way, if you want to force a full mark-and-sweep garbage collection to happen soon, you can do so simply by calling the System.gc() method. You might want to do this if you just freed up a majority of the heap's memory in circular garbage, and want it all taken away quickly. You might also call this whenever you're idle, as a hint to the system about when it would be best to come and collect the garbage. Ideally, you'll never notice the garbage collector, and all those decades of programmers beating their brains out on your behalf will simply let you sleep better at night-and what's wrong with that? The Security Story Speaking of sleeping well at night, if you haven't stepped back yet and said, "My goodness! You mean Java programs will be running rampant on the Internet!?!" you better do so now, for it is a legitimate concern. In fact, it is one of the major technical stumbling blocks (the others being mostly social and economic) to achieving the dream of ubiquity and code sharing for Java mentioned earlier in today's lesson. Why You Should Worry Any powerful, flexible technology can be abused. As the Net becomes mainstream and widespread, it, too, will be abused. Already, there have been many blips on the security radar screens of those of us who worry about such things, warning that (at least until today) not enough attention has been paid by the computer industry (or the media) to solving some of the problems that this new world brings with it. One of the benefits of constructively solving security once and for all will be a flowering unseen before in the virtual communities of the Net; whole new economies based on people's attention and creativity will spring to life, rapidly transforming our world in new and positive ways. The downside to all this new technology is that we (or someone!) must worry long and hard about how to make the playgrounds of the future safe for our children, and for us. Fortunately, Java is a big part of the answer. Why You Might Not Have To What gives me any confidence that the Java language and environment will be safe, that it will solve the technically daunting and extremely thorny problems inherent in any good form of security, especially for networks? One simple reason is the history of the people, and the company, who created Java. Many of them are the very smart programmers referred to throughout the book, who helped pioneer many of the ideas that make Java great and who have file:///G|/ebooks/1575211831/ch21.htm (14 of 21) [11/06/2000 7:45:04 PM] Day 21 -- Under the Hood worked hard over the decades to make techniques such as garbage collection a mainstream reality. They are technically capable of tackling and solving the hard problems that need to be solved. Sun Microsystems, the company, has been pushing networks as the central theme of all its software for more than a decade. Sun has the engineers and the commitment needed to solve these hard problems, because these same problems are at the very center of both its future business and its vision of the future, in which networking is the center of everything-and global networks are nearly useless without good security. Just this year, Sun has advanced the state of the art in easy-to-use Internet security with its new SunScreen products, and it has assigned Whitfield Diffie to oversee them, who is the man who discovered the underlying ideas on which essentially all interesting forms of modern encryption are based. Enough on deep background. What does the Java environment provide right now that helps me feel secure? Java's Applet Security Model Java protects you against potential "nasty" Java code via a series of interlocking defenses that, together, form an imposing barrier to any and all such attacks. Warning Of course no one can protect you from your own ignorance or carelessness. If you're the kind of person who blindly downloads binary executables from your Internet browser and runs them you need read no farther! You are already in more danger than Java will ever pose. As a user of this powerful new medium, the Internet, you should educate yourself to the possible threats this new and exciting world entails. In particular, downloading "auto running macros" or reading e-mail with "executable attachments" is just as much a threat as downloading binaries from the Net and running them. Java does not introduce any new dangers here, but by being the first mainstream use of executable and mobile code on the Net, it is responsible for making people suddenly aware of the dangers that have always been there. Java is already, as you will soon see, much less dangerous than any of these common activities on the Net, and can be made safer still over time. Most of these other (dangerous) activities can never be made safe. So please, do not do them! A good rule of thumb on the Net is: Don't download anything that you plan to execute (or that will be automatically executed for you) except from someone (or some company) you know well and with whom you've had positive, personal experience. If you don't care about losing all the data on your hard drive, or about your privacy, you can do anything you like, but for most of us, this rule should be law. Fortunately, Java allows you to relax that law. You can run Java applets from anyone, anywhere, in relative safety. Java's powerful security mechanisms act at four different levels of the system architecture. First, the Java language itself was designed to be safe, and the Java compiler ensures that source code doesn't violate these safety rules. Second, all bytecodes executed by the runtime are screened to be sure that they also obey these rules. (This layer guards against having an altered compiler produce code that violates the safety rules.) Third, the class loader ensures that classes don't violate namespace or access restrictions when they are loaded into the system. Finally, API-specific security prevents applets from doing destructive things. This final layer depends on the security and integrity guarantees from the other three layers. Let's now examine each of these layers in turn. file:///G|/ebooks/1575211831/ch21.htm (15 of 21) [11/06/2000 7:45:05 PM] Day 21 -- Under the Hood The Language and the Compiler The Java language and its compiler are the first line of defense. Java was designed to be a safe language. Most other C-like languages have facilities to control access to object-like structures, but also have ways to gain unorthodox access to objects (or to parts of objects), usually by (mis-)using pointers. This introduces two fatal security flaws to any system built on these languages. One is that no object can protect itself from outside modification, duplication, or spoofing (other objects pretending to be that object). Another is that a language with powerful pointers is more likely to have serious bugs that compromise security. These pointer bugs, where a pointer starts modifying some other object's memory, were responsible for most of the public (and not-so-public) security problems on the Internet this past decade. Java eliminates these threats in one stroke by eliminating pointers from the language altogether. There are still pointers of a kind-object references-but these are carefully controlled to be safe: they are unforgeable, and all casts are checked for legality before being allowed. In addition, powerful new array facilities in Java not only help to offset the loss of pointers, but add additional safety by strictly enforcing array bounds, catching more bugs for the programmer (bugs that, in other languages, might lead to unexpected and, therefore, bad-guy-exploitable problems). The language definition, and the compilers that enforce it, create a powerful barrier to any Java programmer with evil intentions. Because an overwhelming majority of the Net-savvy software on the Internet may soon be Java, its safe language definition and compilers help to guarantee that most of this software has a solid, secure base. With fewer bugs, Net software will be more predictable-a property that thwarts attacks. Verifying the Bytecodes What if that programmer with evil intentions gets a little more determined, and rewrites the Java compiler to suit his nefarious purposes? The Java runtime, getting the lion's share of its bytecodes from the Net, can never tell whether those bytecodes were generated by a trustworthy compiler. Therefore, it must verify that they meet all the safety requirements. Before running any bytecodes, the runtime subjects them to a rigorous series of tests that vary in complexity from simple format checks all the way to running a theorem prover to make certain that they are playing by the rules. These tests verify that the bytecodes do not forge pointers, violate access restrictions, access objects as other than what they are (InputStream objects are always used as InputStream objects, and never as anything else), call methods with inappropriate argument values or types, nor overflow the stack. Consider the following Java code sample: public class VectorTest { public int array; public int sum() { int localArray = array; int sum = 0; for (int i = localArray.length; sum += localArray[i]; return sum; -i >= 0; ) } } The bytecodes generated when this code is compiled look something like the following: aload_0 getfield #10 astore_1 Load this Load this.array Store in localArray file:///G|/ebooks/1575211831/ch21.htm (16 of 21) [11/06/2000 7:45:05 PM] Day 21 -- Under the Hood A: B: iconst_0 istore_2 aload_1 arraylength istore_3 iinc 3 -1 iload_3 iflt B iload_2 aload_1 iload_3 iaload iadd istore_2 goto A iload_2 ireturn Load 0 Store in sum Load localArray Gets its length Store in i Subtract 1 from i Load i Exit loop if < 0 Load sum Load localArray Load i Load localArray[i] Add sum Store in sum Do it again Load sum Return it Note The excellent examples and descriptions in this section of the book are paraphrased from the tremendously informative "Low Level Security in Java" paper by Frank Yellin. You can read this document at http://java.sun.com:80/sfaq/verifier.html. Extra Type Information and Requirements Java bytecodes encode more type information than is strictly necessary for the interpreter. Even though, for example, the aload and iload opcodes do exactly the same thing, aload is always used to load an object reference and iload used to load an integer. Some bytecodes (such as getfield) include a symbol table reference-and that symbol table has even more type information. This extra type information allows the runtime system to guarantee that Java objects and data aren't illegally manipulated. Conceptually, before and after each bytecode is executed, every slot in the stack and every local variable has some type. This collection of type information-all the slots and local variables-is called the type state of the execution environment. An important requirement of the Java type state is that it must be determinable statically by induction-that is, before any program code is executed. As a result, as the runtime system reads bytecodes, each is required to have the following inductive property: given only the type state before the execution of the bytecode, the type state afterward must be fully determined. Given straight-line bytecodes (no branches) and starting with a known stack state, the state of each slot in the stack is therefore always known. For example, starting with an empty stack: iload_1 iconst 5 iadd Load integer variable. Stack type state is I. Load integer constant. Stack type state is II. Add two integers, producing an integer. Stack type state is I. Note Smalltalk and PostScript bytecodes do not have this restriction. Their more dynamic type behavior does create additional flexibility in those systems, but Java needs to provide a secure execution environment. It must therefore know all types at all times in order to guarantee a certain level of security. file:///G|/ebooks/1575211831/ch21.htm (17 of 21) [11/06/2000 7:45:05 PM] Day 21 -- Under the Hood Another requirement made by the Java runtime is that when a set of bytecodes can take more than one path to arrive at the same point, all such paths must arrive there with exactly the same type state. This is a strict requirement, and implies, for example, that compilers cannot generate bytecodes that load all the elements of an array onto the stack. (Because each time through such a loop the stack's type state changes, the start of the loop-"the same point" in multiple paths-would have more than one type state, which is not allowed.) The Verifier Bytecodes are checked for compliance with all these requirements, using the extra type information in the class file, by a part of the runtime called the verifier. It examines each bytecode in turn, constructing the full type state as it goes, and verifies that all the types of parameters, arguments, and results are correct. Thus, the verifier acts as a gatekeeper to your runtime environment, letting in only those bytecodes that pass muster. Warning The verifier is the crucial piece of Java's security, and it depends on your having a correctly implemented (no bugs, intentional or otherwise) runtime system. As of this writing, only Sun is producing Java runtimes (and licensing them to companies such as Netscape and Microsoft for use in their browsers), and they are secure. In the future, however, you should be careful when downloading or buying another company's (or individual's) version of the Java runtime environment. Eventually, Sun will implement validation suites for runtimes, compilers, and so forth to be sure that they are safe and correct. In the meantime, caveat emptor! Your runtime is the base on which all the rest of Java's security is built, so make sure it is a good, solid, secure base. When bytecodes have passed the verifier, they are guaranteed not to do any of the following: cause any operand stack under- or overflows; use parameter, argument, or return types incorrectly; illegally convert data from one type to another (from an integer to a pointer, for example); or access any object's fields illegally (that is, the verifier checks that the rules for public, private, package, and protected are obeyed). As an added bonus, because the interpreter can now count on all these facts being true, it can run much faster than before. All the required checks for safety have been done up front, so it can run at full throttle. In addition, object references can now be treated as capabilities, because they are unforgeable-capabilities allow, for example, advanced security models for file I/O and authentication to be safely built on top of Java. Note Because you can now trust that a private variable really is private, and that no bytecode can perform some magic with casts to extract information from it (such as your credit card number), many of the security problems that might arise in other, less safe environments simply vanish! These guarantees also make erecting barriers against destructive applets possible, and easier. Because the Java system doesn't have to worry about dangerous bytecodes, it can get on with creating the other levels of security it wants to provide to you. The Class Loader The class loader is another kind of gatekeeper, albeit a higher-level one. The verifier is the security of last resort. The class loader is the security of first resort. When a new class is loaded into the system, it is placed into (lives in) one of several different realms. Commonly, there are three possible realms: your local computer, the firewall-guarded local network on which your computer is located, and the Internet (the global Net). Each of these realms is treated differently by the class loader. Note file:///G|/ebooks/1575211831/ch21.htm (18 of 21) [11/06/2000 7:45:05 PM] Day 21 -- Under the Hood Actually, there can be as many realms as your desired level of security (or paranoia) requires. This is because the class loader is under your control. As a programmer, you can make your own class loader that implements your own peculiar brand of security. (This is a radical step; you may have to give the users of your program a whole bunch of classes-and they give you a whole lot of trust-to accomplish this.) In particular, the class loader never allows a class from a less protected realm to replace a class from a more protected realm. The file system's I/O primitives, about which you should be very worried (and rightly so), are all defined in a local Java class, which means that they all live in the local-computer realm. Thus, no class from outside your computer (from either the supposedly trustworthy local network or from the Internet) can take the place of these classes and spoof Java code into using nasty versions of these primitives. In addition, classes in one realm cannot call upon the methods of classes in other realms, unless those classes have explicitly declared those methods public. This implies that classes from other than your local computer cannot even see the file system I/O methods, much less call them, unless you or the system wants them to. In addition, every new applet loaded from the network is placed into a separate package-like namespace. This means that applets are protected even from each other! No applet can access another's methods (or variables) without its cooperation. Applets from inside the firewall can even be treated differently from those outside the firewall, if you like. Note Actually, it's all a little more complex than this. In the current release, an applet is in a package namespace along with any other applets from that source. This source, or origin, is most often a host (domain name) on the Internet. This special "subrealm" is used extensively in the next section. Depending on where the source is located, outside the firewall or inside, further restrictions may apply (or be removed entirely). This model is likely to be extended in future releases of Java, providing an even finer degree of control over which classes get to do what. The class loader essentially partitions the world of Java classes into small, protected little groups, about which you can safely make assumptions that will always be true. This type of predictability is the key to well-behaved and secure programs. You've now seen the full lifetime of a method. It starts as source code on some computer, is compiled into bytecodes on some (possibly different) computer, and can then travel (as a .class file) into any file system or network anywhere in the world. When you run an applet in a Java-enabled browser (or download a class and run it by hand using java), the method's bytecodes are extracted from its .class file and carefully looked over by the verifier. After they are declared safe, the interpreter can execute them for you (or a code generator can generate native binary code for them using either the just-in-time compiler or java2c, and then run that native code directly). At each stage, more and more security is added. The final level of that security is the Java class library itself, which has several carefully designed classes and APIs that add the final touches to the security of the system. The Security Manager SecurityManager is an abstract class that collects, in one place, all the security policy decisions that the system has to make as bytecodes run. You learned before that you can create your own class loader. In fact, you may not have to, because you can subclass SecurityManager to perform most of the same customizations. An instance of some subclass of SecurityManager is always installed as the current security manager. It has complete control over which of a well-defined set of potentially dangerous methods are allowed to be called by any given class. It takes the realms from the last section into account, the source (origin) of the class, and the type of the class (standalone or loaded by an applet). Each of these can be separately configured to have the effect you (the programmer) like on your Java system. Note that environments such as Web browsers typically already have a security manager in place to handle basic file:///G|/ebooks/1575211831/ch21.htm (19 of 21) [11/06/2000 7:45:05 PM] Day 21 -- Under the Hood applet security (all the restrictions you've learned about so far in this book), and that security manager cannot be replaced or changed. What is this "well-defined set" of methods that are protected? File I/O is a part of the set, for obvious reasons. Applets, by default, can open, read, or write files on the local system. Also in this protected set are the methods that create and use network connections, both incoming and outgoing. The final members of the set are those methods that allow one thread to access, control, and manipulate other threads. (Of course, additional methods can be protected as well, by creating a new subclass of SecurityManager that handles them.) Signed Applets There is a middle ground between the "sandbox" restrictions that applets always have and an application that can freely run amok on your system. This middle ground involves establishing where an applet comes from, so different applet sources can be allowed different kinds of access. For example, you might specify different groups of trusted domains (companies), each of which is allowed added privileges when applets from that group are loaded. Applets from systems on an internal network, for example, are more trustworthy than applets from the Internet at large. Some groups can be more trusted than others, and you might even allow groups to grow automatically by allowing existing members to recommend new members for admission. (The Java seal of approval?) In any case, the possibilities are endless, as long as there is a secure way of recognizing the original creator of an applet. You might think this problem has already been solved, because classes are tagged with their origin. In fact, the Java runtime goes far out of its way to be sure that that origin information is never lost-any executing method can be dynamically restricted by this information anywhere in the call chain. So why isn't this enough? Because what you'd really like to be able to do is permanently "tag" an applet with its original creator (its true origin), and no matter where it has traveled, a browser could verify the integrity and authenticate the creator of that applet. If somehow those applets were irrevocably tagged with a digital signature by their creator, and that signature could also guarantee that the applet had not been tampered with, you'd be golden. Luckily, Sun is planning to do exactly that for Java, to have a mechanism for using public key cryptography to "sign" a fragment of code (an applet, an application, a single class) so that you can reliably and securely tell who created or verified that the piece of Java code was trustworthy. With the signature in place, that code could then break out of the sandbox and use the local system more freely. Expect this capability to become more popular and available in the future. Coming Up in Java 1.1 Promised in Java 1.1 is a set of extension APIs for managing security and encryption in Java. These new classes include support for the digital signature capabilities mentioned in the previous section, as well as general-purpose low-level and high-level cryptography, key management, access control lists, message digest hashes (MD5), and other tools and utilities. On Day 27, "The Standard Extension APIs," you'll learn more about the new security classes in Java. Summary Today you have learned about the grand vision that some of us have for Java, and about the exciting future it promises. Under the hood, the inner workings of the virtual machine, the bytecode interpreter (and all its bytecodes), the garbage collector, the class loader, the verifier, the security manager, and the powerful security features of Java were all revealed. You now know almost enough to write a Java runtime environment of your own-but luckily, you don't have to. You can simply download the latest release of Java-or use a Java-enabled browser to enjoy most of the benefits of Java right away. file:///G|/ebooks/1575211831/ch21.htm (20 of 21) [11/06/2000 7:45:05 PM] Day 21 -- Under the Hood Q&A Q: A: Q: A: Q: A: Q: A: Q: A: I'm still a little unclear about why the Java language and compiler make the Net safer. Can't they just be "side-stepped" by nasty bytecodes? Yes, they can-but don't forget that the whole point of using a safe language and compiler was to make the Net as a whole safer as more Java code is written. An overwhelming majority of this Java code will be written by honest Java programmers, who will produce safe bytecodes. This makes the Net more predictable over time, and thus more secure. I know you said that garbage collection is something I don't have to worry about, but what if I want (or need) to? So, you are planning to fly a plane with Java. Cool! For just such cases, there is a way to ask the Java runtime, during startup (java -noasyncgc), not to run garbage collection unless forced to, either by an explicit call (System.gc()) or by running out of memory. (This can be quite useful if you have multiple threads that are messing each other up and want to stop the gc thread from getting in the way while testing them.) Don't forget that turning garbage collection off means that any object you create will live a long, long time. If you're real-time, you never want to step back for a full gc-so be sure to reuse objects often, and don't create too many of them! Is there anything else I can do to the garbage collector? You can also force the finalize() methods of any recently freed objects to be called immediately via System.runFinalization(). You might want to do this if you're about to ask for some resources that you suspect might still be tied up by objects that are gone but not forgotten (waiting for finalize()). This is even rarer than starting a gc by hand, but it's mentioned here for completeness. I've heard of a tool called java2c, which would convert Java code to C code. Does this exist? Where can I get it? An experimental java2c translator was rumored to exist inside Sun, but was never released. It may be released at a later date. What's the last word on Java? Java adds much more than it can ever take away. It has always done so for me, and now, I hope it will for you as well. The future of the Net is filled with as-yet-undreamt horizons, and the road is long and hard, but Java is a great traveling companion. file:///G|/ebooks/1575211831/ch21.htm (21 of 21) [11/06/2000 7:45:05 PM] Day 27 -- The Standard Extension APIs Day 27 The Standard Extension APIs by Michael Morrison CONTENTS q Java API Overview q The Enterprise API r Java Database Connectivity r Interface Definition Language r Remote Method Invocation q The Commerce API q The Management API q The Server API q The Media API q The Security API q The Java Beans API q The Embedded API q Summary q Q&A Throughout this book you've learned a lot about the Java programming environment and how it can be used to create interactive Web-based programs. Your knowledge of Java thus far has been entirely based on what is known as the core Java API, or Java Base API. Until recently, this core API comprised the entirety of the Java programming landscape. However, JavaSoft recently announced a broad plan for integrating various new software technologies into the Java API. These new technologies come in the form of extension APIs that integrate with the core Java API, and they are referred to as the standard extension APIs. At some point, many of these APIs will merge and become part of the core API, but for now they are all being presented as extensions. Today's lesson takes a look at these new APIs and discusses what they have to offer, along with exactly how they will integrate with the existing core API. The main topics you cover today are q Java software platform overview q Enterprise and commerce API extensions q Management and server API extensions file:///G|/ebooks/1575211831/ch27.htm (1 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs q q q q Media API extensions Security API extensions Component API extensions Embedded systems API extensions Most of these API extensions are very new and haven't even reached the specification stage. For this reason, today's lesson is only meant to give you an idea of where Java is headed with the standard extension APIs. In other words, you may want to check JavaSoft's Web site (www.javasoft.com) to get the latest scoop on the status of these APIs since they are in a constant state of flux. Java API Overview Java release 1.02, which is the latest Java release as of this writing, is now being referred to by JavaSoft as the core Java API. The core Java API defines the minimal set of functionality a Java implementation must support to be considered Java compliant. For example, when someone undertakes the job of supporting Java on a particular platform, he must fully implement the core Java API. This guaranteed support for the core API is what allows Java developers the luxury of being able to write Java programs once and have them run on any Java-compliant platform. In the near future, JavaSoft plans to expand on the core API by introducing new APIs addressing more applied development needs. The new APIs cover a wide range of areas and will ultimately save developers a great deal of time by establishing a consistent approach to certain development issues, thereby reducing the need for custom coding. Some of these new APIs will merge with the core API; others will remain extensions. Regardless of their ultimate relationship to the core API, the new extension APIs are referred to as the standard extension APIs since they extend the current core API as we know it. The standard extension API is broken up into a set of individual APIs targeting different development needs. Following are the major components of the standard extension APIs: q Enterprise API q Commerce API q Management API q Server API q Media API q Security API q Java Beans API q Embedded API The rest of today's lesson focuses on each of these APIs and how they affect the Java software platform. file:///G|/ebooks/1575211831/ch27.htm (2 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs The Enterprise API Enterprise computing has become increasingly important in recent years as more and more companies realize the importance of integrating their operations electronically. The unique possibilities afforded by the increased usage of the Internet have served to magnify the popularity of enterprise computing. JavaSoft took note of Java's lack of support for enterprise systems and announced plans for an Enterprise API. The Java Enterprise API is designed to give Java programs a formal mechanism for connecting to enterprise information systems. This is a much-needed feature in Java since so many corporate computer systems rely heavily on enterprise information sources. In answering this need, the Enterprise API tackles the problem on three fronts. These fronts come in the form of three API subsets: q Java Database Connectivity (JDBC) q Interface Definition Language (IDL) q Remote Method Invocation (RMI) JavaSoft has recognized the importance of these three API subsets and plans to directly incorporate them into the core Java API at some point in the future. Java Database Connectivity The first of these subset APIs, JDBC, defines a structured interface to SQL (Structured Query Language) databases, which is the industry standard approach to accessing relational databases. By supporting SQL, JDBC allows developers to interact and support a wide range of databases. This means that the specifics of the underlying database platform are pretty much irrelevant when it comes to JDBC, which is very good news to Java developers. New Term SQL databases are databases built on the SQL standard, which is a widely accepted standard that defines a strict protocol for accessing and manipulating data. The JDBC API provides Java developers with a consistent approach to accessing SQL databases that is comparable to existing database development techniques, so interacting with a SQL database using JDBC isn't all that much different than interacting with a SQL database using traditional database tools. This should give Java programmers who already have some database experience confidence that they can hit the ground running with JDBC. The JDBC API has already been widely endorsed by industry leaders, including some development-tool vendors who have announced future support for JDBC in their development products. The JDBC API includes classes for common SQL database constructs such as database connections, SQL statements, and result sets. JDBC Java programs will be able to use the familiar SQL programming model of issuing SQL statements and processing the resulting data. The JDBC API is largely dependent on a driver manager that supports multiple drivers connecting to different databases. JDBC database drivers can be either written entirely in Java or implemented using native methods to bridge Java applications to existing database access libraries. file:///G|/ebooks/1575211831/ch27.htm (3 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs New Term A result set is a group data retrieved from a database after a user request. Interface Definition Language The IDL subset of the Enterprise API is aimed at providing a way to connect Java client programs to network servers running on other platforms. IDL is an industry standard protocol for client/server communications across different platforms. The primary use of the IDL API is to transparently connect Java client programs to legacy systems. New Term A legacy system is an outdated system that has yet to be reimplemented using current technologies. The Java IDL API includes the following components: q A client framework that allows Java IDL clients to be designed as either applets or standalone applications q A server framework that allows Java applications to act as network servers for IDL clients q A development tool that automatically generates stub code for specific remote interfaces Remote Method Invocation The RMI component of the Enterprise API defines an interface for invoking object methods in a distributed environment. The RMI API serves a crucial purpose in the Enterprise API by adding full support for remote object communications. The RMI API makes it straightforward for Java developers to add remote computing support to their classes. The Commerce API As the role of the Internet continues to evolve from being just an information source to also being a retail marketplace, the need for a secure commercial transaction protocol is growing to new heights. Both Internet vendors and shoppers alike are eagerly awaiting the inevitable migration of shopping to the Web. JavaSoft has provided an answer to the secure purchasing problem with the Commerce API, which is a Java API extension that provides the overhead for Java programs to support secure purchasing and financial management. The Java Commerce API aims to provide developers with an elegant solution to the problem of commercial transactions on the Web. The goal is to make purchasing goods a seamless, yet secure, part of the Web experience. To this end, the Commerce API is being pushed by JavaSoft as an open, extensible environment for financial management on the Web. The long-term plan for the Commerce API is for integration into the Java software platform partially with the core API and partially as a standard extension. It isn't clear yet which components will make it into the core API and which will remain separate. file:///G|/ebooks/1575211831/ch27.htm (4 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs The Commerce API consists of the following primary components: q An infrastructure q A database q Payment cassettes q Service cassettes q Administrative interfaces The infrastructure of the Commerce API is basically the architectural framework that defines the interactions between the other components of the API. This infrastructure is also what gives the API its extensibility to support future commerce extensions. The database component serves as a repository for user information, such as payment methods and the user's shipping address. The database component contains encryption features so that user information can be kept completely private. Alternately, commerce service providers have the option of sharing user information with one another. The Commerce API makes use of cassettes, which are software modules that implement specific financial protocols. The two different types of cassettes supported are payment cassettes and service cassettes. A payment cassette defines the protocol for making electronic payments. Examples of payment cassettes are credit cards, debit cards, and eventually digital cash. A user could have multiple payment cassettes that represent different payment instruments, much like we carry different payment instruments in our wallets or purses. In fact, one of the classes in the Commerce API specifically models an electronic wallet. New Term A cassette is a software module that implements a specific payment protocol. Service cassettes are more general, and they model any type of value-added financial service such as financial analysis or tax preparation modules. For example, you could feasibly purchase a service cassette to help balance your electronic checkbook or assess the value of your stock portfolio. The last component of the Commerce API includes administrative interfaces, which are dialog boxes and other graphical interfaces used to retrieve information from the user and to configure commerce options. The Management API The Management API is designed to answer the needs of integrated network management systems. It includes a wide range of interfaces, classes, and applets to facilitate the development of integrated management solutions. The primary goal of the Management API is to provide a unified approach to handling the complexities involved in developing and maintaining resources and services on a heterogeneous network. Using the Management API, Java developers will be able to rapidly develop network management applications supporting a wide range of systems on large and often complex networks. JavaSoft plans to keep the Management API as a separate extension from the core API. The Management API includes the following core components: file:///G|/ebooks/1575211831/ch27.htm (5 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs q q q q q q q q The Admin View Module (AVM) Base object interfaces Managed container interfaces Managed notification interfaces Managed data interfaces Managed protocol interfaces SNMP interfaces Applet integration interfaces The Admin View Module is an extension of the Java Abstract Windowing Toolkit (awt) that is enhanced to provide specific support for creating integrated management applications. The classes implemented in the AVM serve as a basis for developing sophisticated graphical user interfaces. For example, the AVM includes support for graphical tables, charts, graphs, and meters. The base object interfaces define the core object types that are used for distributed resources and services in a management system. Using the base object interfaces, developers can define abstractions for a variety of attributes associated with a managed enterprise environment. The managed container interfaces define a means for grouping together managed objects for better organization. This organization facilitates a more group-oriented approach to keeping up with managed resources, which can be a great benefit in complex systems. The managed notification interfaces define a core foundation of managed-event notification services. Developers are free to develop more advanced application-specific notification services by extending these services. The managed data interfaces provide a means of linking managed object attributes to relational databases via JDBC. In doing so, the managed data interfaces establish a transparent link between management resources and external databases. The managed protocol interfaces use the Java Security APIs and Java RMI to add secure distributed object support to the core functionality provided by the base object interfaces. In turn, the SNMP interfaces extend the managed protocol interfaces to provide support for SNMP agents. Since SNMP is the most popular management protocol in use, its support via the SNMP interfaces is an important part of the Management API. New Term SNMP stands for Simple Network Management Protocol, which is a relatively simple protocol originally developed to solve communication problems between different types of networks and gather network statistics. Finally, the applet integration interfaces component of the Management API specifies how Java applets can be integrated with the Management API to provide management solutions. Applet developers use the applet integration interfaces to build management support into their applets. file:///G|/ebooks/1575211831/ch27.htm (6 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs The Server API After the success of Java and its immediate use for developing client-side applets, JavaSoft decided to take steps to make Java a more viable alternative for server-side applications. The Server API is JavaSoft's answer to the need for more complete server-oriented support in Java. The Server API provides a wide range of server functionality including support for administration, accessibility control, and dynamic resource handling. Also included in the Server API is the Servlet API, which provides a framework for extending servers with servlets. JavaSoft plans to keep the Server API an extension separate from the core API. New Term A servlet is a Java object that extends the functionality of an information server, such as an HTTP server. You can think of servlets as the server equivalents of client-side Java applets. The Servlet API provides the overhead necessary for creating servlets and interfacing them with information servers. The Servlet API is equipped to handle the entire servlet/server relationship, with an emphasis on keeping things stable and simple. All that is required to run servlets is a server that supports the Servlet API. The Media API Possibly the weakest area of the core Java API as we know it is its support for media. Currently, the Java API supports only static GIF and JPEG images and wave sounds in the AU sound format. This limited media support won't cut it in the long run. Sure, developers can hack their own media implementations to some extent, but they could do that already in a variety of other languages and platforms. Java was supposed to make things easier, right? JavaSoft realized this weakness and is remedying things with the Media API, which is slated to include support for a dizzying array of media types that will no doubt put Java on the map as a serious multimedia platform. The Media API includes classes that model media types such as full-motion video, audio, 2D and 3D graphics, and telephony. Furthermore, the structure of the API is such that many of these media types will rely on the same underlying facilities. For example, all time-based media like video and audio will use the same underlying timing mechanism, meaning that synchronization won't be a problem. The Media API is designed to be very open and extensible, which is important considering the fact that the world of multimedia is ever changing. JavaSoft plans to integrate the Media API into the Java platform both as core API additions and as standard extension APIs. The following API subsets comprise the Media API: q The Media Framework API q The 2D Graphics API q The Animation API q The 3D Graphics API file:///G|/ebooks/1575211831/ch27.htm (7 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs q q q q q The Video API The Audio API The MIDI API The Share API The Telephony API The Media Framework API handles the low-level timing functionality required by many of the other media APIs. This API includes support for timing and synchronization, both of which are critical to media types that must function together in harmony. Also included in the Media Framework API is support for streaming, compression, and live data sources. New Term Synchronization refers to how different time-based media elements agree with each other in time. For example, it is important for the sound track of a movie to remain synchronized with the picture. New Term Streaming is the process of interacting with data while it is still being transferred. For example, a streaming audio player would begin playing audio as soon as a certain minimal amount of data has been transferred. The 2D Graphics API extends the functionality of the awt classes to provide wider support for 2D graphics primitives and a variety of different graphical output devices, such as printers. Another important addition to the 2D Graphics API is the definition of a uniform graphical model that brings many graphics functions into one structure. The Animation API uses the 2D Graphics API as a basis for its implementation of animated 2D graphics objects, or sprites. It also relies on the Media Framework API for maintaining timing and synchronization. The 3D Graphics API provides the overhead necessary to generate high-performance 3D graphics. This API implements 3D graphics by supporting a model of 3D graphical objects that can be rendered at high speeds. The 3D Graphics API also includes support for VRML, which is a popular 3D modeling language. To pull off all this functionality, the 3D Graphics API relies heavily on the functions provided by many of the other media APIs. The Video API brings full-motion video to Java. The API provides the framework for managing and processing video in either a streaming or stored scenario. Similar to the Video API in some ways, the Audio API also provides support for both streaming and stored media. However, the media supported by the Audio API consists of either sampled or synthesized audio. The Audio API even contains classes for implementing 3D spatial audio. The MIDI API brings timed musical events to Java by way of the popular MIDI standard. MIDI is an efficient way to represent both musical pieces as well as more general timing resources. Expect to hear the Web much differently once this API catches on! New Term file:///G|/ebooks/1575211831/ch27.htm (8 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs MIDI stands for Musical Instrument Digital Interface. It defines a protocol for communicating and storing time-based events, such as those generated by playing a musical instrument. The Share API is probably the most interesting of the media APIs, simply because it's the least obvious. It defines a means by which live, multiparty communication can take place over a network. The Share API provides support for both synchronization and session management. I wouldn't be surprised to see multiplayer games and "chat" applets take on a new feel once this API is out. The last of the media APIs is the Telephony API, which gives Java the ability to interact with telephones. Most important telephone functions, including teleconferencing and caller ID, are supported in this API. The Security API The eagerly awaited Security API will hopefully remedy one of the biggest limitations of Java applets: the inability to read or write files locally. With full support for cryptography, digital signatures, and authentication, Java developers should be able to leverage security issues to some extent and move away from the seemingly overprotective solution currently in place. The Security API will eventually be incorporated directly into the core Java API. New Term Cryptography encompasses the algorithms and techniques used to render data unrecognizable in the hands of unauthorized parties, thereby enforcing information privacy. New Term A digital signature is an electronic identification technique that serves much the same purpose as a handwritten signature. New Term Authentication is the process of verifying an action based on a security check. The cryptographic functions built into the Security API are isolated from the programmatic interface used by applets wanting to make security decisions. This layering allows the cryptographic functions to be replaced by third-party alternatives without impacting any-thing at the applet level, thereby giving Java developers more options when it comes to their security needs. The Java Beans API The Java Beans API defines an open standard for implementing dynamic Java software components, which are tightly packaged classes designed for reusability. Because the Java Beans API is given prominent coverage in tomorrow's lesson, I'll spare you the juicy details for now. However, I will tell you now that the Java Beans API is planned to merge with the core Java API at some point. file:///G|/ebooks/1575211831/ch27.htm (9 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs The Embedded API The last of the standard extension APIs is the Embedded API, which defines a minimal set of Java functionality specifically targeted for embedded systems applications, such as consumer electronics devices. The Embedded API is the only API that doesn't really add anything to the Java core API. In fact, the Embedded API will likely be a subset of the core API since only part of the core functionality is needed in embedded applications. For example, since most embedded systems have no graphical output to speak of, the entire awt is really unnecessary. Likewise, a network connection is unlikely in an embedded system, so there is no need to include the Java networking package. New Term An embedded system is a scaled-down computer system programmed to perform a particular function within an electronic device. It is likely that the Embedded API will consist of the following packages from the core API: language, utilities, and I/O. Beyond those, it's possible that Embedded API extensions could be developed to support specialized networking and output requirements. Since the Embedded API is itself a subset of the core API, it will probably be treated as an extension API. Summary Today you have learned about the standard extension APIs that are planned to expand Java in a variety of different directions. These APIs will no doubt boost the appeal of Java to new levels, since developers will have much more reusable code to leverage when building custom applications and applets. Although this will ultimately mean more learning on the part of developers, it will also result in less time spent writing code that is best suited to a standard extension. Knowing this, many developers will be forced to rethink their current plans based on the availability of the standard extension APIs, as there's no need to reinvent the wheel if it's already in the works. Tomorrow you'll continue to learn about Java extensions by looking at some other technologies that are going to affect Java in the near future. Q&A Q: A: Q: What exactly is the core Java API and how will it change? The core Java API as of Java version 1.02 consists of the eight packages that are shipped with version 1.02 of the Java Developer's Kit. This API will change in future releases to incorporate some of the technologies that are emerging as part of the standard extension APIs. However, not all of the standard extension APIs will make it into the core API; some will remain as extensions. Will the Commerce API help standardize financial transactions on the Web? file:///G|/ebooks/1575211831/ch27.htm (10 of 11) [11/06/2000 7:45:07 PM] Day 27 -- The Standard Extension APIs A: Q: A: Q: A: I sure hope so. Considering the large amount of Java development already taking place, combined with JavaSoft's desire to make the Commerce API extensible to a variety of technologies, it stands to reason that the Commerce API will be a major force in shaping the future of financial transactions on the Web. How are servlets developed? Servlets are developed in much the same way as applets, except you use the Servlet API instead of the Applet API. Most servlets probably won't have graphical interfaces, but the approach of developing servlets based on an API is still very similar to the current approach to developing applets. How will digital signatures affect Java security? It's still not clear what the total impact of digital signatures will be on Java security, but the most likely change will be the removal of the local file access restriction on applets. Using digital signatures, it will be possible to validate the origination of an applet so that users can feel safe allowing the applet more freedom on their system. file:///G|/ebooks/1575211831/ch27.htm (11 of 11) [11/06/2000 7:45:07 PM] Day 26 -- Client/Server Networking in Java Day 26 Client/Server Networking in Java by Michael Morrison CONTENTS q Internet Network Basics r Addresses r Protocols r Ports q The Client/Server Paradigm q Sockets r r q Datagram Sockets Stream Sockets Fortune: A Datagram Client and Server r r Implementing the Fortune Server r Implementing the Fortune Client Applet r q Designing Fortune Running Fortune Trivia: A Stream Client and Server r Designing Trivia r Implementing the Trivia Server r Implementing the Trivia Client Applet r Running Trivia q Summary q Q&A The networking capabilities of Java are perhaps the most powerful component of the Java API because the vast majority of Java programs run in a networked environment. Using the wide range of network features built into Java, you can easily develop Web-based applets that perform a variety of tasks over a network. The network support in Java is particularly well suited to a client/server arrangement where a server marshals information and serves it to clients that handle the details of displaying the information to a user. In today's lesson you'll learn what Java has to offer in regard to communicating over an Internet network connection using a client/server arrangement. You'll begin the lesson by taking a look at some basic concepts surrounding the structure of the Internet as a network. You'll then move on to what specific support is provided by the standard Java networking API and how it fits in with the client/server paradigm. Finally, you'll conclude the lesson by developing a couple of interesting sample programs demonstrating the different types of client/server approaches available in Java. The following topics are covered in today's lesson: q Internet network basics q The client/server paradigm file:///G|/ebooks/1575211831/ch26.htm (1 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java q q q Java sockets Developing a datagram socket applet and server Developing a stream socket applet and server By the end of this lesson, you'll be ready to build your own Java network client/server programs from scratch. You'll also have a better understanding of one of the reasons Java has become so popular-by virtue of its clean and straightforward support for an otherwise messy and complex area of programming: network programming! Internet Network Basics Before you learn about the types of network support Java provides, it's important that you understand some fundamentals about the structure of the Internet as a network. As you are no doubt already aware, the Internet is a global network of many different types of computers connected in various ways. With this wide diversity of both hardware and software all connected together, it's pretty amazing that the Internet is even functional. Trust me, the functionality of the Internet is no accident and has come at no small cost in terms of planning. The only way to guarantee compatibility and reliable communication across a wide range of different computer systems is to define very strict standards that must be conformed to rigorously. That's exactly the approach taken by the planners of the Internet in determining its communications protocols. Please understand that I'm not the type of person who typically preaches conformity, but conformity in one's personal life is very different from conformity in complex computer networks. The point is, the only way to allow a wide range of computer systems to coexist and communicate with each other effectively is to hammer out some standards. Fortunately, plenty of standards abound for the Internet, and they share wide support across many different computer systems. Hopefully, I've convinced you of the importance of communication standards on the Internet-let's take a look at a few of them. Addresses One of the first areas of standardization on the Internet was in establishing a means to uniquely identify each connected computer. It's not surprising that a technique logically equivalent to traditional mailing addresses is the one that was adopted; each computer physically connected to the Internet is assigned an address that uniquely identifies it. These addresses, also referred to as IP addresses, come in the form of a 32-bit number that looks like this: 243.37.126.82. You're probably more familiar with the symbolic form of IP addresses, which looks like this: sams.mcp.com. New Term An IP address is a 32-bit number that uniquely identifies each computer physically attached to the Internet. So addresses provide each computer connected to the Internet with a unique identifier. Each Internet computer has an address for the same reason you have a mailing address and a phone number at your home: to facilitate communication. It might sound simple, and that's because conceptually it is. As long as we can guarantee that each computer is uniquely identifiable, we can easily communicate with any computer without worry. Well, almost. The truth is, addresses are only a small part of the Internet communication equation, but an important part nevertheless. Without addresses, there would be no way to distinguish among different computers. Protocols The idea of communicating among different computers on the Internet might not sound like that big a deal now that you understand that they use addresses similar to mailing addresses. The problem is that there are many different types of communication that can take place on the Internet, meaning that there must be an equal number of mechanisms for handling them. It's at this point that the mailing-address comparison to Internet addressing breaks down. The reason for this is that each type of communication taking place on the Internet requires a unique protocol. Your mailing-address essentially revolves around one type of communication: the postal carrier driving up to your mailbox and placing the mail inside. file:///G|/ebooks/1575211831/ch26.htm (2 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java A protocol specifies the format of data being sent over the Internet, along with how and when it is sent. On the other end of the communication, the protocol also defines how the data is received along with its structure and what it means. You've probably heard mention of the Internet being just a bunch of bits flying back and forth in cyberspace. That's a very true statement, and without protocols, those bits wouldn't mean anything. New Term A protocol is a set of rules and standards defining a certain type of Internet communication. The concept of a protocol is not groundbreaking or even new. We use protocols all the time in everyday situations; we just don't call them protocols. Think about how many times you've been involved in this type of dialogue: "Hi, may I take your order?" "Yes, I'd like the grilled salmon and a frozen strawberry margarita." "Thanks, I'll put your order in and bring you your drink." "Thank you, I'm famished." Although this conversation might not look like anything special, it is a very definite social protocol used to place orders for food at a restaurant. Conversational protocol is important because it gives us familiarity and confidence in knowing what to do in certain situations. Haven't you ever been nervous when entering a new social situation in which you didn't quite know how to act? In these cases, you didn't really have confidence in the protocol, so you probably worried about a communication problem that could have easily resulted in embarrassment. For computers and networks, protocol breakdown translates into errors and information transfer failure rather than embarrassment. Now that you understand the importance of protocols, let's take a look at a couple of the more important ones used on the Internet. Without a doubt, the protocol getting the most attention these days is HTTP, which stands for Hypertext Transfer Protocol. HTTP is the protocol used to transfer HTML documents on the Web. Another important protocol is FTP, which stands for File Transfer Protocol. FTP is a more general protocol used to transfer binary files over the Internet. Each of these protocols has its own unique set of rules and standards defining how information is transferred, and Java provides support for both of them. New Term HTTP stands for Hypertext Transfer Protocol, which is the protocol used to transfer HTML documents on the Web. New Term FTP stands for File Transfer Protocol, which is the protocol used to transfer files across the Internet. Ports Internet protocols make sense only in the context of a service. For example, the HTTP protocol comes into play when you are providing Web content (HTML pages) through an HTTP service. Each computer on the Internet has the capability to provide a variety of services through the various protocols supported. There is a problem, however, in that the type of service must be known before information can be transferred. This is where ports come in. A port is a software abstraction that provides a means to differentiate between network services. More specifically, a port is a 16-bit number identifying the different services offered by a network server. New Term A port is a 16-bit number that identifies each service offered by a network server. Each computer on the Internet has a bunch of ports that can be assigned different services. To use a particular service and therefore establish a line of communication via a particular protocol, you must connect to the correct port. Ports are numbered, and some of the numbers are specifically associated with a type of service. Ports with specific service assignments are known as standard ports, meaning that you can always count on a particular port corresponding to a certain file:///G|/ebooks/1575211831/ch26.htm (3 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java service. For example, the FTP service is located on port 21, so any other computer wanting to perform an FTP file transfer would connect to port 21 of the host computer. Likewise, the HTTP service is located on port 80, so any time you access a Web site, you are really connecting to port 80 of the host using the HTTP protocol behind the scenes. Figure 26.1 illustrates how ports and protocols work. Figure 26.1 : The relationship between protocols and ports. All standard service assignments are given port values below 1024. This means that ports above 1024 are considered available for custom communications, such as those required by a Java client/server program implementing its own protocol. Keep in mind, however, that other types of custom communication also take place above port 1024, so you might have to try a few different ports to find an unused one. The Client/Server Paradigm So far I've managed to explain a decent amount of Internet networking fundamentals while dodging a major issue: the client/server paradigm. You've no doubt heard of clients and servers before, but you might not fully understand their importance in regard to the Internet. Well, it's time to remedy that situation, because you won't be able to get much done in Java without understanding how clients and servers work. As a matter of fact, the Java network- programming framework is based on a client/server arrangement. The client/server paradigm involves thinking of computing in terms of a client, who is essentially in need of some type of information, and a server, who has lots of information and is just waiting to hand it out. Typically, a client will connect to a server and query for certain information. The server will go off and find the information and then return it to the client. It might sound as though I'm oversimplifying things here, but for the most part I'm not; conceptually, client/server computing is as simple as a client asking for information and a server returning it. In the context of the Internet, clients are typically run on desktop or laptop computers attached to the Internet looking for information, whereas servers are typically run on larger computers with certain types of information available for the clients to retrieve. The Web itself is made up of a bunch of computers that act as Web servers; they have vast amounts of HTML pages and related data available for people to retrieve and browse. Web clients are used by those of us who connect to the Web servers and browse through the Web pages. In this way, Netscape Navigator is considered client Web software. Take a look at Figure 26.2 to get a better idea of the client/server arrangement. Figure 26.2 : A Web server with multiple clients connected. Sockets One of Java's major strong suits as a programming language is its wide range of network support. Java has this advantage because it was developed with the Internet in mind. The result is that you have lots of options in regard to network programming in Java. Even though there are many network options, most Java network programming uses a particular type of network communication known as sockets. New Term A socket is a software abstraction for an input or output medium of communication. Java performs all of its low-level network communication through sockets. Logically, sockets are one step lower than ports; you use sockets to communicate through a particular port. So a socket is a communication channel that enables you to transfer data through a certain port. Check out Figure 26.3, which shows communication taking place through multiple sockets on a port. Figure 26.3 : Multiple sockets communicating through a port. This figure brings up an interesting point about sockets: Data can be transferred through multiple sockets for a single port. This makes sense because it is common for multiple Web users to retrieve Web pages from a server via port 80 (HTTP) at file:///G|/ebooks/1575211831/ch26.htm (4 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java the same time. Java provides basic socket classes to make programming with sockets much easier. Java sockets are broken down into two types: datagram sockets and stream sockets. Datagram Sockets A datagram socket uses User Datagram Protocol (UDP) to facilitate the sending of datagrams (self-contained pieces of information) in an unreliable manner. Unreliable means that information sent via datagrams isn't guaranteed to make it to its destination. The trade-off here is that datagram sockets require relatively few resources directly because of this unreliable design. The clients and servers in a datagram scenario don't require a "live" or dedicated network connection, which is sometimes desirable. In this way, a datagram socket is somewhat equivalent to a dial-up network connection, with which you are temporarily connected to a network based on your immediate information needs. Datagrams are sent as individually bundled packets that may or may not make it to their destination in any particular order or at any particular time. On the receiving end of a datagram system, the packets of information can be received in any order and at any time. For this reason, datagrams sometimes include a sequence number that specifies which piece of the puzzle each bundle corresponds to. The receiver can then wait to receive the entire sequence, in which case it will put them back together to form the original information structure. New Term UDP (User Datagram Protocol) is a network broadcast protocol that doesn't guarantee transfer success. In return, UDP relies on few network resources. New Term A datagram is an independent, self-contained piece of information sent over a network whose arrival, arrival time, and content are not guaranteed. New Term A datagram socket, or "unconnected" socket, is a socket over which data is bundled into packets and sent without requiring a "live" connection to the destination computer. The fact that datagram sockets are openly unreliable may lead you to think that they are something to avoid in network programming. However, there are very practical scenarios in which datagram sockets make perfectly acceptable solutions. For example, servers that continually broadcast similar information make great candidates for datagram communication. A stock quote server is a good example since the stock quotes are constantly being spit out with little regard for successful delivery. The fact that stock quotes are highly time-dependent makes it less of an issue if a stock quote never reaches you; you can just wait until a new one is sent. Java supports datagram socket programming through two classes: DatagramSocket and DatagramPacket. The DatagramSocket class provides an implementation for a basic datagram socket. The DatagramPacket class provides the functionality required of a packet of information that is capable of being sent through a datagram socket. These two classes are all you need to get busy writing your own datagram client/server Java programs. Following is a list of some of the more important methods implemented in the DatagramSocket class: DatagramSocket() DatagramSocket(int port) void send(DatagramPacket p) synchronized void receive(DatagramPacket p) synchronized void close() The first two methods listed are actually constructors for the DatagramSocket class. The first constructor is the default constructor and takes no parameters, and the second constructor creates a datagram socket connected to the specified port. The send and receive methods are very straightforward and provide a means to send and receive datagram packets. The close method simply closes the datagram socket. It doesn't get much simpler than that! file:///G|/ebooks/1575211831/ch26.htm (5 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Notice that the DatagramSocket class doesn't distinguish between the socket being a client or server socket. The reason for this is the manner in which datagram communication takes place, which doesn't require that the socket act specifically as a client or server. Rather, Java clients and servers are distinguished by how they use the DatagramSocket class to transmit/receive datagrams. The other half of the datagram solution is the DatagramPacket class, which models a packet of information sent through a datagram socket. Following are some of the more useful methods in the DatagramPacket class: DatagramPacket(byte ibuf, int ilength) DatagramPacket(byte ibuf, int ilength, InetAddress iaddr, int iport) byte getData() int getLength() The first two methods are the constructors for DatagramPacket. As you probably guessed from the parameters, you construct datagram packets from byte arrays of data. The first constructor is used for receiving datagrams, as is evident by the absence of an address or port number. The second constructor is used for sending datagrams, which is why you have to specify a destination address and port number for the datagram to be sent. The other two methods return the raw datagram data and the length of the data, respectively. Other than the constructors, all the methods in DatagramPacket are passive, meaning that they simply return information about the datagram packet and don't actually change anything. This is evidence that the DatagramPacket class is primarily used as a container for data being sent over a datagram socket. In other words, you will typically create a DatagramPacket object as a wrapper for data being sent or received and never call any methods on it. Stream Sockets Unlike datagram sockets, in which the communication is roughly akin to that in a dial-up network, a stream socket is more akin to a live network, in which the communication link is continuously active. A stream socket is a "connected" socket through which data is transferred continuously. By continuously, I don't necessarily mean that data is being sent all the time, but that the socket itself is active and ready for communication all the time. New Term A stream socket, or connected socket, is a socket through which data can be transmitted continuously. The benefit of using a stream socket is that information can be sent with less worry about when it will arrive at its destination. Because the communication link is always live, data is generally transmitted immediately after you send it. Of course, this dedicated communication link brings with it the overhead of consuming more resources. However, most network programs benefit greatly from the consistency and reliability of a stream socket. Note A practical usage of a streaming mechanism is RealAudio, which is a technology that provides a way to listen to audio on the Web as it is being transmitted in real time. Java supports stream socket programming primarily through two classes: Socket and ServerSocket. The Socket class provides the necessary overhead to facilitate a stream socket client, and the ServerSocket class provides the core functionality for a server. Following is a list of some of the more important methods implemented in the Socket class: Socket(String host, int port) Socket(InetAddress address, int port) synchronized void close() InputStream getInputStream() OutputStream getOutputStream() The first two methods listed are constructors for the Socket class. The host computer you are connecting the socket to is file:///G|/ebooks/1575211831/ch26.htm (6 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java specified in the first parameter of each constructor; the difference between the two constructors is whether you specify the host using a string name or an InetAddress object. The second parameter is an integer specifying the port you want to connect to. The close method is used to close a socket. The getInputStream and getOutputStream methods are used to retrieve the input and output streams associated with the socket. The ServerSocket class handles the other end of socket communication in a client/server scenario. Following are a few of the more useful methods defined in the ServerSocket class: ServerSocket(int port) ServerSocket(int port, int count) Socket accept() void close() The first two methods are the constructors for ServerSocket, which both take a port number as the first parameter. The count parameter in the second constructor specifies a timeout period for the server to quit automatically "listening" for a client connection. This is the distinguishing factor between the two constructors; the first version doesn't listen for a client connection, whereas the second version does. If you use the first constructor, you must specifically tell the server to wait for a client connection. You do this by calling the accept method, which blocks program flow until a connection is made. The close method simply closes the server socket. Like with the datagram socket classes, you might be thinking that the stream socket classes seem awfully simple. In fact, they are simple, which is a good thing. Most of the actual code facilitating communication via stream sockets is handled through the input and output streams connected to a socket. In this way, the communication itself is handled independently of the network socket connection. This might not seem like a big deal at first, but it is crucial in the design of the socket classes; after you've created a socket, you connect an input or output stream to it and then forget about the socket. Fortune: A Datagram Client and Server You've now covered the basics of sockets and how they work in Java, but you haven't seen a socket in action. Well, it's time to remedy that situation with a full-blown client/server program that uses datagram sockets. You'll also work through a stream socket example later today, but first things first! The datagram client/server example is called Fortune and consists of a server that transmits interesting quotes called "fortunes" and a client that receives and displays the fortunes. The Fortune example could also be used to implement a joke-of-the-day server, where users can connect and get the latest joke you have to offer. Since I had more interesting quotes than funny jokes, I decided to stick with a quote server! The Fortune example works like this: There is a server program that runs on a Web server and waits patiently for clients to connect and ask for a fortune. On the other end, there is a client applet embedded in a Web page that a user accesses with a Java-enabled Web browser. When the user loads the Web page and fires up the applet, the applet connects to the server and asks it for a fortune. The server in turn picks a fortune at random and sends it back to the applet. The applet in return displays the fortune for the user to see. It's that simple! Designing Fortune Before jumping into the Java code required to implement the Fortune example, let's briefly take a look at what is required of the design on each side of the client/server fence. On the server side, you need a program that monitors a particular port on the host machine for client connections. When a client is detected, the server picks a random fortune, which is a simple text string, and sends it to the client over the specified port. The server is then free to break the connection and let the client go on its merry way. The server returns to its original wait state, where it looks for other clients to connect. So the server is required to perform the following tasks: 1. Wait for a client to connect. 2. Accept the client connection. 3. Send a random fortune to the client. 4. Go back to step 1. file:///G|/ebooks/1575211831/ch26.htm (7 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Now, on to the client. The client side of the Fortune example is an applet that lives in a Web page and has full support for graphical output. The client applet is responsible for connecting to the server and awaiting the server's response. The server's response is the transmission of the fortune string, which the client must receive and display. When the client successfully receives the fortune, it can break the connection with the server. As an added bonus, the client applet is also capable of grabbing another fortune if you click the mouse button. The client's primary tasks follow: 1. Connect to the server. 2. Wait for a fortune to be sent. 3. Display the fortune. 4. Go back to step 1 if the user clicks the mouse button. Implementing the Fortune Server You're no doubt itching to see some real code that carries out all these ideas you've been learning. Well, the time has come! Since the Fortune example ultimately begins and ends with the server, let's start by looking at the code for the server. The complete source code for the FortuneServer class is located on the accompanying CD-ROM in the file FortuneServer.java. Following are the member variables defined in the FortuneServer class: private private private private static final int String DatagramSocket Random PORTNUM = 1234; fortunes; serverSocket; rand = new Random(System.currentTimeMillis()); The PORTNUM member represents the number of the port used by Fortune. The value of PORTNUM-1234-is arbitrarily chosen; the important thing is that it is greater than 1024. The fortunes member variable is an array of strings that hold the text for the actual fortunes. The serverSocket member variable represents the datagram socket used for communication with the client. The rand member variable is a Random object that is used in determining the random fortune to be sent to the client. Warning Be sure to always make your port numbers greater than 1024 so that they don't conflict with standard server port assignments. The constructor for FortuneServer handles creating the server socket: public FortuneServer() { super("FortuneServer"); try { serverSocket = new DatagramSocket(PORTNUM); System.out.println("FortuneServer up and running..."); } catch (SocketException e) { System.err.println("Exception: couldn't create datagram socket"); System.exit(1); } } As you can see, the constructor creates a datagram socket using the port number specified by PORTNUM. If the socket cannot be created, an exception is thrown, and the server exits. The server exits because it is pretty much worthless without a socket to communicate through. The method that does most of the work in the FortuneServer class is the run method, which is shown in Listing 26.1. Listing 26.1. The run method. file:///G|/ebooks/1575211831/ch26.htm (8 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java 1: public void run() { 2: if (serverSocket == null) 3: return; 4: 5: // Initialize the array of fortunes 6: if (!initFortunes()) { 7: System.err.println("Error: couldn't initialize fortunes"); 8: return; 9: } 10: 11: // Look for clients and serve up the fortunes 12: while (true) { 13: try { 14: InetAddress address; 15: int port; 16: DatagramPacket packet; 17: byte data = new byte[256]; 18: int num = Math.abs(rand.nextInt()) % fortunes.length; 19: 20: // Wait for a client connection 21: packet = new DatagramPacket(data, data.length); 22: serverSocket.receive(packet); 23: 24: // Send a fortune 25: address = packet.getAddress(); 26: port = packet.getPort(); 27: fortunes[num].getBytes(0, fortunes[num].length(), data, 0); 28: packet = new DatagramPacket(data, data.length, address, port); 29: serverSocket.send(packet); 30: } 31: catch (Exception e) { 32: System.err.println("Exception: " + e); 33: e.printStackTrace(); 34: } 35: } 36: } Analysis The first thing the run method does is check to make sure the socket is valid. If the socket is okay, run calls initFortunes to initialize the array of fortune strings. You'll learn about the initFortunes method in just a moment. Once the fortunes are initialized, run enters an infinite while loop that waits for a client connection. When a client connection is detected, a datagram packet is created using a random fortune string. This packet is then sent to the client through the socket. Since you wouldn't want to have to recompile the server application every time you wanted to change the fortunes, the fortunes are read from a text file. Each fortune is stored as a single line of text in the file Fortunes.txt. Following is a listing of the Fortunes.txt file: You can no more win a war than you can win an earthquake. file:///G|/ebooks/1575211831/ch26.htm (9 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java The highest result of education is tolerance. The right to be let alone is indeed the beginning of all freedom. When we lose the right to be different, we lost the right to be free. The only vice that cannot be forgiven is hypocrisy. We learn from history that we do not learn from history. That which we call sin in others is experiment for us. Few men have virtue to withstand the highest bidder. The initFortunes method is responsible for reading the fortunes from this file and storing them into an array that is more readily accessible. Listing 26.2 contains the source code for the initFortunes method. Listing 26.2. The initFortunes method. 1: private boolean initFortunes() { 2: try { 3: File inFile = new File("Fortunes.txt"); 4: FileInputStream inStream = new FileInputStream(inFile); 5: byte data = new byte[(int)inFile.length()]; 6: 7: // Read the fortunes into a byte array 8: if (inStream.read(data) <= 0) { 9: System.err.println("Error: couldn't read fortunes"); 10: return false; 11: } 12: 13: // See how many fortunes there are 14: int numFortunes = 0; 15: for (int i = 0; i < data.length; i++) 16: if (data[i] == (byte)'\n') 17: numFortunes++; 18: fortunes = new String[numFortunes]; 19: 20: // Parse the fortunes into an array of strings 21: int start = 0, index = 0; 22: for (int i = 0; i < data.length; i++) 23: if (data[i] == (byte)'\n') { 24: fortunes[index++] = new String(data, 0, start, i - start - 1); 25: start = i + 1; 26: } 27: } 28: catch (FileNotFoundException e) { 29: System.err.println("Exception: couldn't find the fortune file"); 30: return false; 31: } 32: catch (IOException e) { 33: System.err.println("Exception: I/O error trying to read fortunes"); 34: return false; 35: } 36: 37: return true; 38: } Analysis file:///G|/ebooks/1575211831/ch26.htm (10 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java The initFortunes method first creates a File object based on the Fortunes.txt file, which is used to initialize a file input stream. The File object is also used to determine the length of the fortunes file. The length of the file is important because it is used to create a byte array large enough to hold all the fortunes that are read. initFortunes reads the fortunes from the text file with a simple call to the read method of the input stream. The number of fortunes is then determined by counting the number of newline characters ('\n') in the fortune text. This works because each fortune is separated by a newline character in the file. When the number of fortunes has been established, a string array is created that is large enough to hold the fortunes. Using newline characters as separators, the fortune text is then parsed and each fortune stored in the array of strings. The end result is an array of strings that is much more convenient to access than attempting to read a file every time a client wants a fortune. The last method in the FortuneServer class is main, which is the entry point of the server application: public static void main(String args) { FortuneServer server = new FortuneServer(); server.start(); } As you can see, the main method is very simple; it creates a FortuneServer object and tells it to start running. That's it for the server side of Fortune! Implementing the Fortune Client Applet You might have been surprised by the simplicity of the Fortune server code. If so, then you'll probably be even more surprised by the client side of Fortune. The Fortune client class is simply called Fortune and is located on the CD-ROM in the file Fortune.java. Following are the member variables defined in the Fortune class: private static final int private String PORTNUM = 1234; fortune; The PORTNUM member should be very familiar to you. Notice that it is set to the same value as the PORTNUM variable defined in FortuneServer. This is critical because the port number is what ties the two programs together. The fortune member variable simply holds the current fortune being displayed. Warning It is very important that the port numbers for your client and server match exactly, because the port number is how the client and server are linked to each other. The Fortune applet attempts to grab a fortune from the server as soon as it runs. This is accomplished in the init method, whose code follows: public void init() { fortune = getFortune(); if (fortune == null) fortune = "Error: No fortunes found!"; } The init method calls getFortune to get a fortune from the server. If the fortune is invalid, an error message is displayed instead. The getFortune method handles the work of actually connecting to and getting a fortune from the server. The code for getFortune is shown in Listing 26.3. Listing 26.3. The getFortune method. file:///G|/ebooks/1575211831/ch26.htm (11 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java 1: private String getFortune() { 2: try { 3: DatagramSocket socket; 4: DatagramPacket packet; 5: byte data = new byte[256]; 6: 7: // Send a fortune request to the server 8: socket = new DatagramSocket(); 9: packet = new DatagramPacket(data, data.length, 10: InetAddress.getByName(getCodeBase().getHost()), PORTNUM); 11: socket.send(packet); 12: 13: // Receive a fortune 14: packet = new DatagramPacket(data, data.length); 15: socket.receive(packet); 16: fortune = new String(packet.getData(), 0); 17: socket.close(); 18: } 19: catch (UnknownHostException e) { 20: System.err.println("Exception: host could not be found"); 21: return null; 22: } 23: catch (Exception e) { 24: System.err.println("Exception: " + e); 25: e.printStackTrace(); 26: return null; 27: } 28: return fortune; 29: } Analysis The getFortune method first creates a request packet and sends it to the server. The contents of this packet are unimportant; the point is to just make contact with the server. After sending the request packet, getFortune creates a new packet and uses it to receive a fortune from the server. Because Fortune is an applet, the fortunes are displayed graphically via the paint method. Listing 26.4 contains the paint method defined in the Fortune class. Listing 26.4. The paint method. 1: public void paint(Graphics g) { 2: // Draw the title and fortune text 3: Font f1 = new Font("TimesRoman", Font.BOLD, 28), 4: f2 = new Font("Helvetica", Font.PLAIN, 16); 5: FontMetrics fm1 = g.getFontMetrics(f1), 6: fm2 = g.getFontMetrics(f2); 7: String title = new String("Today's Fortune:"); 8: g.setFont(f1); 9: g.drawString(title, (size().width - fm1.stringWidth(title)) / 2, 10: ((size().height - fm1.getHeight()) / 2) + fm1.getAscent()); file:///G|/ebooks/1575211831/ch26.htm (12 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java 11: g.setFont(f2); 12: g.drawString(fortune, (size().width - fm2.stringWidth(fortune)) / 2, 13: size().height - fm2.getHeight() - fm2.getAscent()); 14: } Analysis The paint method may look a little complicated, but all it's doing is performing some fancy centering and alignment so that the positioning of the fortune looks good. The paint method also displays the text Today's Fortune: just above the fortune. The final aspect of the Fortune class that you haven't covered is how to get a new fortune when the user clicks the mouse button. This is handled in the mouseDown method, whose code follows: public boolean mouseDown(Event evt, int x, int y) { // Display a new fortune getFortune(); repaint(); return true; } Since getFortune already takes care of the details involved in getting a new fortune from the server, all the mouseDown method has to do is call getFortune and update the screen with a call to repaint. That sums up the client side of Fortune, which means you're probably ready to take it for a spin! Running Fortune As you already know, the Fortune example is composed of two parts: a client and a server. The Fortune server must be running in order for the client to work. So to get things started, you must first run the server by using the Java interpreter (java); you do this from a command line, like this: java FortuneServer The other half of Fortune is the client, which is an applet that runs from within a Java-compatible Web browser, like Netscape Navigator or Microsoft Internet Explorer. After you have the server up and running, fire up a browser and load an HTML document including the Fortune client applet. On the CD-ROM, this HTML document is called Example1.html, in keeping with the standard JDK demo applets. After running the Fortune client applet, you should see something similar to what's shown in Figure 26.4. You can click in the applet window to retrieve new fortunes. Figure 26.4 : The Fortune client applet. Note This discussion on running the Fortune example assumes that you either have access to a Web server or can simulate a network connection on your local machine. Since my local Windows 95 system is not part of a physical network, I tested the programs by simulating a network connection. I did this by changing the TCP/IP configuration on my system so that it used a specific IP address (I just made up an address). If you make this change to your network configuration, you won't be able to access a real network using TCP/IP until you set it back, so don't forget to restore things when you're finished. file:///G|/ebooks/1575211831/ch26.htm (13 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Trivia: A Stream Client and Server The Fortune programs are a good example of how to use Java's datagram networking facilities. You will probably find, however, that more networking problems require a stream approach. Since I wouldn't want to leave you feeling like half a Java network programmer, let's look at an example that requires a stream socket approach. The stream client/server example is called Trivia and consists of a server that asks trivia questions and a client that interacts with the server by allowing the user to answer the questions. The Trivia example differs from the Fortune example in that there is an ongoing two-way communication between the client and the server. The Trivia example works like this: The server program waits patiently for a client to connect. When a client connects, the server sends a question and waits for a response. On the other end, the client receives the question and prompts the user for an answer. The user types in an answer that is sent back to the server. The server then checks to see if the answer is correct and notifies the user. The server follows this up by asking the client if it wants another question. If so, the process repeats. Designing Trivia It's important to always perform a brief preliminary design before you start churning out code. With that in mind, let's take a look at what is required of the Trivia server and client. On the server side, you need a program that monitors a particular port on the host machine for client connections, just as you did in Fortune. When a client is detected, the server picks a random question and sends it to the client over the specified port. The server then enters a wait state until it hears back from the client. When it gets an answer back from the client, the server checks it and notifies the client whether it is correct or incorrect. The server then asks the client if it wants another question, upon which it enters another wait state until the client answers. Finally, the server either repeats the process by asking another question, or it terminates the connection with the client. In summary, the server performs the following tasks: 1. Wait for a client to connect. 2. Accept the client connection. 3. Send a random question to the client. 4. Wait for an answer from the client. 5. Check the answer and notify the client. 6. Ask the client if it wants another question. 7. Wait for an answer from the client. 8. Go back to step 3 if necessary. Unlike Fortune, the client side of the Trivia example is an application that runs from a command line. The client is responsible for connecting to the server and waiting for a question. When it receives a question from the server, the client displays it to the user and allows the user to type in an answer. This answer is sent back to the server, and the client again waits for the server's response. The client displays the server's response to the user and allows the user to confirm whether he wants another question. The client then sends the user's response to the server and exits if the user declined any more questions. The client's primary tasks follow: 1. Connect to the server. 2. Wait for a question to be sent. 3. Display the question and input the user's answer. 4. Send the answer to the server. 5. Wait for a reply from the server. 6. Display the server's reply and prompt the user to confirm another question. 7. Send the user's reply to the server. 8. Go back to step 2 if necessary. file:///G|/ebooks/1575211831/ch26.htm (14 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Implementing the Trivia Server Like Fortune, the heart of the Trivia example lies in the server. The Trivia server program is called TriviaServer and is located on the CD-ROM in the file TriviaServer.java. Following are the member variables defined in the TriviaServer class: private static final int PORTNUM = 1234; private static final int WAITFORCLIENT = 0; private static final int WAITFORANSWER = 1; private static final int WAITFORCONFIRM = 2; private String questions; private String answers; private ServerSocket serverSocket; private int numQuestions; private int num = 0; private int state = WAITFORCLIENT; private Random rand = new Random(System.currentTimeMillis()); The WAITFORCLIENT, WAITFORANSWER, and WAITFORCONFIRM members are all state constants that define different states the server can be in. You'll see these constants in action in a moment. The questions and answers member variables are string arrays used to store the questions and corresponding answers. The serverSocket member variable keeps up with the server socket connection. numQuestions is used to store the total number of questions, while num is the number of the current question being asked. The state member variable holds the current state of the server, as defined by the three state constants (WAITFORCLIENT, WAITFORANSWER, and WAITFORCONFIRM). Finally, the rand member variable is used to pick questions at random. The TriviaServer constructor is very similar to FortuneServer's constructor, except that it creates a ServerSocket rather than a DatagramSocket. Check it out: public TriviaServer() { super("TriviaServer"); try { serverSocket = new ServerSocket(PORTNUM); System.out.println("TriviaServer up and running..."); } catch (IOException e) { System.err.println("Exception: couldn't create socket"); System.exit(1); } } Also like Fortune, the run method in TriviaServer is where most of the action is. The source code for the run method is shown in Listing 26.5. Listing 26.5. The run method. 1: public void run() { 2: Socket clientSocket; 3: 4: // Initialize the arrays of questions and answers 5: if (!initQnA()) { 6: System.err.println("Error: couldn't initialize questions and answers"); 7: return; 8: } 9: file:///G|/ebooks/1575211831/ch26.htm (15 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java 10: // Look for clients and ask trivia questions 11: while (true) { 12: // Wait for a client 13: if (serverSocket == null) 14: return; 15: try { 16: clientSocket = serverSocket.accept(); 17: } 18: catch (IOException e) { 19: System.err.println("Exception: couldn't connect to client socket"); 20: System.exit(1); 21: } 22: 23: // Perform the question/answer processing 24: try { 25: DataInputStream is = new DataInputStream(new 26: BufferedInputStream(clientSocket.getInputStream())); 27: PrintStream os = new PrintStream(new 28: BufferedOutputStream(clientSocket.getOutputStream()), false); 29: String inLine, outLine; 30: 31: // Output server request 32: outLine = processInput(null); 33: os.println(outLine); 34: os.flush(); 35: 36: // Process and output user input 37: while ((inLine = is.readLine()) != null) { 38: outLine = processInput(inLine); 39: os.println(outLine); 40: os.flush(); 41: if (outLine.equals("Bye.")) 42: break; 43: } 44: 45: // Cleanup 46: os.close(); 47: is.close(); 48: clientSocket.close(); 49: } 50: catch (Exception e) { 51: System.err.println("Exception: " + e); 52: e.printStackTrace(); 53: } 54: } 55: } Analysis file:///G|/ebooks/1575211831/ch26.htm (16 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java The run method first initializes the questions and answers by calling initQnA. You'll learn about the initQnA method in a moment. An infinite while loop is then entered that waits for a client connection. When a client connects, the appropriate I/O streams are created, and the communication is handled via the processInput method. You'll learn about processInp ut next. processInput continually processes client responses and handles asking new questions until the client decides not to receive any more questions. This is evidenced by the server sending the string "Bye.". The run method then cleans up the streams and client socket. The processInput method keeps up with the server state and manages the logic of the whole question/answer process. The source code for processInput is shown in Listing 26.6. Listing 26.6. The processInput method. 1: String processInput(String inStr) { 2: String outStr; 3: 4: switch (state) { 5: case WAITFORCLIENT: 6: // Ask a question 7: outStr = questions[num]; 8: state = WAITFORANSWER; 9: break; 10: 11: case WAITFORANSWER: 12: // Check the answer 13: if (inStr.equalsIgnoreCase(answers[num])) 14: outStr = "That's correct! Want another? (y/n)"; 15: else 16: outStr = "Wrong, the correct answer is " + answers[num] + 17: ". Want another? (y/n)"; 18: state = WAITFORCONFIRM; 19: break; 20: 21: case WAITFORCONFIRM: 22: // See if they want another question 23: if (inStr.equalsIgnoreCase("y")) { 24: num = Math.abs(rand.nextInt()) % questions.length; 25: outStr = questions[num]; 26: state = WAITFORANSWER; 27: } 28: else { 29: outStr = "Bye."; 30: state = WAITFORCLIENT; 31: } 32: break; 33: } 34: return outStr; 35: } file:///G|/ebooks/1575211831/ch26.htm (17 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Analysis The first thing to note about the processInput method is the outStr local variable. The value of this string is sent back to the client in the run method when processInput returns. So keep an eye on how processInput uses outStr to convey information back to the client. In FortuneServer, the state WAITFORCLIENT represents the server when it is idle and waiting for a client connection. Understand that each case statement in processInput() represents the server leaving the given state. For example, the WAITFORCLIENT case statement is entered when the server has just left the WAITFORCLIENT state. In other words, a client has just connected to the server. When this occurs, the server sets the output string to the current question and sets the state to WAITFORANSWER. If the server is leaving the WAITFORANSWER state, it means that the client has responded with an answer. processInput checks the client's answer against the correct answer and sets the output string accordingly. It then sets the state to WAITFORCONFIRM. The WAITFORCONFIRM state represents the server waiting for a confirmation answer from the client. In processInput, the WAITFORCONFIRM case statement indicates that the server is leaving the state because the client has returned a confirmation (yes or no). If the client answered yes with a y, processInput picks a new question and sets the state back to WAITFORANSWER. Otherwise, the server tells the client Bye. and returns the state to WAITFORCLIENT to await a new client connection. Similar to Fortune, the questions and answers in Trivia are stored in a text file. This file is called QnA.txt and is organized with questions and answers on alternating lines. By alternating, I mean that each question is followed by its answer on the following line, which is in turn followed by the next question. Following is a partial listing of the QnA.txt file: What caused the craters on the moon? meteorites How far away is the moon (in miles)? 239000 How far away is the sun (in millions of miles)? 93 Is the Earth a perfect spere? no What is the internal temperature of the Earth (in degrees F)? 9000 The initQnA method handles the work of reading the questions and answers from the text file and storing them in separate string arrays. Listing 26.7 contains the source code for the initQnA method. Listing 26.7. The initQnA method. 1: private boolean initQnA() { 2: try { 3: File inFile = new File("QnA.txt"); 4: FileInputStream inStream = new FileInputStream(inFile); 5: byte data = new byte[(int)inFile.length()]; 6: 7: // Read the questions and answers into a byte array 8: if (inStream.read(data) <= 0) { 9: System.err.println("Error: couldn't read questions and answers"); 10: return false; 11: } 12: file:///G|/ebooks/1575211831/ch26.htm (18 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: } // See how many question/answer pairs there are for (int i = 0; i < data.length; i++) if (data[i] == (byte)'\n') numQuestions++; numQuestions /= 2; questions = new String[numQuestions]; answers = new String[numQuestions]; // Parse the questions and answers into arrays of strings int start = 0, index = 0; boolean isQ = true; for (int i = 0; i < data.length; i++) if (data[i] == (byte)'\n') { if (isQ) { questions[index] = new String(data, 0, start, i - start - 1); isQ = false; } else { answers[index] = new String(data, 0, start, i - start - 1); isQ = true; index++; } start = i + 1; } } catch (FileNotFoundException e) { System.err.println("Exception: couldn't find the fortune file"); return false; } catch (IOException e) { System.err.println("Exception: I/O error trying to read questions"); return false; } return true; Analysis The initQnA method is similar to the initFortunes method in FortuneServer, except that in this case two arrays are being filled with alternating strings. The two arrays are the question and answer string arrays. Rather than repeat the earlier explanation for initFortunes, I'll leave it up to you to compare and contrast the differences between initFortunes and initQnA. You'll find that the differences are very small and have to do with the fact that you are now filling two arrays with alternating strings. The only remaining method in TriviaServer is main, which follows: public static void main(String args) { TriviaServer server = new TriviaServer(); server.start(); } file:///G|/ebooks/1575211831/ch26.htm (19 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Like the main method in FortuneServer, all this main method does is create the server object and get it started with a call to the start method. Implementing the Trivia Client Applet Since the client side of the Trivia example requires the user to type in answers and receive responses back from the server, it is more straightforward to implement as a command-line application. Sure, this may not be as cute as a graphical applet, but it makes it very easy to see the communication events as they unfold. The client application is called Trivia and is located on the CD-ROM in the file Trivia.java. The only member defined in the Trivia class is PORTNUM, which defines the port number used by both the client and server. There is also only one method defined in the Trivia class: main. The source code for the main method is shown in Listing 26.8. Listing 26.8. The main method. 1: public static void main(String args) { 2: Socket socket; 3: DataInputStream in; 4: PrintStream out; 5: String address; 6: 7: // Check the command-line args for the host address 8: if (args.length != 1) { 9: System.out.println("Usage: java Trivia <address>"); 10: return; 11: } 12: else 13: address = args[0]; 14: 15: // Initialize the socket and streams 16: try { 17: socket = new Socket(address, PORTNUM); 18: in = new DataInputStream(socket.getInputStream()); 19: out = new PrintStream(socket.getOutputStream()); 20: } 21: catch (IOException e) { 22: System.err.println("Exception: couldn't create stream socket"); 23: System.exit(1); 24: } 25: 26: // Process user input and server responses 27: try { 28: StringBuffer str = new StringBuffer(128); 29: String inStr; 30: int c; 31: 32: while ((inStr = in.readLine()) != null) { 33: System.out.println("Server: " + inStr); 34: if (inStr.equals("Bye.")) 35: break; 36: while ((c = System.in.read()) != '\n') 37: str.append((char)c); 38: System.out.println("Client: " + str); file:///G|/ebooks/1575211831/ch26.htm (20 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java 39: out.println(str.toString()); 40: out.flush(); 41: str.setLength(0); 42: } 43: 44: // Cleanup 45: out.close(); 46: in.close(); 47: socket.close(); 48: } 49: catch (IOException e) { 50: System.err.println("Exception: I/O error trying to talk to server"); 51: } 52: } Analysis The first interesting thing you might notice about the main method is that it looks for a command-line argument. The command-line argument required of the Trivia client is the address of the server, such as thetribe.com. You may be wondering why the Fortune client didn't require you to specify a server address. The reason is that Java applets are accessed via Web pages, which are always associated with a particular server. So Java applets are inherently tied to a server and can therefore query the server for its address. This was accomplished in the Fortune client by calling the getHost method. With Java applications, you don't have this option because there is no inherent server associated with the application. So you have to either hard-code the server address or ask for it as a command-line argument. I'm not very fond of hard-coding because it requires you to recompile any time you want to change something. Hence the command-line argument! If the server address command-line argument is valid (not null), the main method creates the necessary socket and I/O streams. It then enters a while loop, where it processes information from the server and transmits user requests back to the server. When the server quits sending information, the while loop falls through, and the main method cleans up the socket and streams. And that's all there is to the Trivia client! Running Trivia Like Fortune, the Trivia server must be running in order for the client to work. To get things started, you must first run the server by using the Java interpreter; this is done from a command line, like this: java TriviaServer The Trivia client is also run from a command line, but you must specify a server address as the only argument. Following is an example of running the Trivia client and connecting to the server thetribe.com: java Trivia "thetribe.com" After running the Trivia client and answering a few questions, you should see output similar to this: Server: Is the Galaxy rotating? yes Client: yes Server: That's correct! Want another? (y/n) y file:///G|/ebooks/1575211831/ch26.htm (21 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java Client: y Server: Is the Earth a perfect sphere? no Client: no Server: That's correct! Want another? (y/n) y Client: y Server: What caused the craters on the moon? asteroids Client: asteroids Server: Wrong, the correct answer is meteorites. Want another? (y/n) n Client: n Server: Bye. Summary Today you have learned a wealth of information about client/server network programming in Java. You began the lesson by learning some fundamental concepts about the Internet and how it is organized as a network. More specifically, you learned about addresses, protocols, and ports, which all play a critical role in Internet communications. From there, you moved on to learning about client/server computing and how Java supports the client/server model through two different types of sockets: datagram sockets and stream sockets. The last half of today's lesson led you through building two complete client/server programs. These two examples demonstrate the differing approaches to client/server network programming afforded by the Java datagram and stream socket classes. Both of these examples should serve as a solid basis for your own client/server projects. If all the coding over the past few days has taken its toll on you, relax-tomorrow's lesson involves absolutely no programming. Tomorrow's lesson covers the Java standard extension APIs, which are a new set of API extensions that promise to add all kinds of neat features to Java. Aren't you excited? Q&A Q: A: Q: A: Q: A: Q: A: Why is the client/server paradigm so important in Java network programming? The client/server model was integrated into Java because it has proved time and again to be superior to other networking approaches. By dividing the process of serving data from the process of viewing and working with data, the client/server approach provides network developers with the freedom to implement a wide range of solutions to common network problems. Why are datagram sockets less suitable for network communications than stream sockets? The primary reason is speed, because you have no way of knowing when information transferred through a datagram socket will reach its destination. Admittedly, you don't really know for sure when stream socket data will get to its destination either, but you can rest assured it will be faster than with the datagram socket. Also, datagram socket transfers have the additional complexity of your having to reorganize the incoming data, which is an unnecessary and time-consuming annoyance except in very rare circumstances. How do I incorporate Fortune into a Web site? Beyond simply including the client applet in an HTML document that is served up by your Web server, you must also make sure that the Fortune server (FortuneServer) is running on the Web server machine. Without the fortune server, the clients are worthless. How do I change the trivia questions and answers for Trivia? You simply edit the QnA.txt text file and add as many questions and answers as you want. Just make sure that each question and answer appears on its own line, and that each answer immediately follows its corresponding question. file:///G|/ebooks/1575211831/ch26.htm (22 of 23) [11/06/2000 7:45:11 PM] Day 26 -- Client/Server Networking in Java file:///G|/ebooks/1575211831/ch26.htm (23 of 23) [11/06/2000 7:45:11 PM] file:///G|/ebooks/1575211831/f26-1.gif file:///G|/ebooks/1575211831/f26-1.gif [11/06/2000 7:45:12 PM] file:///G|/ebooks/1575211831/f26-2.gif file:///G|/ebooks/1575211831/f26-2.gif [11/06/2000 7:45:12 PM] file:///G|/ebooks/1575211831/f26-3.gif file:///G|/ebooks/1575211831/f26-3.gif [11/06/2000 7:45:12 PM] file:///G|/ebooks/1575211831/f26-4.gif file:///G|/ebooks/1575211831/f26-4.gif [11/06/2000 7:45:13 PM] Day 25 -- Fun with Image Filters Day 25 Fun with Image Filters by Michael Morrison CONTENTS q The Basics of Color q Color Images in Java q Color Models r Direct Color Models r Index Color Models q The Color Model Classes q Image Filters q The Image Filter Classes q Writing Your Own Image Filters r A Color Image Filter r An Alpha Image Filter r A Brightness Image Filter q Using Image Filters q Summary q Q&A As you learned both yesterday and earlier in this book, Java provides lots of neat ways to work with graphical images. One of Java's more interesting image-handling features is its support for image filters, which allow you to alter the individual pixels of an image according to a particular algorithm. Image filters can range from simple effects such as adjusting the brightness of an image to more advanced effects such as embossing. At the heart of Java's graphics and imaging are Java color models. Today's lesson begins by looking into what a color model is, along with how color models affect image handling and Java graphics in general. You'll then move on to learn about Java image filters and how they are used to manipulate graphical images. Java provides a variety of image filter classes that interact together to form a framework for easily filtering graphical images. You can extend the standard Java image filtering classes and build your own image filters to perform just about any type of image processing you can imagine. You'll finish today's lesson by implementing your own image filters. So today's lesson covers the following primary topics: file:///G|/ebooks/1575211831/ch25.htm (1 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters q q q q The basics of color Color models Image filters Writing your own image filters I think you'll find that image filters are a very interesting and powerful feature of Java that haven't received as much attention as they deserve. Granted, image filters don't share the wide applicability of some other aspects of Java, but they can be fun to tinker with and will inevitably be useful in some specialized applets. The Basics of Color Everything graphical in Java, including image filters, begins with the concept of color. I know, you've probably heard something about color before, but maybe not in the way I'm about to describe. You see, when I talk about the notion of color, I mean a computer's notion of color. In other words, I want to briefly take a look at how color is represented on a computer, since it will ultimately give you more insight into how image filters work. Since modern computer environments are highly graphical, it is imperative that computers know how to process and display information in color. Although most computer operating systems have some degree of platform-dependent handling of color, they all share a common approach to the general representation of colors. Knowing that all data in a computer is ultimately stored in a binary form, it stands to reason that physical colors are somehow mapped to binary values, or numbers, in the computer domain. The question is, how are colors mapped to numbers? One way to come up with numeric representations of colors would be to start at one end of the color spectrum and assign a number to each color until you reach the other end. This approach solves the problem of representing a color as a number, but it doesn't provide any way to handle the mixing of colors. As anyone who has experienced the joy of Play-Doh can tell you, colors react in different ways when combined with each other. The way colors mix to form other colors goes back to physics, which is a little beyond this discussion. The point is that a computer color system needs to be able to handle mixing colors with accurate, predictable results. The best place to look for a solution to the color problem is a color computer monitor. A color monitor has three electron guns: red, green, and blue. The output from these three guns converge on each pixel of the screen, exciting phosphors to produce the appropriate color (see Figure 25.1). The combined intensities of each gun determine the resulting pixel color. This convergence of different colors from the monitor guns is very similar to the convergence of different colored Play-Doh. The primary difference is that monitors use only these three colors (red, green, and blue) to come up with every possible color that can be represented on a computer. (Actually, the biggest difference is that Play-Doh can't display high-resolution computer graphics, but that's another discussion.) Figure 25.1 : Electron guns in a color monitor converging to create a unique color. Knowing that monitors form unique colors by using varying intensities of the colors red, green, and blue, you might be thinking that a good solution to the color problem would be to provide an intensity value for each of these primary colors. This is exactly how computers model color. Computers represent different colors by combining the numeric intensities of the primary colors red, green, and blue. This color system is file:///G|/ebooks/1575211831/ch25.htm (2 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters known as RGB (red, green, blue) and is fully supported by Java. New Term RGB is the primary color system used by Java and stands for red, green, blue. Although RGB is the most popular computer color system in use, there are others. Another popular color system is HSB, which stands for hue, saturation, brightness. In this system, colors are defined by varying degrees of hue, saturation, and brightness. The HSB color system is also supported by Java. Note You already learned about Java's support for color on Day 9, "Graphics, Fonts, and Color." Just so you won't think I'm repeating what you've already learned, understand that this discussion of color is meant to lay more complete groundwork for the advanced issues of using color that are a big part of Java image filtering. Color Images in Java Bitmapped color images are composed of pixels that describe the colors at each location of an image. Each pixel in an image has a specific color that is usually described using the RGB color system. Java provides support for working with 32-bit images, which means that each pixel in an image is described using 32 bits. The red, green, and blue components of a pixel's color are stored in these 32 bits, along with an alpha component. The alpha component of a pixel refers to the transparency or opaqueness of the pixel. New Term A pixel is the smallest graphical component of an image and is assigned a particular color. New Term The alpha component of a pixel refers to the transparency or opaqueness of the pixel. A 32-bit Java image pixel is therefore composed of red, green, blue, and alpha components. By default, these four components are packed into a 32-bit pixel value, as shown in Figure 25.2. Notice that each component is described by 8 bits (a byte), yielding possible values between 0 and 255 for each. These components are packed into the 32-bit pixel value from high-order byte to low-order byte in the following order: alpha, red, green, and blue. It is possible for the pixel components to be packed differently, but this is the default pixel storage method used in Java. Figure 25.2 : The four components of a pixel in a 32-bit Java image. A color component value of 0 means the component is absent, and a value of 255 means it is maxed out. If all three color components are 0, the resulting pixel color is black. Likewise, if all three components are 255, the color is white. If the red component is 255 and the others are 0, the resulting color is pure red. The alpha component describes the transparency of a pixel, independent of the color components. An alpha value of 0 means a pixel is completely transparent (invisible), and an alpha value of 255 means a pixel is file:///G|/ebooks/1575211831/ch25.htm (3 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters completely opaque. Values between 0 and 255 enable the background color to show through a pixel in varying degrees. The color components of a Java image are encapsulated in a simple class called Color. The Color class is a member of the java.awt package and represents the three primary color components red, green, and blue. This class is useful because it provides a clean abstraction for representing color, along with useful methods for extracting and modifying the primary components. The Color class also contains predefined constant members representing many popular colors. Color Models In Java, pixel colors are managed through color models. Java color models provide an important abstraction that enables Java to work with images of different formats in a similar fashion. More specifically, a color model is a Java object that provides methods for translating from pixel values to the corresponding red, green, and blue color components of an image. At first, this may seem like a trivial chore, knowing that pixel color components are packed neatly into a 32-bit value. However, there are different types of color models reflecting different methods of maintaining pixel colors. The two types of color models supported by Java are direct color models and index color models. New Term A color model is an abstraction that provides a means to convert pixel color values to absolute colors. Color models are used extensively in the internal implementations of the various Java image processing classes. What does this mean to you, the ever-practical Java programmer? It means that by understanding color models you know a great deal about the internal workings of color in the Java graphics system. Without fully understanding color models and how they work, you would no doubt run into difficulties when trying to work with the advanced graphics and image-processing classes provided by Java. Direct Color Models Direct color models are based on the earlier description of pixels, where each pixel contains specific color and alpha components. Direct color models provide methods for translating these types of pixels into their corresponding color and alpha components. Typically, direct color models extract the appropriate components from the 32-bit pixel value using bit masks. Technical Note A bit mask is a binary code used to extract specific bits out of a numeric value. The bits are extracted by bitwise ANDing the mask with the value. Masks themselves are typically specified in hexadecimal. For example, to mask out the low-order word of a 32-bit value, you use the mask 0x0000FFFF. file:///G|/ebooks/1575211831/ch25.htm (4 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters Index Color Models Index color models work differently than direct color models. In fact, index color models work with pixels containing completely different information than you've learned thus far. Pixels in an image using an index color model don't contain the alpha and RGB components like the pixels used in a direct color model. An index color model pixel contains an index into an array of fixed colors (see Figure 25.3). This array of colors is called a color map. Figure 25.3 : An index color model pixel and its associated color map. New Term A color map is a list of colors referenced by an image using an index color model. Color maps are also sometimes referred to as palettes. An example of an image that uses an index color model is a 256-color image. 256-color images use 8 bits to describe each pixel, which doesn't leave much room for RGB components, let alone an alpha component. Rather than try to cram these components into 8 bits, 256-color pixels store an 8-bit index into a color map. The color map itself has 256 color entries that each contain RGB and alpha values describing a particular color. Index color models provide methods for resolving pixels containing color map indexes into alpha, red, green, and blue components. Index color models handle looking up the index of a pixel in the color map and extracting the appropriate components from the color entry. The Color Model Classes Java provides standard classes for working with color models in the java.awt.image package. At the top of the hierarchy is the ColorModel class, which defines the core functionality required of all color models. The ColorModel class is an abstract class containing the basic support required to translate pixel values into alpha and color components. Two other classes are derived from ColorModel, representing the two types of color models supported by Java: DirectColorModel and IndexColorModel. The DirectColorModel class is derived from ColorModel and provides specific support for direct color models. If you recall, pixels in a direct color model directly contain the alpha and color components in each pixel value. The IndexColorModel class is also derived from ColorModel and provides support for index color models. Pixels in an index color model contain indexes into a fixed array of colors known as a color map, or palette. Even though the color model classes are important in understanding the conceptual side of Java graphics, you won't be using them directly when working with image filters, so there's no need to go into any more detail with them here. Image Filters Now it's time to move into the meat of today's lesson: image filters. Image filtering is sometimes referred to as image processing. Most popular graphical paint programs contain image-processing features, such as sharpening or softening an image. Typically, image processing programs involve the usage of complex file:///G|/ebooks/1575211831/ch25.htm (5 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters libraries of routines for manipulating images. Java provides a simple yet powerful framework for manipulating images. In Java, image processing objects are called image filters, and they serve as a way to abstract the filtering of an image without worrying about the details associated with the source or destination of the image data. New Term An image filter is an object that alters the individual pixels of an image according to a particular algorithm. A Java image filter can be thought of quite literally as a filter into which all the data for an image must enter and exit on its way from a source to a destination. Take a look at Figure 25.4 to see how image data passes through an image filter. Figure 25.4 : Image data passing through an image filter. While passing through an image filter, the individual pixels of an image can be altered in any way as determined by the filter. By design, image filters are structured to be self-contained components. The image filter model supported by Java is based on three logical components: an image producer, an image filter, and an image consumer. The image producer makes the raw pixel data for an image available, the image filter in turn filters this data, and the resulting filtered image data is passed on to the image consumer where it has usually been requested. Figure 25.5 shows how these three components interact with each other. Figure 25.5 : The relationship between an image producer, an image filter, and an image consumer. New Term An image producer is an abstract data source that makes available raw pixel data for an image. New Term An image consumer is an abstract data destination that receives raw pixel data from an image consumer. Breaking down the process of filtering images into these three components provides a very powerful object-oriented solution to a complex problem. Different types of image producers can be derived that are able to retrieve image data from a variety of image sources. Likewise, this organization allows filters to ignore the complexities associated with different image sources and focus on the details of manipulating the individual pixels of an image. The Image Filter Classes Java's support for image filters is scattered across several classes and interfaces. You don't necessarily have to understand all these classes in detail to work with image filters, but it is important that you understand what functionality they provide and where they fit into the scheme of things. Following are the Java classes and interfaces that provide support for image filtering: q ImageProducer q FilteredImageSource q MemoryImageSource file:///G|/ebooks/1575211831/ch25.htm (6 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters q q q q q ImageConsumer PixelGrabber ImageFilter RGBImageFilter CropImageFilter The ImageProducer interface describes the methods necessary to extract image pixel data from Image objects. Classes implementing the ImageProducer interface provide implementations of these methods specific to the image source they represent. For example, the MemoryImageSource class implements the ImageProducer interface and produces image pixels from an array of pixel values stored in memory. The FilteredImageSource class implements the ImageProducer interface and produces filtered image data. The filtered image data produced is based on the image and the filter object passed in the FilteredImageSource class's constructor. FilteredImageSource provides a very simple way to apply image filters to Image objects. The MemoryImageSource class implements the ImageProducer interface and produces image data based on an array of pixels in memory. This is very useful in cases where you need to build an Image object directly from data in memory. The ImageConsumer interface describes methods necessary for an object to retrieve image data from an image producer. Objects implementing the ImageConsumer interface are attached to an image producer object when they are interested in its image data. The image producer object delivers the image data by calling methods defined by the ImageConsumer interface. The PixelGrabber class implements the ImageConsumer interface and provides a way of retrieving a subset of the pixels in an image. A PixelGrabber object can be created based on either an Image object or an object implementing the ImageProducer interface. The constructor for PixelGrabber enables you to specify a rectangular section of the image data to be grabbed. This image data is then delivered by the image producer to the PixelGrabber object. The ImageFilter class provides the basic functionality of an image filter that operates on image data being delivered from an image producer to an image consumer. ImageFilter objects are specifically designed to be used in conjunction with FilteredImageSource objects. The ImageFilter class is implemented as a null filter, which means that it passes image data unmodified. Nevertheless, it implements the overhead for processing the data in an image. The only thing missing is the actual modification of the pixel data, which is left up to derived filter classes. This is actually a very nice design because it enables you to create new image filters by deriving from ImageFilter and overriding only a few methods. The ImageFilter class operates on an image using the color model defined by the image producer. The RGBImageFilter class, on the other hand, derives from ImageFilter and implements an image filter specific to the default RGB color model. RGBImageFilter provides the overhead necessary to process image data in a single method that converts pixels one at a time in the default RGB color model. This processing takes place in the default RGB color model regardless of the color model used by the image producer. Like ImageFilter, RGBImageFilter is meant to be used in conjunction with the FilteredImageSource image producer. The seemingly strange thing about RGBImageFilter is that it is an abstract class, so you can't instantiate objects from it. It is abstract because of a single abstract method, filterRGB. The filterRGB method file:///G|/ebooks/1575211831/ch25.htm (7 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters is used to convert a single input pixel to a single output pixel in the default RGB color model. filterRGB is the workhorse method that handles filtering the image data; each pixel in the image is sent through this method for processing. To create your own RGB image filters, all you must do is derive from RGBImageFilter and implement the filterRGB method. This is the technique you'll use a little later today when you implement your own image filters. The RGBImageFilter class contains a member variable that is very important in determining how it processes image data: canFilterIndexColorModel. The canFilterIndexColorModel member variable is a boolean that specifies whether the filterRGB method can be used to filter the color map entries of an image using an index color model, rather than the individual pixels themselves. If this member variable is false, each pixel in the image is processed, similar to if it was using a direct color model. The CropImageFilter class is derived from ImageFilter and provides a means of extracting a rectangular region within an image. Like ImageFilter, the CropImageFilter class is designed to be used with the FilteredImageSource image producer. You may be a little confused by CropImageFilter because it sounds a lot like the PixelGrabber class mentioned earlier. It is important to understand the differences between these two classes because they perform very different functions. First, remember that PixelGrabber implements the ImageConsumer interface, so it functions as an image consumer. CropImageFilter, on the other hand, is an image filter. This means that PixelGrabber is used as a destination for image data, where CropImageFilter is applied to image data in transit. You use PixelGrabber to extract a region of an image to store in an array of pixels (the destination). You use CropImageFilter to extract a region of an image that is sent along to its destination (usually another Image object). Writing Your Own Image Filters Although the standard Java image filter classes are powerful as a framework, they aren't that exciting to work with by themselves. Image filters don't really get interesting until you start implementing your own. Fortunately, the Java classes make it very simple to write your own image filters. All the image filters you'll develop in today's lesson are derived from RGBImageFilter, which enables you to filter images through a single method, filterRGB. It really is as easy as deriving your class from RGBImageFilter and implementing the filterRGB method. Let's give it a try! A Color Image Filter Probably the simplest image filter imaginable is one that filters out the individual color components (red, green, and blue) of an image. The ColorFilter class does exactly that. Listing 25.1 contains the source code for the ColorFilter class. It is located on the CD-ROM in the file ColorFilter.java. Listing 25.1. The ColorFilter class. 1: class ColorFilter extends RGBImageFilter { 2: boolean red, green, blue; file:///G|/ebooks/1575211831/ch25.htm (8 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: } public ColorFilter(boolean r, boolean g, boolean b) { red = r; green = g; blue = b; canFilterIndexColorModel = true; } public int filterRGB(int x, int y, int rgb) { // Filter the colors int r = red ? 0: ((rgb >> 16) & 0xff); int g = green ? 0: ((rgb >> 8) & 0xff); int b = blue ? 0: ((rgb >> 0) & 0xff); // Return the result return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0); } Analysis The ColorFilter class is derived from RGBImageFilter and contains three boolean member variables that determine which colors are to be filtered out of the image. These member variables are set by the parameters passed into the constructor. The member variable inherited from RGBImageFilter-canFilterIndexColorModel-is set to true to indicate that the color map entries can be filtered using filterRGB if the incoming image is using an index color model. Beyond the constructor, ColorFilter implements only one method, filterRGB, which is the abstract method defined in RGBImageFilter. filterRGB takes three parameters: the x and y position of the pixel within the image and the 32-bit (integer) color value. The only parameter you are concerned with is the color value, rgb. Recalling that the default RGB color model places the red, green, and blue components in the lower 24 bits of the 32-bit color value, it is easy to extract each one by shifting out of the rgb parameter. These individual components are stored in the local variables r, g, and b. Notice, however, that each color component is shifted only if it is not being filtered. For filtered colors, the color component is set to 0. The new color components are then shifted back into a 32-bit color value and returned from filterRGB. Notice that care is taken to ensure that the alpha component of the color value is not altered. The 0xff000000 mask takes care of this because the alpha component resides in the upper byte of the color value. Congratulations! You've written your first image filter! You have two more to go before you plug them all into a test program. file:///G|/ebooks/1575211831/ch25.htm (9 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters An Alpha Image Filter It isn't always apparent to programmers how the alpha value stored in the color value for each pixel affects an image. Remember that the alpha component specifies the transparency or opaqueness of a pixel. By altering the alpha values for an entire image, you can make it appear to fade in and out. This works because the alpha values range from totally transparent (invisible) to totally opaque. The AlphaFilter class filters the alpha components of an image according to the alpha level you supply in its constructor. Listing 25.2 contains the source code for the AlphaFilter class. It is located on the CD-ROM in the file AlphaFilter.java. Listing 25.2. The AlphaFilter class. 1: class AlphaFilter extends RGBImageFilter { 2: int alphaLevel; 3: 4: public AlphaFilter(int alpha) { 5: alphaLevel = alpha; 6: canFilterIndexColorModel = true; 7: } 8: 9: public int filterRGB(int x, int y, int rgb) { 10: // Adjust the alpha value 11: int alpha = (rgb >> 24) & 0xff; 12: alpha = (alpha * alphaLevel) / 255; 13: 14: // Return the result 15: return ((rgb & 0x00ffffff) | (alpha << 24)); 16: } 17: } Analysis The AlphaFilter class contains a single member variable, alphaLevel, that keeps up with the alpha level to be applied to the image. This member variable is initialized in the constructor, as is the canFilterIndexModel member variable. Similar to the ColorFilter class, the filterRGB method is the only other method implemented by AlphaFilter. The alpha component of the pixel is first extracted by shifting it into a local variable, alpha. This value is then scaled according to the alphaLevel member variable initialized in the constructor. The purpose of the scaling is to alter the alpha value based on its current value. If you were to set the alpha component to the alpha level, you wouldn't be taking into account the original alpha component value. The new alpha component is shifted back into the pixel color value and the result returned from filterRGB. Notice that the red, green, and blue components are preserved by using the 0x00ffffff mask. file:///G|/ebooks/1575211831/ch25.htm (10 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters A Brightness Image Filter So far the image filters you've seen have been pretty simple. The last one you create is a little more complex, but it acts as a more interesting filter. The BrightnessFilter class implements an image filter that brightens or darkens an image based on a brightness percentage you provide in the constructor. Listing 25.3 contains the source code for the BrightnessFilter class. It is located on the CD-ROM in the file BrightnessFilter.java. Listing 25.3. The BrightnessFilter class. 1: class BrightnessFilter extends RGBImageFilter { 2: int brightness; 3: 4: public BrightnessFilter(int b) { 5: brightness = b; 6: canFilterIndexColorModel = true; 7: } 8: 9: public int filterRGB(int x, int y, int rgb) { 10: // Get the individual colors 11: int r = (rgb >> 16) & 0xff; 12: int g = (rgb >> 8) & 0xff; 13: int b = (rgb >> 0) & 0xff; 14: 15: // Calculate the brightness 16: r += (brightness * r) / 100; 17: g += (brightness * g) / 100; 18: b += (brightness * b) / 100; 19: 20: // Check the boundaries 21: r = Math.min(Math.max(0, r), 255); 22: g = Math.min(Math.max(0, g), 255); 23: b = Math.min(Math.max(0, b), 255); 24: 25: // Return the result 26: return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0); 27: } 28: } Analysis file:///G|/ebooks/1575211831/ch25.htm (11 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters The BrightnessFilter class contains one member variable, brightness, that keeps track of the percentage to alter the brightness of the image. This member variable is set via the constructor, along with the canFilterIndexModel member variable. The brightness member variable can contain values in the range -100 to 100. A value of -100 means the image is darkened by 100 percent, and a value of 100 means the image is brightened by 100 percent. A value of 0 doesn't alter the brightness of the image at all. It should come as no surprise by now that filterRGB is the only other method implemented by BrightnessFilter. In filterRGB, the individual color components are first extracted into the local variables r, g, and b. The brightness effects are then calculated based on the brightness member variable. The new color components are then checked against the 0 and 255 boundaries and modified if necessary. Finally, the new color components are shifted back into the pixel color value and returned from filterRGB. Hey, it's not that complicated after all! Using Image Filters You put in the time writing some of your own image filters, but you have yet to enjoy the fruit of your labors. It's time to plug the filters into a real Java applet and see how they work. Figure 25.6 shows the FilterTest applet busily at work filtering an image of a pear, quite literally the fruit of your labors! Figure 25.6 : The FilterTest applet. The FilterTest applet uses all three filters you've written to enable you to filter an image of a pear. The R, G, and B keys on the keyboard change the different colors filtered by the color filter. The left and right arrow keys modify the alpha level for the alpha filter. The up and down arrow keys alter the brightness percentage used by the brightness filter. Finally, the Home key restores the image to its unfiltered state. Listing 25.4 contains the source code for the FilterTest applet. The complete source code and executables for the FilterTest applet are located on the accompanying CD-ROM. Listing 25.4. The FilterTest applet. 1: public class FilterTest extends Applet { 2: Image src, dst; 3: boolean red, green, blue; 4: final int alphaMax = 9; 5: int alphaLevel = alphaMax; 6: int brightness; 7: 8: public void init() { 9: src = getImage(getDocumentBase(), "Pear.gif"); file:///G|/ebooks/1575211831/ch25.htm (12 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: dst = src; } public void paint(Graphics g) { g.drawImage(dst, 0, 0, this); } public boolean keyDown(Event evt, int key) { switch (key) { case Event.HOME: red = false; green = false; blue = false; alphaLevel = alphaMax; brightness = 0; break; case Event.LEFT: if (--alphaLevel < 0) alphaLevel = 0; break; case Event.RIGHT: if (++alphaLevel > alphaMax) alphaLevel = alphaMax; break; case Event.UP: brightness = Math.min(brightness + 10, 100); break; case Event.DOWN: brightness = Math.max(-100, brightness - 10); break; case (int)'r': case (int)'R': red = !red; break; case (int)'g': case (int)'G': green = !green; break; case (int)'b': case (int)'B': blue = !blue; break; default: return false; } filterImage(); return true; file:///G|/ebooks/1575211831/ch25.htm (13 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters 57: } 58: 59: void filterImage() { 60: dst = src; 61: 62: // Apply the color filter 63: dst = createImage(new FilteredImageSource(dst.getSource(), 64: new ColorFilter(red, green, blue))); 65: 66: // Apply the alpha filter 67: dst = createImage(new FilteredImageSource(dst.getSource(), 68: new AlphaFilter((alphaLevel * 255) / alphaMax))); 69: 70: // Apply the brightness filter 71: dst = createImage(new FilteredImageSource(dst.getSource(), 72: new BrightnessFilter(brightness))); 73: 74: // Redraw the image 75: repaint(); 76: } 77: } Analysis The FilterTest applet class contains member variables for keeping up with the source and destination images, along with member variables for maintaining the various filter parameters. The first method implemented by FilterTest is init, which loads the image Pear.gif into the src member variable. It also initializes the dst member variable to the same image. The paint method is implemented next, and simply consists of a call to the drawImage method, which draws the destination (filtered) Image object. The keyDown method is implemented to handle keyboard events generated by the user. In this case, the keys used to control the image filters are handled in the switch statement. The corresponding member variables are altered according to the keys pressed. Notice the call to the filterImage near the end of keyDown. The filterImage method is where the actual filtering takes place; it applies each image filter to the image. The dst member variable is first initialized with the src member variable to restore the destination image to its original state. Each filter is then applied using a messy-looking call to createImage. The only parameter to createImage is an ImageProducer object. In this case, you create a FilteredImageSource object to pass into createImage. The constructor for FilteredImageSource takes two parameters: an image producer and an image filter. The first parameter is an ImageProducer object for the source image, which is obtained using the getSource method for the image. The second parameter is an ImageFilter-derived object. The color filter is first applied to the image by creating a ColorFilter object using the three boolean file:///G|/ebooks/1575211831/ch25.htm (14 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters color value member variables. The alpha filter is applied by creating an AlphaFilter object using the alphaLevel member variable. Rather than allowing 255 different alpha levels, the alpha level is normalized to provide only 10 different alpha levels. This is evident in the equation using alphaMax, which is set to 9. Finally, the brightness filter is applied by creating a BrightnessFilter object and passing in the brightness member variable. Summary Although the overall goal of today's lesson is to learn how to use image filters, you also covered a great deal of related material along the way. You first learned about color in general and then about the heart of advanced Java graphics: color models. With color models under your belt, you moved on to image filters. You saw how the Java image filter classes provide a powerful framework for working with images without worrying about unnecessary details. You finished up the lesson by writing three of your own image filters, along with an applet that put them to work filtering a real image. You're now well versed in one of the more advanced areas of Java graphics programming. Just in case you're starting to burn out on all this graphics stuff, tomorrow's lesson shifts gears dramatically and introduces you to client/server network programming in Java. Q&A Q: A: Q: A: Q: A: Q: A: If Java colors are inherently 32 bit, how does Java display color on systems using less than 32 bits to represent color? The reality is that there aren't a lot of computer systems out there equipped to fully support 32-bit color. For example, most high-end pcs and Macintoshes only support 24-bit color. Additionally, the average pc only supports 8-bit color. Java handles this internally by mapping 32-bit color values to the underlying system as efficiently as possible, sometimes by using an index color model. In some cases image quality will suffer because the full range of colors in the image can't be displayed. I still don't understand why there is an alpha component in Java colors. What's the deal? Strictly speaking, all that is required of Java to support a wide range of colors are the three primary color components: red, green, and blue. However, the alpha component adds the ability to alter the opaqueness of a color, which makes it much easier to implement graphics effects that alter the transparency properties of a color. Is there a situation in which I will ever need to implement my own color model? I'm hesitant to say that you'll never need to implement your own color model, but let me say that the situation in which you would need a custom color model is highly unlikely to occur. This is because color models are mainly an internal abstraction used by the Java graphics system itself. I understand why image filters are useful, but what exactly is the importance of image producers and consumers? Image producers and consumers provide a clean abstraction for the source and destination of raw image data. Without image producers and consumers, you would have to use a custom solution each time you wanted to get data from or write data to an image. By having the source and destination of image data clearly defined, more advanced graphics functions like image filters are much easier to work with. file:///G|/ebooks/1575211831/ch25.htm (15 of 16) [11/06/2000 7:45:16 PM] Day 25 -- Fun with Image Filters file:///G|/ebooks/1575211831/ch25.htm (16 of 16) [11/06/2000 7:45:16 PM] file:///G|/ebooks/1575211831/f25-1.gif file:///G|/ebooks/1575211831/f25-1.gif [11/06/2000 7:45:16 PM] Day 9 -- Graphics, Fonts, and Color Day 9 Graphics, Fonts, and Color by Laura Lemay CONTENTS q The Graphics Class r q The Graphics Coordinate System Drawing and Filling r r Rectangles r Polygons r Ovals r Arcs r A Simple Graphics Example r q Lines Copying and Clearing Text and Fonts r r Drawing Characters and Strings r q Creating Font Objects Finding Out Information About a Font Color r Using Color Objects r Testing and Setting the Current Colors r A Simple Color Example q Summary q Q&A Knowing the basics of how applets work is only the first step. The next step is to become familiar with the capabilities Java gives you for drawing to the screen, performing dynamic updating, managing mouse and keyboard events, and creating user interface elements. You'll do all these things this week. You'll start today with how to draw to the screen-that is, how to produce lines and shapes with the built-in graphics primitives, how to print text using fonts, and how to use and modify color in your applets. Today you'll learn, specifically, the following: q How the graphics system works in Java: the Graphics class, the coordinate system used to draw to the screen, and how applets paint and repaint q How to use the Java graphics primitives, including drawing and filling lines, rectangles, ovals, and arcs q How to create and use fonts, including how to draw characters and strings and how to find out the metrics of a given font for better layout file:///G|/ebooks/1575211831/ch9.htm (1 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color q All about color in Java, including the Color class and how to set the foreground (drawing) and background color for your applet Note Today and for the rest of this week, you'll get an introduction to many of the classes that make up the Java class libraries, in particular the classes in the java.awt package. Keep in mind, however, that I only have the space to give you an introduction to these classes-there are many other capabilities available to you in these classes that you can use in your own programs, depending on what you're trying to accomplish. After you finish this book (and perhaps after each of these lessons), you'll want to familiarize yourself with the classes themselves and what they can do. Be sure to check out the Java API documentation for more details; you can find that API documentation on the Java Web site at http://java.sun.com/products/JDK/1.0.2/api/packages.html. The Graphics Class With the basic graphics capabilities built into Java's class libraries, you can draw lines, shapes, characters, and images to the screen inside your applet. Most of the graphics operations in Java are methods defined in the Graphics class. You don't have to create an instance of Graphics in order to draw something in your applet; in your applet's paint() method (which you learned about yesterday), you are given a Graphics object. By drawing on that object, you draw onto your applet and the results appear onscreen. The Graphics class is part of the java.awt package, so if your applet does any painting (as it usually will), make sure you import that class at the beginning of your Java file: import java.awt.Graphics; public class MyClass extends java.applet.Applet { ... } The Graphics Coordinate System To draw an object on the screen, you call one of the drawing methods available in the Graphics class. All the drawing methods have arguments representing endpoints, corners, or starting locations of the object as values in the applet's coordinate system-for example, a line starts at the point 10,10 and ends at the point 20,20. Java's coordinate system has the origin (0,0) in the top-left corner. Positive x values are to the right and positive y values are down. All pixel values are integers; there are no partial or fractional pixels. Figure 9.1 shows how you might draw a simple square by using this coordinate system. Figure 9.1 : The Java graphics coordinate system. Java's coordinate system is different from that of many painting and layout programs, which have their x and y in the bottom left. If you're not used to working with this upside-down graphics system, it may take some practice to get familiar with it. file:///G|/ebooks/1575211831/ch9.htm (2 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color Drawing and Filling The Graphics class provides a set of simple built-in graphics primitives for drawing, including lines, rectangles, polygons, ovals, and arcs . Note Bitmap images, such as GIF files, can also be drawn by using the Graphics class. You'll learn about this tomorrow. Lines To draw straight lines, use the drawLine() method. drawLine() takes four arguments: the x and y coordinates of the starting point and the x and y coordinates of the ending point. So, for example, the following MyLine class draws a line from the point 25,25 to the point 75,75. Note that the drawLine() method is defined in the Graphics class (as are all the other graphics methods you'll learn about today). Here we're using that method for the current graphics context stored in the variable g: import java.awt.Graphics; public class MyLine extends java.applet.Applet { public void paint(Graphics g) { g.drawLine(25,25,75,75); } } Figure 9.2 shows how the simple MyLine class looks in a Java-enabled browser such as Netscape. Figure 9.2 : Drawing lines. Rectangles The Java graphics primitives provide not just one, but three kinds of rectangles: q Plain rectangles q Rounded rectangles, which are rectangles with rounded corners q Three-dimensional rectangles, which are drawn with a shaded border For each of these rectangles, you have two methods to choose from: one that draws the rectangle in outline form and one that draws the rectangle filled with color. To draw a plain rectangle, use either the drawRect() or fillRect() methods. Both take four arguments: the x and y coordinates of the top-left corner of the rectangle, and the width and height of the rectangle to draw. For example, the following class (MyRect) draws two squares: The left one is an outline and the right one is filled (Figure 9.3 shows the result): Figure 9.3 : Rectangles. import java.awt.Graphics; public class MyRect extends java.applet.Applet { file:///G|/ebooks/1575211831/ch9.htm (3 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color public void paint(Graphics g) { g.drawRect(20,20,60,60); g.fillRect(120,20,60,60); } } Rounded rectangles are, as you might expect, rectangles with rounded corners. The drawRoundRect() and fillRoundRect() methods to draw rounded rectangles are similar to regular rectangles except that rounded rectangles have two extra arguments for the width and height of the angle of the corners. Those two arguments determine how far along the edges of the rectangle the arc for the corner will start; the first for the angle along the horizontal plane, the second for the vertical. Larger values for the angle width and height make the overall rectangle more rounded; values equal to the width and height of the rectangle itself produce a circle. Figure 9.4 shows some examples of rounded corners. Figure 9.4 : Rounded corners. The following is a paint() method inside a class called MyRRect that draws two rounded rectangles: one as an outline with a rounded corner 10 pixels square; the other, filled, with a rounded corner 20 pixels square (Figure 9.5 shows the resulting squares): Figure 9.5 : Rounded rectangles. import java.awt.Graphics; public class MyRRect extends java.applet.Applet { public void paint(Graphics g) { g.drawRoundRect(20,20,60,60,10,10); g.fillRoundRect(120,20,60,60,20,20); } } Finally, there are three-dimensional rectangles. These rectangles aren't really 3D; instead, they have a slight shadow effect that makes them appear either raised or indented from the surface of the applet. Three-dimensional rectangles have four arguments for the x and y of the start position and the width and height of the rectangle. The fifth argument is a boolean indicating whether the 3D effect is to raise the rectangle (true) or indent it (false). As with the other rectangles, there are also different methods for drawing and filling: draw3DRect() and fill3DRect(). The following is a class called My3DRect, which produces two 3D squares-the left one raised, the right one indented (Figure 9.6 shows the result): Figure 9.6 : Three dimensional rectangles. import java.awt.Graphics; public class My3DRect extends java.applet.Applet { public void paint(Graphics g) { g.draw3DRect(20,20,60,60,true); g.draw3DRect(120,20,60,60,false); } } Note file:///G|/ebooks/1575211831/ch9.htm (4 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color The 3D rectangles in Figure 9.6 don't look very 3D, do they? In the current version of the Java Developer's Kit, it is extremely difficult to see the 3D effect on 3D rectangles, due to a very small line width. If you are having troubles with 3D rectangles, this may be why. Drawing 3D rectangles in any color other than black makes them easier to see. Polygons Polygons are shapes with an unlimited number of sides. To draw a polygon, you need a set of x and y coordinates. The polygon is then drawn as a set of straight lines from the first point to the second, the second to the third, and so on. As with rectangles, you can draw an outline or a filled polygon (using the drawPolygon() and fillPolygon() methods, respectively). You also have a choice of how you want to indicate the list of coordinates-either as arrays of x and y coordinates or as an instance of the Polygon class. Using the first way of drawing polygons, the drawPolygon() and fillPolygon() methods take three arguments: q An array of integers representing x coordinates q An array of integers representing y coordinates q An integer for the total number of points The x and y arrays should, of course, have the same number of elements. Here's an example of drawing a polygon's outline using this method (Figure 9.7 shows the result): Figure 9.7 : A polygon. import java.awt.Graphics; public class MyPoly extends java.applet.Applet { public void paint(Graphics g) { int exes = { 39,94,97,142,53,58,26 }; int whys = { 33,74,36,70,108,80,106 }; int pts = exes.length; g.drawPolygon(exes,whys,pts); } } Note that Java does not automatically close the polygon; if you want to complete the shape, you have to include the starting point of the polygon at the end of the array. Drawing a filled polygon, however, joins the starting and ending points. The second way of calling drawPolygon() and fillPolygon() is to use a Polygon object to store the individual points of the polygon. The Polygon class is useful if you intend to add points to the polygon or if you're building the polygon on-the-fly. Using the Polygon class, you can treat the polygon as an object rather than having to deal with individual arrays. To create a polygon object, you can either first create an empty polygon: file:///G|/ebooks/1575211831/ch9.htm (5 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color Polygon poly = new Polygon(); or create a polygon from a set of points using integer arrays, as in the previous example: int exes = { 39,94,97,142,53,58,26 }; int whys = { 33,74,36,70,108,80,106 }; int pts = exes.length; Polygon poly = new Polygon(exes,whys,pts); Once you have a polygon object, you can add points to the polygon as you need to: poly.addPoint(20,35); Then, to draw the polygon, just use the polygon object as an argument to drawPolygon() or fillPolygon(). Here's that previous example, rewritten this time with a Polygon object. You'll also fill this polygon rather than just drawing its outline (Figure 9.8 shows the output): Figure 9.8 : Another polygon. import java.awt.Graphics; public class MyPoly2 extends java.applet.Applet { public void paint(Graphics g) { int exes = { 39,94,97,142,53,58,26 }; int whys = { 33,74,36,70,108,80,106 }; int pts = exes.length; Polygon poly = new Polygon(exes,whys,pts); g.fillPolygon(poly); } } Ovals You use ovals to draw ellipses or circles. Ovals are just like rectangles with overly rounded corners. You draw them using four arguments: the x and y of the top corner, and the width and height of the oval itself. Note that because you're drawing an oval, the starting point is some distance to the left and up from the actual outline of the oval itself. Again, if you think of it as a rectangle, it's easier to place. As with the other drawing operations, the drawOval() method draws an outline of an oval, and the fillOval() method draws a filled oval. The following example draws two ovals-a circle and an ellipse (Figure 9.9 shows how these two ovals appear onscreen): Figure 9.9 : Ovals. import java.awt.Graphics; public class MyOval extends java.applet.Applet { public void paint(Graphics g) { g.drawOval(20,20,60,60); g.fillOval(120,20,100,60); file:///G|/ebooks/1575211831/ch9.htm (6 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color } } Arcs Of all the shapes you can construct using methods in the Graphics class, arcs are the most complex to construct, which is why I saved them for last. An arc is a part of an oval; in fact, the easiest way to think of an arc is as a section of a complete oval. Figure 9.10 shows some arcs. Figure 9.10: Arcs. The drawArc() method takes six arguments: the starting corner, the width and height, the angle at which to start the arc, and the degrees to draw it before stopping. Once again, there is a drawArc method to draw the arc's outline and the fillArc() method to fill the arc. Filled arcs are drawn as if they were sections of a pie; instead of joining the two endpoints, both endpoints are joined to the center of the circle. The important thing to understand about arcs is that you're actually formulating the arc as an oval and then drawing only some of that. The starting corner and width and height are not the starting point and width and height of the actual arc as drawn on the screen; they're the width and height of the full ellipse of which the arc is a part. Those first points determine the size and shape of the arc; the last two arguments (for the degrees) determine the starting and ending points. Let's start with a simple arc, a C shape on a circle, as shown in Figure 9.11. Figure 9.11: A C arc. To construct the method to draw this arc, the first thing you do is think of it as a complete circle. Then you find the x and y coordinates and the width and height of that circle. Those four values are the first four arguments to the drawArc() or fillArc() methods. Figure 9.12 shows how to get those values from the arc. Figure 9.12: Constructing a circular arc. To get the last two arguments, think in degrees around the circle, going counterclockwise. Zero degrees is at 3 o'clock, 90 degrees is at 12 o'clock, 180 at 9 o'clock, and 270 at 6 o'clock. The start of the arc is the degree value of the start of the arc. In this example, the starting point is the top of the C at 90 degrees; 90 is the fifth argument. The sixth and last argument is another degree value indicating how far around the circle to sweep and the direction to go in (it's not the ending degree angle, as you might think). In this case, because you're going halfway around the circle, you're sweeping 180 degrees-and 180 is therefore the last argument in the arc. The important part is that you're sweeping 180 degrees counterclockwise, which is in the positive direction in Java. If you are drawing a backwards C, you sweep 180 degrees in the negative direction, and the last argument is -180. See Figure 9.13 for the final illustration of how this works. Figure 9.13: Arcs on circles. Note It doesn't matter which side of the arc you start with. Because the shape of the arc has already been determined by the complete oval it's a section of, starting at either endpoint will work. Here's the code for this example; you'll draw an outline of the C and a filled C to its right, as shown in Figure 9.14: Figure 9.14: Two circular arcs. file:///G|/ebooks/1575211831/ch9.htm (7 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color import java.awt.Graphics; public class MyOval extends java.applet.Applet { public void paint(Graphics g) { g.drawArc(20,20,60,60,90,180); g.fillArc(120,20,60,60,90,180); } } Circles are an easy way to visualize arcs on circles; arcs on ellipses are slightly more difficult. Let's go through this same process to draw the arc shown in Figure 9.15. Figure 9.15: An elliptical arc. Like the arc on the circle, this arc is a piece of a complete oval, in this case, an elliptical oval. By completing the oval that this arc is a part of, you can get the starting points and the width and height arguments for the drawArc() or fillArc() method (see Figure 9.16). Figure 9.16: Arcs on ellipses. Then all you need is to figure out the starting angle and the angle to sweep. This arc doesn't start on a nice boundary such as 90 or 180 degrees, so you'll need some trial and error. This arc starts somewhere around 25 degrees, and then sweeps clockwise about 130 degrees (see Figure 9.17). Figure 9.17: Starting and ending points. With all portions of the arc in place, you can write the code. Here's the Java code for this arc, both drawn and filled (note in the filled case how filled arcs are drawn as if they were pie sections): import java.awt.Graphics; public class MyOval extends java.applet.Applet { public void paint(Graphics g) { g.drawArc(10,20,150,50,25,-130); g.fillArc(10,80,150,50,25,-130); } } Figure 9.18 shows the two elliptical arcs. Figure 9.18: Two elliptical arcs. To summarize, here are the steps to take to construct arcs in Java: 1. Think of the arc as a slice of a complete oval. 2. Construct the full oval with the starting point and the width and height (it often helps to draw the full oval on the screen to get an idea of the right positioning). 3. Determine the starting angle for the beginning of the arc. 4. Determine how far to sweep the arc and in which direction (counterclockwise indicates positive values, clockwise indicates negative). file:///G|/ebooks/1575211831/ch9.htm (8 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color A Simple Graphics Example Here's an example of an applet that uses many of the built-in graphics primitives to draw a rudimentary shape. In this case, it's a lamp with a spotted shade (or a sort of cubist mushroom, depending on your point of view). Listing 9.1 has the complete code for the lamp; Figure 9.19 shows the resulting applet. Figure 9.19: The Lamp applet. Listing 9.1. The Lamp class. 1: import java.awt.*; 2: 3: public class Lamp extends java.applet.Applet { 4: 5: public void paint(Graphics g) { 6: // the lamp platform 7: g.fillRect(0,250,290,290); 8: 9: // the base of the lamp 10: g.drawLine(125,250,125,160); 11: g.drawLine(175,250,175,160); 12: 13: // the lamp shade, top and bottom edges 14: g.drawArc(85,157,130,50,-65,312); 15: g.drawArc(85,87,130,50,62,58); 16: 17: // lamp shade, sides 18: g.drawLine(85,177,119,89); 19: g.drawLine(215,177,181,89); 20: 21: // dots on the shade 22: g.fillArc(78,120,40,40,63,-174); 23: g.fillOval(120,96,40,40); 24: g.fillArc(173,100,40,40,110,180); 25: } 26: } Copying and Clearing Once you've drawn a few things on the screen, you may want to move them around or clear the entire applet. The Graphics class provides methods for doing both these things. The copyArea() method copies a rectangular area of the screen to another area of the screen. copyArea() takes six arguments: the x and y of the top corner of the rectangle to copy, the width and the height of that rectangle, and the distance in the x and y directions to which to copy it. For example, this line copies a square area 100 pixels on a side 100 pixels directly to its right: g.copyArea(0,0,100,100,100,0); To clear a rectangular area, use the clearRect() method. clearRect(), which takes the same four arguments file:///G|/ebooks/1575211831/ch9.htm (9 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color as the drawRect() and fillRect() methods, fills the given rectangle with the current background color of the applet (you'll learn how to set the current background color later today). To clear the entire applet, you can use the size() method, which returns a Dimension object representing the width and height of the applet. You can then get to the actual values for width and height by using the width and height instance variables: g.clearRect(0,0,size().width,size().height); Text and Fonts Using the Graphics class, you can also print text on the screen, in conjunction with the Font class (and, sometimes, the FontMetrics class). The Font class represents a given font-its name, style, and point size-and FontMetrics gives you information about that font (for example, the actual height or width of a given character) so that you can precisely lay out text in your applet. Note that the text here is drawn to the screen once and intended to stay there. You'll learn about entering text from the keyboard later this week. Creating Font Objects To draw text to the screen, first you need to create an instance of the Font class. Font objects represent an individual font-that is, its name, style (bold, italic), and point size. Font names are strings representing the family of the font, for example, "TimesRoman", "Courier", or "Helvetica". Font styles are constants defined by the Font class; you can get to them using class variables-for example, Font.PLAIN, Font.BOLD, or Font.ITALIC. Finally, the point size is the size of the font, as defined by the font itself; the point size may or may not be the height of the characters. To create an individual font object, use these three arguments to the Font class's new constructor: Font f = new Font("TimesRoman", Font.BOLD, 24); This example creates a font object for the TimesRoman BOLD font, in 24 points. Note that like most Java classes, you have to import the java.awt.Font class before you can use it. Tip Font styles are actually integer constants that can be added to create combined styles; for example, Font.BOLD + Font.ITALIC produces a font that is both bold and italic. The fonts you have available to you in your applets depend on which fonts are installed on the system where the applet is running. If you pick a font for your applet and that font isn't available on the current system, Java will substitute a default font (usually Courier). You can get an array of the names of the current fonts available in the system using this bit of code: String fontslist = this.getToolkit().getFontList(); From this list, you can then often intelligently decide which fonts you want to use in your applet. For best results, however, it's a good idea to stick with standard fonts such as "TimesRoman", "Helvetica", and "Courier". file:///G|/ebooks/1575211831/ch9.htm (10 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color Drawing Characters and Strings With a font object in hand, you can draw text on the screen using the methods drawChars() and drawString(). First, though, you need to set the current font to your font object using the setFont() method. The current font is part of the graphics state that is kept track of by the Graphics object on which you're drawing. Each time you draw a character or a string to the screen, Java draws that text in the current font. To change the font of the text, therefore, first change the current font. The following paint() method creates a new font, sets the current font to that font, and draws the string "This is a big font.", at the point 10,100: public void paint(Graphics g) { Font f = new Font("TimesRoman", Font.PLAIN, 72); g.setFont(f); g.drawString("This is a big font.", 10, 100); } This should all look familiar to you; this is how the Hello World and Hello Again applets throughout this book were produced. The latter two arguments to drawString() determine the point where the string will start. The x value is the start of the leftmost edge of the text; y is the baseline for the entire string. Similar to drawString() is the drawChars() method that, instead of taking a string as an argument, takes an array of characters. drawChars() has five arguments: the array of characters, an integer representing the first character in the array to draw, another integer for the last character in the array to draw (all characters between the first and last are drawn), and the x and y for the starting point. Most of the time, drawString() is more useful than drawChars(). Listing 9.2 shows an applet that draws several lines of text in different fonts; Figure 9.20 shows the result. Figure 9.20: The output of the ManyFonts applet. Listing 9.2. Many different fonts. 1: import java.awt.Font; 2: import java.awt.Graphics; 3: 4: public class ManyFonts extends java.applet.Applet { 5: 6: public void paint(Graphics g) { 7: Font f = new Font("TimesRoman", Font.PLAIN, 18); 8: Font fb = new Font("TimesRoman", Font.BOLD, 18); 9: Font fi = new Font("TimesRoman", Font.ITALIC, 18); 10: Font fbi = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 18); 11: 12: g.setFont(f); 13: g.drawString("This is a plain font", 10, 25); 14: g.setFont(fb); 15: g.drawString("This is a bold font", 10, 50); 16: g.setFont(fi); 17: g.drawString("This is an italic font", 10, 75); 18: g.setFont(fbi); file:///G|/ebooks/1575211831/ch9.htm (11 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color 19: 20: 21: 22: } g.drawString("This is a bold italic font", 10, 100); } Finding Out Information About a Font Sometimes you may want to make decisions in your Java program based on the qualities of the current font-for example, its point size and the total height of its characters. You can find out some basic information about fonts and font objects by using simple methods on Graphics and on the Font objects. Table 9.1 shows some of these methods. Table 9.1. Font methods. Method Name getFont() In Object Graphics getName() getSize() getStyle() Font Font Font isPlain() Font isBold() Font isItalic() Font Action Returns the current font object as previously set by setFont() Returns the name of the font as a string Returns the current font size (an integer) Returns the current style of the font (styles are integer constants: 0 is plain, 1 is bold, 2 is italic, 3 is bold italic) Returns true or false if the font's style is plain Returns true or false if the font's style is bold Returns true or false if the font's style is italic For more detailed information about the qualities of the current font (for example, the length or height of given characters), you need to work with font metrics. The FontMetrics class describes information specific to a given font: the leading between lines, the height and width of each character, and so on. To work with these sorts of values, you create a FontMetrics object based on the current font by using the applet method getFontMetrics(): Font f = new Font("TimesRoman", Font.BOLD, 36); FontMetrics fmetrics = getFontMetrics(f); g.setfont(f); Table 9.2 shows some of the things you can find out using font metrics. All these methods should be called on a FontMetrics object. Table 9.2. Font metrics methods. Method Name Action stringWidth(string) Given a string, returns the full width of that string, in pixels Given a character, returns the width of that character charWidth(char) Returns the ascent of the font, that is, the distance getAscent() between the font's baseline and the top of the characters file:///G|/ebooks/1575211831/ch9.htm (12 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color getDescent() getLeading() getHeight() Returns the descent of the font-that is, the distance between the font's baseline and the bottoms of the characters (for characters such as p and q that drop below the baseline) Returns the leading for the font, that is, the spacing between the descent of one line and the ascent of another line Returns the total height of the font, which is the sum of the ascent, descent, and leading value As an example of the sorts of information you can use with font metrics, Listing 9.3 shows the Java code for an applet that automatically centers a string horizontally and vertically inside an applet. The centering position is different depending on the font and font size; by using font metrics to find out the actual size of a string, you can draw the string in the appropriate place. Figure 9.21 shows the result (which is less interesting than if you actually compile and experiment with various applet and font sizes). Figure 9.21: The centered text. Listing 9.3. Centering a string. 1: import java.awt.Font; 2: import java.awt.Graphics; 3: import java.awt.FontMetrics; 4: 5: public class Centered extends java.applet.Applet { 6: 7: public void paint(Graphics g) { 8: Font f = new Font("TimesRoman", Font.PLAIN, 36); 9: FontMetrics fm = getFontMetrics(f); 10: g.setFont(f); 11: 12: String s = "This is how the world ends."; 13: int xstart = (size().width - fm.stringWidth(s)) / 2; 14: int ystart = size().height / 2; 15: 16: g.drawString(s, xstart, ystart); 17: } 18:} Analysis Note the size() method in lines 13 and 14, which returns the width and height of the overall applet area as a Dimension object. You can then get to the individual width and height using the width and height instance variables of that Dimension, here by chaining the method call and the variable name. Getting the current applet size in this way is a better idea than hard coding the size of the applet into your code; this code works equally well with an applet of any size. file:///G|/ebooks/1575211831/ch9.htm (13 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color Note also that the line of text, as shown in Figure 9.21, isn't precisely vertically centered in the applet bounding box. This example centers the baseline of the text inside the applet; using the getAscent() and getDescent() methods from the FontMetrics class (to get the number of pixels from the baseline to the top of the characters and the number of pixels from the baseline to the bottom of the characters), you can figure out exactly the middle of the line of text. Color Drawing black lines and text on a gray background is all very nice, but being able to use different colors is much nicer. Java provides methods and behaviors for dealing with color in general through the Color class, and also provides methods for setting the current foreground and background colors so that you can draw with the colors you created. Java's abstract color model uses 24-bit color, wherein a color is represented as a combination of red, green, and blue values. Each component of the color can have a number between 0 and 255. 0,0,0 is black, 255,255,255 is white, and Java can represent millions of colors between as well. Java's abstract color model maps onto the color model of the platform Java is running on, which usually has only 256 or fewer colors from which to choose. If a requested color in a color object is not available for display, the resulting color may be mapped to another or dithered, depending on how the browser viewing the color implemented it, and depending on the platform on which you're running. In other words, although Java gives the capability of managing millions of colors, very few may actually be available to you in real life. Using Color Objects To draw an object in a particular color, you must create an instance of the Color class to represent that color. The Color class defines a set of standard color objects, stored in class variables, to quickly get a color object for some of the more popular colors. For example, Color.red returns a Color object representing red (RGB values of 255, 0, and 0), Color.white returns a white color (RGB values of 255, 255, and 255), and so on. Table 9.3 shows the standard colors defined by variables in the Color class. Table 9.3. Standard colors. Color Name Color.white Color.black Color.lightGray Color.gray Color.darkGray Color.red Color.green Color.blue Color.yellow Color.magenta Color.cyan Color.pink Color.orange RGB Value 255,255,255 0,0,0 192,192,192 128,128,128 64,64,64 255,0,0 0,255,0 0,0,255 255,255,0 255,0,255 0,255,255 255,175,175 255,200,0 If the color you want to draw in is not one of the standard Color objects, fear not. You can create a color object for file:///G|/ebooks/1575211831/ch9.htm (14 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color any combination of red, green, and blue, as long as you have the values of the color you want. Just create a new color object: Color c = new Color(140,140,140); This line of Java code creates a color object representing a dark gray. You can use any combination of red, green, and blue values to construct a color object. Alternatively, you can create a color object using three floats from 0.0 to 1.0: Color c = new Color(0.55,0.55,0.55); Testing and Setting the Current Colors To draw an object or text using a color object, you have to set the current color to be that color object, just as you have to set the current font to the font in which you want to draw. Use the setColor() method (a method for Graphics objects) to do this: g.setColor(Color.green); After you set the current color, all drawing operations will occur in that color. In addition to setting the current color for the graphics context, you can also set the background and foreground colors for the applet itself by using the setBackground() and setForeground() methods. Both of these methods are defined in the java.awt.Component class, which Applet-and therefore your classes-automatically inherits. The setBackground() method sets the background color of the applet, which is usually a light gray (to match the default background of the browser). It takes a single argument, a Color object: setBackground(Color.white); The setForeground() method also takes a single color as an argument, and it affects everything that has been drawn on the applet, regardless of the color in which it has been drawn. You can use setForeground() to change the color of everything in the applet at once, rather than having to redraw everything: setForeground(Color.black); In addition to the setColor(), setForeground(), and setBackground() methods, there are corresponding get methods that enable you to retrieve the current graphics color, background, or foreground. Those methods are getColor() (defined in Graphics objects), getForeground() (defined in Applet), and getBackground() (also in Applet). You can use these methods to choose colors based on existing colors in the applet: setForeground(g.getColor()); A Simple Color Example Listing 9.4 shows the code for an applet that fills the applet's drawing area with square boxes, each of which has a randomly chosen color in it. It's written so that it can handle any size of applet and automatically fill the area with the right number of boxes. file:///G|/ebooks/1575211831/ch9.htm (15 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color Listing 9.4. Random color boxes. 1: import java.awt.Graphics; 2: import java.awt.Color; 3: 4: public class ColorBoxes extends java.applet.Applet { 5: 6: public void paint(Graphics g) { 7: int rval, gval, bval; 8: 9: for (int j = 30; j < (size().height -25); j += 30) 10: for (int i = 5; i < (size().width -25); i += 30) { 11: rval = (int)Math.floor(Math.random() * 256); 12: gval = (int)Math.floor(Math.random() * 256); 13: bval = (int)Math.floor(Math.random() * 256); 14: 15: g.setColor(new Color(rval,gval,bval)); 16: g.fillRect(i, j, 25, 25); 17: g.setColor(Color.black); 18: g.drawRect(i-1, j-1, 25, 25); 19: } 20: } 21: } Analysis The two for loops are the heart of this example; the first one draws the rows, and the second draws the individual boxes within each row. When a box is drawn, the random color is calculated first, and then the box is drawn. A black outline is drawn around each box, because some of them tend to blend into the background of the applet. Because this paint method generates new colors each time the applet is painted, you can regenerate the colors by moving the window around or by covering the applet's window with another one (or by reloading the page). Figure 9.22 shows the final applet (although given that this picture is black and white, you can't get the full effect of the multicolored squares). Figure 9.22: The random colors applet. Summary You present something on the screen by painting inside your applet: shapes, graphics, text, or images. Today you have learned the basics of how to paint, including how to use the graphics primitives to draw rudimentary shapes, how to use fonts and font metrics to draw text, and how to use Color objects to change the color of what you're drawing on the screen. It's this foundation in painting that enables you to do animation inside an applet (which basically involves just painting repeatedly to the screen) and to work with images. These are topics you'll learn about tomorrow. file:///G|/ebooks/1575211831/ch9.htm (16 of 17) [11/06/2000 7:45:20 PM] Day 9 -- Graphics, Fonts, and Color Q&A Q: A: Q: A: Q: A: Q: A: Q: A: In all the examples you show, and in all the tests I've made, the graphics primitives, such as drawLine() and drawRect(), produce lines that are one pixel wide. How can I draw thicker lines? In the current state of the Java Graphics class, you can't; no methods exist for changing the default line width. If you really need a thicker line, you have to draw multiple lines one pixel apart to produce that effect. I want to draw a line of text with a boldface word in the middle. I understand that I need two font objects-one for the regular font and one for the bold one-and that I'll need to reset the current font in between. The problem is that drawString() requires an x and a y position for the start of each string, and can't find anything that refers to "current point." How can I figure out where to start the boldface word? Java's text display capabilities are fairly primitive. There is no concept of the current point, so you'll have to manually figure out where the end of one string was so that you can begin the next string. The stringWidth() methods can help you with that, both to find out the width of the string you just drew and to add the space after it. How do I use non-roman fonts such as kanji in Java? Java's support for international fonts in the 1.0.2 version of the JDK is sketchy, beyond the encoding of the raw characters as Unicode. Your best bet is to wait for the 1.1 version of the JDK, which will offer much more flexibility in the way of Unicode character display, support for internationalization, and non-roman fonts. I tried out the applet that draws boxes with random colors, but each time it draws, a lot of the boxes are the same color. If the colors are truly random, why is it doing this? Two reasons. The first is that the random number generator I used in that code (from the Math class) isn't a very good random number generator; in fact, the documentation for that method says as much. For a better random number generator, use the Random class from the java.util package. The second, more likely, reason is that there just aren't enough colors available in your browser or on your system to draw all the colors that the applet is generating. If your system can't produce the wide range of colors available using the Color class, or if the browser has allocated too many colors for other things, you may end up with duplicate colors in the boxes, depending on how the browser and the system have been written to handle that. Usually your applet won't use quite so many colors, so you won't run into this problem quite so often. I have a tiled background on my Web page. I can create images with transparent backgrounds so that the tiled page background shows through. Can I create transparent applets? Not with the 1.02 JDK (and perhaps not with 1.1 either). For applets, your best bet is to use a plain-colored background and set your applet's background to be that same color. Another idea if you use a tile for the page background is to import that image and draw it as the background for your applet (you'll learn about images tomorrow). However, using that mechanism, it is unlikely that the edges of the tile will exactly match up. Unfortunately, there doesn't appear to be a good workaround for this problem. file:///G|/ebooks/1575211831/ch9.htm (17 of 17) [11/06/2000 7:45:20 PM] file:///G|/ebooks/1575211831/f9-1.gif file:///G|/ebooks/1575211831/f9-1.gif [11/06/2000 7:45:21 PM] file:///G|/ebooks/1575211831/f9-2.gif file:///G|/ebooks/1575211831/f9-2.gif [11/06/2000 7:45:21 PM] file:///G|/ebooks/1575211831/f9-3.gif file:///G|/ebooks/1575211831/f9-3.gif [11/06/2000 7:45:21 PM] file:///G|/ebooks/1575211831/f9-4.gif file:///G|/ebooks/1575211831/f9-4.gif [11/06/2000 7:45:22 PM] file:///G|/ebooks/1575211831/f9-5.gif file:///G|/ebooks/1575211831/f9-5.gif [11/06/2000 7:45:22 PM] file:///G|/ebooks/1575211831/f9-6.gif file:///G|/ebooks/1575211831/f9-6.gif [11/06/2000 7:45:22 PM] file:///G|/ebooks/1575211831/f9-7.gif file:///G|/ebooks/1575211831/f9-7.gif [11/06/2000 7:45:23 PM] file:///G|/ebooks/1575211831/f9-8.gif file:///G|/ebooks/1575211831/f9-8.gif [11/06/2000 7:45:23 PM] file:///G|/ebooks/1575211831/f9-9.gif file:///G|/ebooks/1575211831/f9-9.gif [11/06/2000 7:45:23 PM] file:///G|/ebooks/1575211831/f9-10.gif file:///G|/ebooks/1575211831/f9-10.gif [11/06/2000 7:45:24 PM] file:///G|/ebooks/1575211831/f9-11.gif file:///G|/ebooks/1575211831/f9-11.gif [11/06/2000 7:45:24 PM] file:///G|/ebooks/1575211831/f9-12.gif file:///G|/ebooks/1575211831/f9-12.gif [11/06/2000 7:45:24 PM] file:///G|/ebooks/1575211831/f9-13.gif file:///G|/ebooks/1575211831/f9-13.gif [11/06/2000 7:45:25 PM] file:///G|/ebooks/1575211831/f9-14.gif file:///G|/ebooks/1575211831/f9-14.gif [11/06/2000 7:45:25 PM] file:///G|/ebooks/1575211831/f9-15.gif file:///G|/ebooks/1575211831/f9-15.gif [11/06/2000 7:45:25 PM] file:///G|/ebooks/1575211831/f9-16.gif file:///G|/ebooks/1575211831/f9-16.gif [11/06/2000 7:45:25 PM] file:///G|/ebooks/1575211831/f9-17.gif file:///G|/ebooks/1575211831/f9-17.gif [11/06/2000 7:45:25 PM] file:///G|/ebooks/1575211831/f9-18.gif file:///G|/ebooks/1575211831/f9-18.gif [11/06/2000 7:45:26 PM] file:///G|/ebooks/1575211831/f9-19.gif file:///G|/ebooks/1575211831/f9-19.gif [11/06/2000 7:45:26 PM] file:///G|/ebooks/1575211831/f9-20.gif file:///G|/ebooks/1575211831/f9-20.gif [11/06/2000 7:45:27 PM] file:///G|/ebooks/1575211831/f9-21.gif file:///G|/ebooks/1575211831/f9-21.gif [11/06/2000 7:45:27 PM] file:///G|/ebooks/1575211831/f9-22.gif file:///G|/ebooks/1575211831/f9-22.gif [11/06/2000 7:45:27 PM] Day 8 -- Java Applet Basics Day 8 Java Applet Basics by Laura Lemay CONTENTS q How Applets and Applications Are Different q Creating Applets r r q Major Applet Activities A Simple Applet Including an Applet on a Web Page r r Testing the Result r q The <APPLET> Tag Making Java Applets Available to the Web More About the <APPLET> Tag r ALIGN r HSPACE and VSPACE r CODE and CODEBASE q Java Archives q Passing Parameters to Applets q Summary q Q&A Much of Java's current popularity has come about because of Java-enabled World Wide Web browsers and their support for applets-Java programs that run on Web pages and can be used to create dynamic, interactive Web sites. Applets, as noted at the beginning of this book, are written in the Java language, and can be viewed in any browser that supports Java, including Netscape's Navigator and Microsoft's Internet Explorer. Learning how to create applets is most likely the reason you bought this book, so let's waste no more time. Last week, you focused on learning about the Java language itself, and most of the little programs you created were Java applications. This week, now that you have the basics down, you'll move on to creating and using applets, which includes a discussion of many of the classes in the standard Java class library. Today you'll start with the basics: q A small review of differences between Java applets and applications q Getting started with applets: the basics of how an applet works and how to create your own simple applets q Including an applet on a Web page by using the <APPLET> tag, including the various features of that tag q Passing parameters to applets file:///G|/ebooks/1575211831/ch8.htm (1 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics How Applets and Applications Are Different Although you explored the differences between Java applications and Java applets in the early part of this book, let's review them. In short, Java applications are standalone Java programs that can be run by using just the Java interpreter, for example, from a command line. Most everything you've used up to this point in the book has been a Java application, albeit a simple one. Java applets, however, are run from inside a World Wide Web browser. A reference to an applet is embedded in a Web page using a special HTML tag. When a reader, using a Java-enabled browser, loads a Web page with an applet in it, the browser downloads that applet from a Web server and executes it on the local system (the one the browser is running on). (The Java interpreter is built into the browser and runs the compiled Java class file from there.) Because Java applets run inside a Java browser, they have access to the structure the browser provides: an existing window, an event-handling and graphics context, and the surrounding user interface. Java applications can also create this structure (allowing you to create graphical applications), but they don't require it (you'll learn how to create Java applications that use applet-like graphics and user interface (UI) features on Day 14, "Windows, Networking, and Other Tidbits"). Note that a single Java program can be written to operate as both a Java application and a Java applet. While you use different procedures and rules to create applets and applications, none of those procedures or rules conflict with each other. The features specific to applets are ignored when the program runs as an application, and vice versa. Keep this in mind as you design your own applets and applications. One final significant difference between Java applets and applications-probably the biggest difference-is the set of restrictions placed on how applets can operate in the name of security. Given the fact that Java applets can be downloaded from any site on the World Wide Web and run on a client's system, Java-enabled browsers and tools limit what can be done to prevent a rogue applet from causing system damage or security breaches. Without these restrictions in place, Java applets could be written to contain viruses or trojan horses (programs that seem friendly but do some sort of damage to the system), or be used to compromise the security of the system that runs them. The restrictions on applets include the following: q Applets can't read or write to the reader's file system, which means they cannot delete files or test to see what programs you have installed on the hard drive. q Applets can't communicate with any network server other than the one that had originally stored the applet, to prevent the applet from attacking another system from the reader's system. q Applets can't run any programs on the reader's system. For UNIX systems, this includes forking a process. q Applets can't load programs native to the local platform, including shared libraries such as DLLs. All these rules are true for Java applets running Netscape Navigator or Microsoft Internet Explorer. Other Java-enabled browsers or tools may allow you to configure the level of security you want-for example, the appletviewer tool in the JDK allows you to set an access control list for which directories an applet can read or write. However, as an applet developer, it's safe to assume that most of your audience is going to be viewing your applets in a browser that implements the strictest rules for what an applet can do. Java applications have none of these restrictions. Note The security restrictions imposed on applets are sometimes called "the sandbox" (as in applets are only allowed to play in the sandbox and can go no further). Work is being done by Sun and by the Java community to find ways for applets to be able to break out of the sandbox, including digital signatures and encryption. On Day 21, "Under the Hood," you'll learn more details on Java and applet security. file:///G|/ebooks/1575211831/ch8.htm (2 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics In addition to the applet restrictions listed, Java itself includes various forms of security and consistency checking in the Java compiler and interpreter for all Java programs to prevent unorthodox use of the language (you'll learn more about this on Day 21). This combination of restrictions and security features makes it more difficult for a rogue Java applet to do damage to the client's system. Note These restrictions prevent all of the traditional ways of causing damage to a client's system, but it's impossible to be absolutely sure that a clever programmer cannot somehow work around these restrictions, violate privacy, use CPU resources, or just plain be annoying. Sun has asked the Net at large to try to break Java's security and to create an applet that can work around the restrictions imposed on it, and, in fact, several problems have been unearthed and fixed, usually relating to loading classes and to connecting to unauthorized sites. You'll learn about more issues in Java security on Day 21. Creating Applets For the most part, all the Java programs you've created up to this point have been Java applications-simple programs with a single main() method that create objects, set instance variables, and run methods. Today and in the next few days you'll be creating applets exclusively, so you will need a good grasp of how an applet works, the sorts of features an applet has, and where to start when you first create your own applets. To create an applet, you create a subclass of the class Applet. The Applet class, part of the java.applet package, provides much of the behavior your applet needs to work inside a Java-enabled browser. Applets also take strong advantage of Java's Abstract Windowing Toolkit (awt), which provides behavior for creating graphical user interface (GUI)-based applets and applications: drawing to the screen; creating windows, menu bars, buttons, check boxes, and other UI elements; and managing user input such as mouse clicks and keypresses. The awt classes are part of the java.awt package. New Term Java's Abstract Windowing Toolkit (awt) provides classes and behavior for creating GUI-based applications in Java. Applets make use of many of the capabilities in the awt. Although your applet can have as many additional "helper" classes as it needs, it's the main applet class that triggers the execution of the applet. That initial applet class always has a signature like this: public class myClass extends java.applet.Applet { ... } Note the public keyword. Java requires that your applet subclass be declared public. Again, this is true only of your main applet class; any helper classes you create do not necessarily need to be public. public, private, and other forms of access control are described on Day 15, "Modifiers, Access Control, and Class Design." When a Java-enabled browser encounters your applet in a Web page, it loads your initial applet class over the network, as well as any other helper classes that first class uses, and runs the applet using the browser's built-in bytecode interpreter. Unlike with applications, where Java calls the main() method directly on your initial class, when your applet is loaded, Java creates an instance of the applet class, and a series of special applet methods are called on that instance. Different applets that use the same class use different instances, so each one can behave differently from the other applets running in the same browser. file:///G|/ebooks/1575211831/ch8.htm (3 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics Major Applet Activities To create a basic Java application, your class has to have one method, main(), with a specific signature. Then, when your application runs, main() is found and executed, and from main() you can set up the behavior that your program needs to run. Applets are similar but more complicated-and, in fact, applets don't need a main() method at all. Applets have many different activities that correspond to various major events in the life cycle of the applet-for example, initialization, painting, and mouse events. Each activity has a corresponding method, so when an event occurs, the browser or other Java-enabled tool calls those specific methods. The default implementations of these activity methods do nothing; to provide behavior for an event you must override the appropriate method in your applet's subclass. You don't have to override all of them, of course; different applet behavior requires different methods to be overridden. You'll learn about the various important methods to override as the week progresses, but, for a general overview, here are five of the most important methods in an applet's execution: initialization, starting, stopping, destroying, and painting. Initialization Initialization occurs when the applet is first loaded (or reloaded), similarly to the main() method in applications. The initialization of an applet might include reading and parsing any parameters to the applet, creating any helper objects it needs, setting up an initial state, or loading images or fonts. To provide behavior for the initialization of your applet, override the init() method in your applet class: public void init() { ... } Starting After an applet is initialized, it is started. Starting is different from initialization because it can happen many different times during an applet's lifetime, whereas initialization happens only once. Starting can also occur if the applet was previously stopped. For example, an applet is stopped if the reader follows a link to a different page, and it is started again when the reader returns to this page. To provide startup behavior for your applet, override the start() method: public void start() { ... } Functionality that you put in the start() method might include creating and starting up a thread to control the applet, sending the appropriate messages to helper objects, or in some way telling the applet to begin running. You'll learn more about starting applets on Day 10, "Simple Animation and Threads." Stopping Stopping and starting go hand in hand. Stopping occurs when the reader leaves the page that contains a currently running applet, or you can stop the applet yourself by calling stop(). By default, when the reader leaves a page, any threads the applet had started will continue running. You'll learn more about threads on Day 10. By overriding stop(), you can suspend execution of these threads and then restart them if the applet is viewed again: public void stop() { ... } file:///G|/ebooks/1575211831/ch8.htm (4 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics Destroying Destroying sounds more violent than it is. Destroying enables the applet to clean up after itself just before it is freed or the browser exits-for example, to stop and remove any running threads, close any open network connections, or release any other running objects. Generally, you won't want to override destroy() unless you have specific resources that need to be released-for example, threads that the applet has created. To provide clean-up behavior for your applet, override the destroy() method: public void destroy() { ... } Technical Note How is destroy() different from finalize(), which was described on Day 7, "More About Methods"? First, destroy() applies only to applets. finalize() is a more general-purpose way for a single object of any type to clean up after itself. Painting Painting is how an applet actually draws something on the screen, be it text, a line, a colored background, or an image. Painting can occur many thousands of times during an applet's life cycle (for example, after the applet is initialized, if the browser is placed behind another window on the screen and then brought forward again, if the browser window is moved to a different position on the screen, or perhaps repeatedly, in the case of animation). You override the paint() method if your applet needs to have an actual appearance on the screen (that is, most of the time). The paint() method looks like this: public void paint(Graphics g) { ... } Note that unlike the other major methods in this section, paint() takes an argument, an instance of the class Graphics. This object is created and passed to paint by the browser, so you don't have to worry about it. However, you will have to make sure that the Graphics class (part of the java.awt package) gets imported into your applet code, usually through an import statement at the top of your Java file: import java.awt.Graphics; A Simple Applet Way back on Day 2, "Object-Oriented Programming and Java," you created a simple applet called HelloAgainApplet (this was the one with the big red Hello Again). There, you created and used that applet as an example of creating a subclass. Let's go over the code for that applet again, this time looking at it slightly differently in light of the things you just learned about applets. Listing 8.1 shows the code for that applet. Listing 8.1. The Hello Again applet. 1: 2: 3: 4: import java.awt.Graphics; import java.awt.Font; import java.awt.Color; file:///G|/ebooks/1575211831/ch8.htm (5 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics 5: public class HelloAgainApplet extends java.applet.Applet { 6: 7: Font f = new Font("TimesRoman", Font.BOLD, 36); 8: 9: public void paint(Graphics g) { 10: g.setFont(f); 11: g.setColor(Color.red); 12: g.drawString("Hello again!", 5, 40); 13: } 14: } Analysis This applet implements the paint() method, one of the major methods described in the previous section (actually, it overrides the default implementation of paint(), which does nothing). Because the applet doesn't actually do much (all it does is print a couple words to the screen), and there's not really anything to initialize, you don't need a start(), stop(), init(), or destroy() method. The paint method is where the real work of this applet (what little work goes on) really occurs. The Graphics object passed into the paint() method holds the graphics state for the applet-that is, the current features of the drawing surface, such as foreground and background colors or clipping area. Lines 10 and 11 set up the font and color for this graphics state (here, the font object held in the f instance variable, and a Color object representing the color red). Line 12 draws the string "Hello Again!" by using the current font and color at the position 5, 40. Note that the 0 point for x, y is at the top left of the applet's drawing surface, with positive y moving downward, so 50 is actually at the bottom of the applet. Figure 8.1 shows how the applet's bounding box and the string are drawn on the page. Figure 8.1 : Drawing the applet. If you've been following along with all the examples up to this point, you might notice that there appears to be something missing in this class: a main() method. As mentioned in the section on the differences between applets and applications, applets don't need a main() method. By implementing the right applet methods in your class (init(), start(), stop(), paint(), and so on), your applet just seamlessly works without needing an explicit jumping-off point. Including an Applet on a Web Page After you create a class or classes that contain your applet and compile them into class files as you would any other Java program, you have to create a Web page that will hold that applet by using the HTML language. There is a special HTML tag for including applets in Web pages; Java-enabled browsers use the information contained in that tag to locate the compiled class files and execute the applet itself. In this section, you'll learn about how to put Java applets in a Web page and how to serve those files to the Web at large. Note The following section assumes that you have at least a passing understanding of writing HTML pages. If you need help in this area, you may find the book Teach Yourself Web Publishing with HTML in 14 Days useful. It is also from Sams.net and also by Laura Lemay, the author of much of this book. file:///G|/ebooks/1575211831/ch8.htm (6 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics The <APPLET> Tag To include an applet on a Web page, use the <APPLET> tag. <APPLET> is a special extension to HTML for including applets in Web pages. Listing 8.2 shows a very simple example of a Web page with an applet included in it. Listing 8.2. A simple HTML page. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: <HTML> <HEAD> <TITLE>This page has an applet on it</TITLE> </HEAD> <BODY> <P>My second Java applet says: <BR><APPLET CODE="HelloAgainApplet.class" WIDTH=200 HEIGHT=50> Hello Again! </APPLET> </BODY> </HTML> Analysis There are three things to note about the <APPLET> tag in this page: q q q The CODE attribute indicates the name of the class file that contains this applet, including the .class extension. In this case, the class file must be in the same directory as this HTML file. To indicate applets are in a specific directory, use CODEBASE, described later today. WIDTH and HEIGHT are required and are used to indicate the bounding box of the applet-that is, how big a box to draw for the applet on the Web page. Be sure you set WIDTH and HEIGHT to be an appropriate size for the applet; depending on the browser, if your applet draws outside the boundaries of the space you've given it, you may not be able to see or get to those parts of the applet outside the bounding box. The text between the <APPLET> and </APPLET> tags is displayed by browsers that do not understand the <APPLET> tag (which includes most browsers that are not Java aware). Because your page may be viewed in many different kinds of browsers, it is a very good idea to include some sort of alternate text or HTML tags here so that readers of your page who don't have Java will see something other than a blank line. For example, you might show just an image or some other element. Here, you include a simple statement that says Hello Again!. Note that the <APPLET> tag, like the <IMG> tag itself, is not a paragraph, so it should be enclosed inside a more general text tag, such as <P> or one of the heading tags (<H1>, <H2>, and so on). Testing the Result Now with a class file and an HTML file that refers to your applet, you should be able to load that HTML file into your Java-enabled browser from your local disk (in Netscape, use Open File from the File menu; in Internet Explorer, use Open from the File menu and then choose Browse to find the right file on your disk). The browser loads and parses your HTML file, and then loads and executes your applet class. If you don't have a Java-enabled browser, there are often tools that come with your development environment to help you test applets. In the JDK, the appletviewer application will test your applets. You won't see the Web page the applet is running on, but you can figure out if the applet is indeed running the way you expect it to. Figure 8.2 shows the Hello Again applet running in Netscape. Figure 8.2 : The Hello Again applet. file:///G|/ebooks/1575211831/ch8.htm (7 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics Making Java Applets Available to the Web After you have an applet and an HTML file, and you've verified that everything is working correctly on your local system, the last step is to make that applet available to the World Wide Web at large so that anyone with a Java-enabled browser can view that applet. Java applets are served by a Web server the same way that HTML files, images, and other media are. You don't need special server software to make Java applets available to the Web; you don't even need to configure your server to handle Java files. If you have a Web server up and running, or space on a Web server available to you, all you have to do is move your HTML and compiled class files to that server, as you would any other file. If you don't have a Web server, you have to rent space on one or set one up yourself. (Web server setup and administration, as well as other facets of Web publishing in general, are outside the scope of this book.) More About the <APPLET> Tag In its simplest form, by using CODE, WIDTH, and HEIGHT, the <APPLET> tag merely creates a space of the appropriate size and then loads and runs the applet in that space. The <APPLET> tag, however, does include several attributes that can help you better integrate your applet into the overall design of your Web page. Note The attributes available for the <APPLET> tag are almost identical to those for the HTML <IMG> tag. ALIGN The ALIGN attribute defines how the applet will be aligned on the page. This attribute can have one of nine values: LEFT, RIGHT, TOP, TEXTTOP, MIDDLE, ABSMIDDLE, BASELINE, BOTTOM, or ABSBOTTOM. In the case of ALIGN=LEFT and ALIGN=RIGHT, the applet is placed at the left or right margin of the page, respectively, and all text following that applet flows in the space to the right or left of that applet. The text will continue to flow in that space until the end of the applet, or you can use a line break tag (<BR>) with the CLEAR attribute to start the left line of text below that applet. The CLEAR attribute can have one of three values: CLEAR=LEFT starts the text at the next clear left margin, CLEAR=RIGHT does the same for the right margin, and CLEAR=ALL starts the text at the next line where both margins are clear. Note In Netscape Navigator for Windows, the use of the ALIGN attribute prevents the applet from actually being loaded (this is a bug; it works fine in the UNIX and Macintosh versions of Netscape, as well as in Internet Explorer). If you're using alignment extensively in your Web pages with applets, you might want to enclose them in tables and align the tables themselves rather than use ALIGN. For example, here's a snippet of HTML code that aligns an applet against the left margin, has some text flowing alongside it, and then breaks at the end of the paragraph so that the next bit of text starts below the applet: <P><APPLET CODE="HelloAgainApplet.class" WIDTH=200 HEIGHT=50 ALIGN=LEFT>Hello Again!</APPLET> To the left of this paragraph is an applet. It's a simple, unassuming applet, in which a small string is printed in red type, set in 36 point Times bold. <BR CLEAR=ALL> file:///G|/ebooks/1575211831/ch8.htm (8 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics <P>In the next part of the page, we demonstrate how under certain conditions, styrofoam peanuts can be used as a healthy snack. Figure 8.3 shows how this applet and the text surrounding it might appear in a Java-enabled browser (I've lightened the default page background so you can see where the applet begins and the background ends). Figure 8.3 : An applet aligned left. For smaller applets, you might want to include your applet within a single line of text. To do this, there are seven values for ALIGN that determine how the applet is vertically aligned with the text: q ALIGN=TEXTTOP aligns the top of the applet with the top of the tallest text in the line. q ALIGN=TOP aligns the applet with the topmost item in the line (which may be another applet, or an image, or the top of the text). q ALIGN=ABSMIDDLE aligns the middle of the applet with the middle of the largest item in the line. q ALIGN=MIDDLE aligns the middle of the applet with the middle of the baseline of the text. q ALIGN=BASELINE aligns the bottom of the applet with the baseline of the text. ALIGN=BASELINE is the same as ALIGN=BOTTOM, but ALIGN=BASELINE is a more descriptive name. q ALIGN=ABSBOTTOM aligns the bottom of the applet with the lowest item in the line (which may be the baseline of the text or another applet or image). Figure 8.4 shows the various alignment options, where the line is an image and the arrow is a small applet. Figure 8.4 : Applet alignment options. HSPACE and VSPACE The HSPACE and VSPACE attributes are used to set the amount of space, in pixels, between an applet and its surrounding text. HSPACE controls the horizontal space (the space to the left and right of the applet). VSPACE controls the vertical space (the space above and below). For example, here's that sample snippet of HTML with vertical space of 50 and horizontal space of 10: <P><APPLET CODE="HelloAgainApplet.class" WIDTH=300 HEIGHT=200 ALIGN=LEFT VSPACE=50 HSPACE=10>Hello Again!</APPLET> To the left of this paragraph is an applet. Its a simple, unassuming applet, in which a small string is printed in red type, set in 36 point Times bold. <BR CLEAR=ALL> <P>In the next part of the page, we demonstrate how under certain conditions, styrofoam peanuts can be used as a healthy snack. The result in a typical Java browser might look like that in Figure 8.5. Figure 8.5 : Vertical and horizontal space. CODE and CODEBASE The final two attributes to note in <APPLET> are CODE and CODEBASE. Unlike the other attributes, neither of these has anything to do with the applet's appearance on the page; these two refer to the actual location of the Java applet file so that the Java-enabled browser can find it. CODE is used to indicate the name of the class file that holds the current applet. If CODE is used alone in the <APPLET> file:///G|/ebooks/1575211831/ch8.htm (9 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics tag, the class file is searched for in the same directory as the HTML file that references it. Note that class filenames used in CODE have the .class extension; this is different from in the Java command-line interpreter, which doesn't use the extension. If you want to store your class files in a different directory on your Web server than that of your HTML files, you have to tell the browser where to find those class files. To do this, you use CODEBASE. CODE contains only the name of the class file; CODEBASE contains an alternate pathname (actually a URL or relative pathname) where classes are contained. For example, if you store your class files in a directory called classes, which is in the same directory as your HTML files, CODEBASE is the following: <APPLET CODE="myclass.class" CODEBASE="classes" WIDTH=100 HEIGHT=100></APPLET> If you store all your Java classes in some central location, you can also use a URL in CODEBASE: <APPLET CODE="myclass.class" CODEBASE="http://myserver.com/javaclasses" WIDTH=100 HEIGHT=100></APPLET> What if your class files are actually stored on an entirely different server altogether? You can use that URL in CODEBASE as well: <APPLET CODE="myclass.class" CODEBASE="http://www.joesserver.com/javaclasses" WIDTH=100 HEIGHT=100></APPLET> Java Archives Normally, using the standard way of indicating Java applets in Web pages, you use <APPLET> to point to the primary applet class for your applet. Your Java-enabled browser will then download and run that applet. That applet may use other classes or media files, all of which are also downloaded from the Web server as they are needed. The problem with running applets in this way is that every single file an applet needs-be it another helper class, image, audio file, text file, or anything else-is a separate connection the browser has to make to the server. Because there's a fair amount of time needed just to make the connection itself, this can increase the amount of time it takes to download your applet and everything it needs. The solution to this problem is a Java archive. A Java archive is a collection of Java classes and other files contained in a single file. By using a Java archive, the browser only makes one connection to the server, rather than several. By reducing the number of files the browser has to load from the server, your applet can be downloaded and run that much faster. Java archives may also be compressed, making the overall file size smaller and therefore faster to download as well (although it may take some time on the browser side for the files to be decompressed before they can run). Right now only Netscape supports the use of Java archives, and only for Java class files (not for media). Within Netscape, you can use the ARchIVE attribute to indicate the name of the archive, like this: <APPLET CODE="MyApplet.class" ARchIVE="appletstuff.zip" WIDTH=100 HEIGHT=100> ... </APPLET> The archive itself is an uncompressed zip file. Standard zip files, which use some form of compression to make the file smaller, are not recognized. Also, helper classes may be contained inside or outside the zip file; Netscape will look in either place. The ARchIVE attribute is ignored by browsers or applet viewers that may run across this Web page. If you do use Java archives for Netscape, it's a good idea to store both the archive and the individual files on your Web server so that all the file:///G|/ebooks/1575211831/ch8.htm (10 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics Java-enabled browsers who visit your Web page can view your applet. In addition to Netscape's simple archive scheme, Java 1.1 will include support for JAR files. JAR files are Java archives, with or without compression, that can contain both classes and media. In addition, JAR files are platform independent, and the tools to create them will be available on any platform that supports the JDK. JAR files and their individual components can also be digitally signed, meaning that their creator can be reliably identified (a form of security). For more information about JAR files, including the specifications for the actual file format, see the JDK 1.1 Preview Page at http://java.sun.com/products/JDK/1.1/designspecs/. Passing Parameters to Applets With Java applications, you pass parameters to your main() routine by using arguments on the command line, or, for Macintoshes, in the Java Runner's dialog box. You can then parse those arguments inside the body of your class, and the application acts accordingly, based on the arguments it is given. Applets, however, don't have a command line. How do you pass in different arguments to an applet? Applets can get different input from the HTML file that contains the <APPLET> tag through the use of applet parameters. To set up and handle parameters in an applet, you need two things: q A special parameter tag in the HTML file q Code in your applet to parse those parameters Applet parameters come in two parts: a parameter name, which is simply a name you pick, and a value, which is the actual value of that particular parameter. So, for example, you can indicate the color of text in an applet by using a parameter with the name color and the value red. You can determine an animation's speed using a parameter with the name speed and the value 5. In the HTML file that contains the embedded applet, you indicate each parameter using the <PARAM> tag, which has two attributes for the name and the value, called (surprisingly enough) NAME and VALUE. The <PARAM> tag goes inside the opening and closing <APPLET> tags: <APPLET CODE="MyApplet.class" WIDTH=100 HEIGHT=100> <PARAM NAME=font VALUE="TimesRoman"> <PARAM NAME=size VALUE="36"> A Java applet appears here.</APPLET> This particular example defines two parameters to the MyApplet applet: one whose name is font and whose value is TimesRoman, and one whose name is size and whose value is 36. Parameters are passed to your applet when it is loaded. In the init() method for your applet, you can then get hold of those parameters by using the getParameter() method. getParameter() takes one argument-a string representing the name of the parameter you're looking for-and returns a string containing the corresponding value of that parameter. (Like arguments in Java applications, all the parameter values are strings.) To get the value of the font parameter from the HTML file, you might have a line such as this in your init() method: String theFontName = getParameter("font"); Note file:///G|/ebooks/1575211831/ch8.htm (11 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics The names of the parameters as specified in <PARAM> and the names of the parameters in getParameter() must match identically, including having the same case. In other words, <PARAM NAME="name"> is different from <PARAM NAME="Name">. If your parameters are not being properly passed to your applet, make sure the parameter cases match. Note that if a parameter you expect has not been specified in the HTML file, getParameter() returns null. Most often, you will want to test for a null parameter in your Java code and supply a reasonable default: if (theFontName == null) theFontName = "Courier" Keep in mind that getParameter() returns strings-if you want a parameter to be some other object or type, you have to convert it yourself. To parse the size parameter from that same HTML file and assign it to an integer variable called theSize, you might use the following lines: int theSize; String s = getParameter("size"); if (s == null) theSize = 12; else theSize = Integer.parseInt(s); Get it? Not yet? Let's create an example of an applet that uses this technique. You'll modify the Hello Again applet so that it says hello to a specific name, for example, "Hello Bill" or "Hello Alice". The name is passed into the applet through an HTML parameter. Let's start by copying the original HelloAgainApplet class and calling it MoreHelloAgain (see Listing 8.3). Listing 8.3. The More Hello Again applet. 1:import java.awt.Graphics; 2:import java.awt.Font; 3:import java.awt.Color; 4: 5:public class MoreHelloApplet extends java.applet.Applet { 6: 7: Font f = new Font("TimesRoman", Font.BOLD, 36); 8: 9: public void paint(Graphics g) { 10: g.setFont(f); 11: g.setColor(Color.red); 12: g.drawString("Hello Again!", 5, 40); 13: } 14:} The first thing you need to add to this class is a place to hold the name of the person you're saying hello to. Because you'll need that name throughout the applet, let's add an instance variable for the name, just after the variable for the font in line 7: String name; To set a value for the name, you have to get that parameter from the HTML file. The best place to handle parameters to file:///G|/ebooks/1575211831/ch8.htm (12 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics an applet is inside an init() method. The init() method is defined similarly to paint() (public, with no arguments, and a return type of void). Make sure when you test for a parameter that you test for a value of null. The default, in this case, if a name isn't indicated, is to say hello to "Laura". Add the init() method in between your instance variable definitions and the definition for paint(), just before line 9: public void init() { name = getParameter("name"); if (name == null) name = "Laura"; } Now that you have the name from the HTML parameters, you'll need to modify it so that it's a complete string-that is, to tack the word Hello with a space onto the beginning, and an exclamation point onto the end. You could do this in the paint() method just before printing the string to the screen, but that would mean creating a new string every time the applet is painted. It would be much more efficient to do it just once, right after getting the name itself, in the init() method. Add this line to the init() method just before the last brace: name = "Hello " + name + "!"; And now, all that's left is to modify the paint() method to use the new name parameter. The original drawString() method looked like this: g.drawString("Hello Again!", 5, 40); To draw the new string you have stored in the name instance variable, all you need to do is substitute that variable for the literal string: g.drawString(name, 5, 40); Listing 8.4 shows the final result of the MoreHelloApplet class. Compile it so that you have a class file ready. Listing 8.4. The MoreHelloApplet class. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: import java.awt.Graphics; import java.awt.Font; import java.awt.Color; public class MoreHelloApplet extends java.applet.Applet { Font f = new Font("TimesRoman", Font.BOLD, 36); String name; public void init() { name = getParameter("name"); if (name == null) name = "Laura"; name = "Hello " + name + "!"; } public void paint(Graphics g) { g.setFont(f); g.setColor(Color.red); file:///G|/ebooks/1575211831/ch8.htm (13 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics 21: 22: 23: } g.drawString(name, 5, 40); } Now let's create the HTML file that contains this applet. Listing 8.5 shows a new Web page for the MoreHelloApplet applet. Listing 8.5. The HTML file for the MoreHelloApplet applet. 1: <HTML> 2: <HEAD> 3: <TITLE>Hello!</TITLE> 4: </HEAD> 5: <BODY> 6: <P> 7: <APPLET CODE="MoreHelloApplet.class" WIDTH=200 HEIGHT=50> 8: <PARAM NAME=name VALUE="Bonzo"> 9: Hello to whoever you are! 10: </APPLET> 11: </BODY> 12: </HTML> Analysis Note the <APPLET> tag, which points to the class file for the applet and has the appropriate width and height (200 and 50). Just below it (line 8) is the <PARAM> tag, which you use to pass in the value for the name. Here, the NAME parameter is simply name, and the VALUE is the string "Bonzo". Loading up this HTML file in Netscape produces the result shown in Figure 8.6. Figure 8.6 : The result of using MoreHelloApplet the first time. Let's try a second example. Remember that in the code for MoreHelloApplet, if no name is specified in a parameter, the default is the name Laura. Listing 8.6 creates an HTML file with no parameter tag for name. Listing 8.6. Another HTML file for the MoreHelloApplet applet. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: <HTML> <HEAD> <TITLE>Hello!</TITLE> </HEAD> <BODY> <P> <APPLET CODE="MoreHelloApplet.class" WIDTH=200 HEIGHT=50> Hello to whoever you are! </APPLET> </BODY> </HTML> Here, because no name was supplied, the applet uses the default, and the result is what you might expect (see Figure 8.7). file:///G|/ebooks/1575211831/ch8.htm (14 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics Figure 8.7 : The result of using MoreHelloApplet the second time. Summary Applets are probably the most common use of the Java language today. Applets are more complicated than many Java applications because they are executed and drawn inline within Web pages, but they can access the graphics, user interface, and event structure provided by the Web browser itself. Today you learned the basics of creating applets, including the following things: q All applets you develop using Java inherit from the Applet class, which is part of the java.applet package. The Applet class provides basic behavior for how the applet will be integrated with and react to the browser and various forms of input from that browser and the person running it. By subclassing Applet, you have access to all that behavior. q Applets have five main methods, which are used for the basic activities an applet performs during its life cycle: init(), start(), stop(), destroy(), and paint(). Although you don't need to override all these methods, these are the most common methods you'll see repeated in many of the applets you'll create in this book and in other sample programs. q To run a compiled applet class file, you include it in an HTML Web page by using the <APPLET> tag. When a Java-capable browser comes across <APPLET>, it loads and runs the applet described in that tag. Note that to publish Java applets on the World Wide Web alongside HTML files you do not need special server software; any plain old Web server will do just fine. q Unlike applications, applets do not have a command line on which to pass arguments, so those arguments must be passed into the applet through the HTML file that contains it. You indicate parameters in an HTML file by using the <PARAM> tag inside the opening and closing <APPLET> tags. <PARAM> has two attributes: NAME for the name of the parameter, and VALUE for its value. Inside the body of your applet (usually in init()), you can then gain access to those parameters using the getParameter() method. Q&A Q: A: In the first part of today's lesson, you say that applets are downloaded from random Web servers and run on the client's system. What's to stop an applet developer from creating an applet that deletes all the files on that system, or in some other way compromises the security of the system? Recall that Java applets have several restrictions that make it difficult for all of the more obvious malicious behavior to take place. For example, because Java applets cannot read or write files on the client system, they cannot delete files or read system files that might contain private information. Because they cannot run programs on the client's system without your express permission, they cannot, for example, pretend to be you and run system programs. Nor can they run so many programs that your system crashes. In addition, Java's very architecture makes it difficult to circumvent these restrictions. The language itself, the Java compiler, and the Java interpreter all have checks to make sure that no one has tried to sneak in bogus code or play games with the system itself. You'll learn more about these checks at the end of this book. Q: Of course, no system can claim to be 100 percent secure, and the fact that Java applets are run on your system should make you suspicious-see Day 21 for more on security. Wait a minute. If I can't read or write files or run programs on the system the applet is running on, doesn't that mean I basically can't do anything other than simple animation and flashy graphics? How can I save state in an applet? How can I create, say, a word processor or a spreadsheet as a Java applet? file:///G|/ebooks/1575211831/ch8.htm (15 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics A: For everyone who doesn't believe that Java is secure enough, there is someone who believes that Java's security restrictions are too severe for just these reasons. Yes, Java applets are limited because of the security restrictions. But, given the possibility for abuse, I believe that it's better to err on the side of being more conservative as far as security is concerned. Consider it a challenge. Keep in mind, also, that Java applications have none of the restrictions that Java applets do, but because they are also compiled to bytecode, they are portable across platforms. It may be that the thing you want to create would make a much better application than an applet. Q: A: Q: A: Q: A: Q: A: Q: A: Q: A: If the thing you want to create has to be an applet, the only solution you have for saving state or implementing something like a word processor in a Java applet is to allow your readers to save the state back to your server. Will applets be like this forever-confined to the sandbox and unable to do anything other than whizzy animation and simple toys? Sun is working on future models for applet security that will allow applets to break out of the sandbox in some instances. One of the solutions being discussed is for the applet class file to be digitally signed, which is a way to identify without a doubt where an applet came from (for example, if an applet is signed by Sun, you can be sure it was Sun that actually created it, and therefore trust it more than some other random applet need). You'll learn more about applet security on Day 21. I have an older version of the HotJava browser. I followed all the examples in this section, but HotJava cannot read my applets (it seems to ignore them). What's going on? You most likely have an alpha version of HotJava. Recall that significant changes were made to the Java API and how Java applets are written between alpha and the 1.0 release. The result of these changes is that browsers that support alpha applets cannot read beta applets, and vice versa. The HTML tags are even different, so an older browser just skips over newer applets, and vice versa. By the time you read this, there may be a new version of HotJava with support for 1.0. If not, you can use Netscape, Internet Explorer, or the JDK's appletviewer to view applets written to the beta specification. I noticed in my documentation that the <APPLET> tag also has a NAME attribute. You didn't discuss it here. NAME is used when you have multiple applets on a page that need to communicate with each other. You'll learn about this on Day 12, "Managing Simple Events and Interactivity." Lots of the applet examples I've seen on the Web have an init() method that does nothing to call a resize() method with the same values as in the <APPLET> tag's WIDTH and HEIGHT. I asked a friend about that and he said that you have to have resize() in there to make sure the applet's the right size. You don't mention resize(). The call to the resize() method in init() is left over from the early days of applets when you did need resize() to set the initial size of the applet. These days only the WIDTH and HEIGHT attributes do that; calling resize() isn't necessary. I have an applet that takes parameters and an HTML file that passes it those parameters. But when my applet runs, all I get are null values. What's going on here? Do the names of your parameters (in the NAME attribute) match exactly with the names you're testing for in getParameter()? They must be exact, including case, for the match to be made. Make sure also that your <PARAM> tags are inside the opening and closing <APPLET> tags, and that you haven't misspelled anything. Since applets don't have a command line or a stdout stream, how can you do simple debugging output like System.out.println() in an applet? You can. Depending on your browser or other Java-enabled environment, there may be a console window where debugging output (the result of System.out.println()) appears, or it may be saved to a log file (Netscape has a Java Console under the Options menu; Internet Explorer uses a Java log file that you must enable using Options | Advanced). You can continue to print messages using System.out.println() in your applets-just remember to remove them once you're done so they don't confuse your actual readers! file:///G|/ebooks/1575211831/ch8.htm (16 of 17) [11/06/2000 7:45:31 PM] Day 8 -- Java Applet Basics file:///G|/ebooks/1575211831/ch8.htm (17 of 17) [11/06/2000 7:45:31 PM] Day 14 -- Windows, Networking, and Other Tidbits Day 14 Windows, Networking, and Other Tidbits by Laura Lemay CONTENETS q Windows, Menus, and Dialog Boxes r r Frames r Closing Windows r Menus r Dialog Boxes r Cursors r Window Events r q The awt Window Classes Standalone awt Applications Networking in Java r r Opening Web Connections r openStream() r Sockets r q Creating Links Inside Applets Changes to Sockets for Java 1.1 Other Applet Hints r The showStatus() Method r Applet Information r Communicating Between Applets q Summary q Q&A Here you are on the last day of the second week, and you're just about finished with applets and the awt. With the information you'll learn today you can create a wide variety of applets and applications using Java. Next week's lessons provide more of the advanced stuff that you'll need if you start doing really serious work in Java. Today, to finish up this week, we'll cover three very different topics: q Windows, menus, and dialog boxes-the last of the awt classes that enable you to pop up real windows and dialog boxes from applets, to add menus to those windows, and to create standalone graphical Java applications that can use all the awt features you've learned about this week. q Networking-how to load new HTML files from a Java-enabled browser, how to retrieve files from Web sites, and some basics on how to work with generic sockets in Java. q Extra tidbits-the smaller stuff that didn't fit in anywhere else, but that might be useful to you as you write your Java applets and applications. file:///G|/ebooks/1575211831/ch14.htm (1 of 24) [11/06/2000 7:45:35 PM] Day 14 -- Windows, Networking, and Other Tidbits Windows, Menus, and Dialog Boxes Today you'll finish up the last bits of the awt that didn't fit into yesterday's lesson. In addition to all the graphics, events, user interface, and layout mechanisms that the awt provides, it also provides windows, menus, and dialog boxes, enabling to you create fully featured applications either as part of your applet or independently for standalone Java applications. The awt Window Classes The Java awt classes to produce windows and dialogs inherit from a single class: Window. The Window class, which itself inherits from Container (and is therefore a standard awt component), provides generic behavior for all window-like things. Generally you don't use instances of Window, however; you use instances of Frame or Dialog. Figure 14.1 shows the simple Window class hierarchy. Figure 14.1 : The Window class hierarchy. The Frame class provides a window with a title bar, close boxes, and other platform-specific window features. Frames also let you add menu bars. Dialog is a more limited form of Frame that typically doesn't have a title. FileDialog, a subclass of Dialog, provides a standard file-picker dialog box (usually only usable from inside Java applications because of security restrictions on applets). When you want to add a new window or dialog to your applet or application, you'll create subclasses of the Frame and Dialog classes. Frames Frames are windows that are independent of an applet and of the browser that contains it-they are separate windows with their own titles, resize handles, close boxes, and menu bars. You can create frames for your own applets to produce windows, or you can use frames in Java applications to hold the contents of that application. A frame is a platform-specific window with a title, a menu bar, close boxes, resize handles, and other window features. To create a frame, use one of the following constructors: q new Frame() creates a basic frame without a title. q new Frame(String) creates a basic frame with the given title. Because frames inherit from Window, which inherits from Container, which inherits from Component, frames are created and used much in the same way that other awt components are created and used. Frames are containers, just like panels are, so you can add other components to them just as you would regular panels, using the add() method. The default layout for frames is BorderLayout. Here's a single example that creates a frame, sets its layout, and adds two buttons: win = new Frame("My Cool Window"); win.setLayout(new BorderLayout(10, 20)); win.add("North", new Button("Start")); win.add("Center", new Button("Move")); To set a size for the new frame, use the resize() method with the width and height of the new frame. So, for example, this line of code resizes the window to be 100 pixels wide and 200 pixels high: win.resize(100, 200); Note that because different systems have different ideas of what a pixel is and different resolutions for those pixels, it's difficult to create a window that is the "right" size for every platform. Windows that work fine for one may be way too large or too small for another. One way around this is to use the pack() method instead of resize(). The pack() file:///G|/ebooks/1575211831/ch14.htm (2 of 24) [11/06/2000 7:45:35 PM] Day 14 -- Windows, Networking, and Other Tidbits method, which has no arguments, creates a window of the smallest possible size given the current sizes of all the components inside that window and the layout manager and insets in use. Here's an example that creates two buttons, and adds them to a window. The window will then be resized to the smallest possible window that can still hold those buttons: win = new Frame("My Other Cool Window"); win.setLayout(new FlowLayout())); win.add("North", new Button("OK")); win.add("Center", new Button("Cancel")); win.pack(); When you initially create a window, it's invisible. You need to use the show() method to make the window appear onscreen (you can use hide() to hide it again): win.show(); Note that when you pop up windows from inside applets, the browser may indicate in some way that the window is not a regular browser window-usually with a warning in the window itself. In Netscape, there's a yellow bar at the bottom of every window that says Untrusted Java Window. This warning is intended to let your users know that your window comes from the applet and not from the browser itself (remember that the frame class produces windows that look just like normal system windows). The warning is to prevent you from creating a malicious applet that might, for example, ask the user for his password. There isn't anything you can do to avoid this warning; it's there to stay as long as you want to use windows with applets. Listings14.1 and 14.2 show examples of a simple applet with a pop-up window frame (both the applet and the window are shown in Figure 14.2). The applet has two buttons: one to show the window, and one to hide the window. The frame itself, created from a subclass I created called BaseFrame, contains a single label: This is a Window. You'll use this basic window and applet all through this section, so the more you understand what's going on here the easier it will be later. Figure 14.2 : Windows. Listing 14.1. A pop-up window. 1:import java.awt.*; 2: 3:public class PopupWindow extends java.applet.Applet { 4: Frame window; 5: 6: public void init() { 7: add(new Button("Open Window")); 8: add(new Button("Close Window")); 9: 10: window = new BaseFrame("A Popup Window"); 11: window.resize(150,150); 12: window.show(); 13: } 14: 15: public boolean action(Event evt, Object arg) { 16: if (evt.target instanceof Button) { 17: String label = (String)arg; 18: if (label.equals("Open Window")) { 19: if (!window.isShowing()) 20: window.show(); 21: } file:///G|/ebooks/1575211831/ch14.htm (3 of 24) [11/06/2000 7:45:35 PM] Day 14 -- Windows, Networking, and Other Tidbits 22: 23: 24: 25: 26: 27: 28: 29: 30:} else if (label.equals("Close Window")) { if (window.isShowing()) window.hide(); } return true; } else return false; } Listing 14.2. The BaseFrame class. 1:import java.awt.*; 2: 3:class BaseFrame extends Frame { 4: String message = "This is a Window"; 5: 6: BaseFrame1(String title) { 7: super(title); 8: setFont(new Font("Helvetica", Font.BOLD, 12)); 9: } 10: 11: public void paint(Graphics g) { 12: g.drawString(message, 20, 20); 13: } 14:} There are two classes that make up this example: The first, PopupWindow, is the applet class that creates and controls the pop-up window. In the init() method for that class (lines 6 to 13), we added two control buttons to the applet to control the window, and then created, resized, and showed the window itself. The control in this applet occurs when one of the buttons is pressed. Here, the Open Window button simply shows the window if it's hidden (lines 18 to 21), and hides it if it's showing (lines 22 to 25). The window itself is a special kind of frame called BaseFrame. In this example, the frame is fairly simple; all it does is paint a text message near the top of the frame. Because frames are components, just like other components, you could have just as easily added a layout manager, buttons, text fields, and so on, to this frame. Closing Windows You may have noticed, if you started up that pop-up window applet to play with it, that the new window's close box doesn't work. Nothing happens when you click the mouse on the box. To implement behavior for closing the window-for pop-up windows as in applets to hide them, or to exit the application altogether for applications-you'll have to use a handleEvent() method in your Frame class to test for the WINDOW_DESTROY event. In the pop-up window example, choosing the close box should hide the window (call the hide() method). You can then show it again using the Open Window button in the applet. This is a very simple fix; just add the following handleEvent() to your BaseFrame1 class: public boolean handleEvent(Event evt) { if (evt.id == Event.WINDOW_DESTROY) hide(); return super.handleEvent(evt); } file:///G|/ebooks/1575211831/ch14.htm (4 of 24) [11/06/2000 7:45:35 PM] Day 14 -- Windows, Networking, and Other Tidbits Menus Each new window you create can have its own menu bar along the top of that window. Each menu bar can have a number of menus, and each menu, in turn, can have menu items. The awt provides classes for all these things called, respectively, MenuBar, Menu, and MenuItem. Figure 14.3 shows the menu classes. Figure 14.3 : The awt menu classes. Note that you can have menu bars and individual menus in Java only on components that have title bars-frames in pop-up windows from applets work just fine, as do Java application windows, but you cannot have a menu bar attached to an applet itself. Menus and Menu Bars To create a menu bar for a given window, create a new instance of the class MenuBar: MenuBar mbar = new MenuBar(); To set this menu bar as the default menu for the window, use the setMenuBar() method (defined in the Frame class): window.setMenuBar(mbar); Add individual menus (File, Edit, and so on) to the menu bar by creating them and then adding them to the menu bar using add(): Menu myMenu = new Menu("File"); mbar.add(myMenu); Some systems provide a special help menu, which is drawn on the right side of the menu bar as opposed to somewhere in the middle. You can indicate that a specific menu is the help menu with the setHelpMenu() method. The given menu should already be added to the menu itself before being made a help menu: Menu helpmenu = new Menu("Help"); mbar.add(helpmenu); mbar.setHelpMenu(helpmenu); If, for any reason, you want to prevent a user from selecting a menu, you can use the disable() command on that menu (and the enable() command to make it available again): myMenu.disable(); Menu Items There are four kinds of items you can add to individual menus: q Instances of the class MenuItem, for regular menu items q Instances of the class CheckBoxMenuItem, for toggled menu items q Other menus, with their own menu items q Separators, for lines that separate groups of items on menus Regular menu items are added by using the MenuItem class. Add them to a menu using the add() method: Menu myMenu = new Menu("Tools"); myMenu.add(new MenuItem("Info")); file:///G|/ebooks/1575211831/ch14.htm (5 of 24) [11/06/2000 7:45:35 PM] Day 14 -- Windows, Networking, and Other Tidbits myMenu.add(new MenuItem("Colors")); Submenus can be added simply by creating a new instance of Menu and adding it to the first menu. You can then add items to that menu: Menu submenu = new Menu("Sizes"); myMenu.add(submenu); submenu.add(new MenuItem("Small")); submenu.add(new MenuItem("Medium")); submenu.add(new MenuItem("Large")); The CheckBoxMenuItem class creates a menu item with a check box on it, enabling the menu state to be toggled on and off (selecting it once makes the check box appear selected; selecting it again unselects the check box). Create and add a check box menu item the same way you create and add regular menu items: CheckboxMenuItem coords = new CheckboxMenuItem("Show Coordinates"); myMenu.add(coords); Finally, to add a separator to a menu (a line used to separate groups of items in a menu), create and add a menu item with a single dash (-) as the label. That special menu item will be drawn with a separator line. These next two lines of Java code create a separator menu item and add it to the menu myMenu: MenuItem msep = new MenuItem("-"); myMenu.add(msep); Any menu item can be disabled by using the disable() method and enabled again using enable(). Disabled menu items cannot be selected: MenuItem item = new MenuItem("Fill"); myMenu.addItem(item); item.disable(); You'll add a typical menu and menu bar to the pop-up window applet in a bit; but first let's learn about how to activate menu items when they're selected. Menu Actions The act of selecting a menu item causes an action event to be generated. You can handle that action the same way you handle other action methods-by overriding action(). Both regular menu items and check box menu items have actions that generate an extra argument representing the label for that menu. You can use that label to determine which action to take. Note, also, that because CheckBoxMenuItem is a subclass of MenuItem, you don't have to treat that menu item as a special case. In this example, the Show Coordinates menu item is a CheckBoxMenuItem, and Fill is a regular menu item: public boolean action(Event evt, Object arg) { if (evt.target instanceof MenuItem) { String label = (String)arg; if (label.equals("Show Coordinates")) toggleCoords(); else if (label.equals("Fill")) fillcurrentArea(); return true; } else return false; } file:///G|/ebooks/1575211831/ch14.htm (6 of 24) [11/06/2000 7:45:35 PM] Day 14 -- Windows, Networking, and Other Tidbits A Pop-up Window with Menus Let's add a menu to the pop-up window you created in the previous section. There are two steps here: creating and adding the menu, with all its menu items, to the layout, and then adding an action method to deal with the actions. Here we'll modify the BaseFrame class to include both these things; Listing 14.3 shows the new code. Figure 14.4 shows the menu in action. Figure 14.4 : A menu. Note In the sample code on the CD, I created a new class called BaseFrame2 for this part of the example, and a new class PopupWindowMenu.java to be the applet that owns this window. Use PopupWindowMenu.html to view it. Listing 14.3. BaseFrame with a menu. 1:import java.awt.*; 2: 3:class BaseFrame2 extends Frame { 4: String message = "This is a Window"; 5: 6: BaseFrame2(String title) { 7: super(title); 8: setFont(new Font("Helvetica", Font.BOLD, 12)); 9: 10: MenuBar mb = new MenuBar(); 11: Menu m = new Menu("Colors"); 12: m.add(new MenuItem("Red")); 13: m.add(new MenuItem("Blue")); 14: m.add(new MenuItem("Green")); 15: m.add(new MenuItem("-")); 16: m.add(new CheckboxMenuItem("Reverse Text")); 17: mb.add(m); 18: setMenuBar(mb); 19: } 20: 21: public boolean action(Event evt, Object arg) { 22: String label = (String)arg; 23: if (evt.target instanceof MenuItem) { 24: if (label.equals("Red")) setBackground(Color.red); 25: else if (label.equals("Blue")) setBackground(Color.blue); 26: else if (label.equals("Green")) setBackground(Color.green); 27: else if (label.equals("Reverse Text")) { 28: if (getForeground() == Color.black) { 29: setForeground(Color.white); 30: } else setForeground(Color.black); 31: } 32: repaint(); 33: return true; 34: } else return false; 35: } file:///G|/ebooks/1575211831/ch14.htm (7 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:} public void paint(Graphics g) { g.drawString(message, 20, 20); } public boolean handleEvent(Event evt) { if (evt.id == Event.WINDOW_DESTROY) hide(); return super.handleEvent(evt); } This menu has four items: one each for the colors red, blue, and green (which, when selected, change the background of the window), and one check box menu item for reversing the color of the text (to white). All are added as part of the constructor to this class, in lines 6 to 19. To handle these menu items when they're chosen, you need an action() method. Inside action() you test to see if the action came from a menu item (which includes the one check box menu item), and if so, test for each of the menu labels in turn. For the red, blue, and green menu items, all you need to do is set the background. For the Reverse Text toggle, you need to first find out the current color of the text, and then reverse it. To finish up, call a repaint() to make sure the background and the text get updated properly and return the appropriate boolean. Dialog Boxes Dialog boxes are functionally similar to frames in that they pop up new windows on the screen. However, dialog boxes are intended to be used for transient windows-for example, windows that let you know about warnings, windows that ask you for specific information, and so on. Dialogs don't usually have title bars or many of the more general features that windows have (although you can create one with a title bar), and they can be made nonresizable or modal (modal dialogs prevent input to any other windows on the screen until they are dismissed). Dialogs are transient windows intended to alert the user to some event or to get input from the user. Unlike frames, dialogs do not generally have a title bar or close boxes. A modal dialog prevents input to any of the other windows on the screen until that dialog is dismissed. (You won't be able to bring other windows to the front or iconify a modal dialog window; you must actually dismiss the modal dialog before being able to do anything else on the system. Warnings and alerts are typically modal dialogs.) The awt provides two kinds of dialog boxes: the Dialog class, which provides a generic dialog, and FileDialog, which produces the platform-specific file browser dialog. Dialog Objects Dialogs are created and used in much the same way as windows. To create a generic dialog, use one of these constructors: q Dialog(Frame, boolean) creates an initially invisible dialog, attached to the current frame, which is either modal (true) or not (false). q Dialog(Frame, String, boolean) is the same as the previous constructor, with the addition of a title bar and a title indicated by the string argument. The dialog window, like the frame window, is a panel on which you can lay out and draw user interface components and perform graphics operations, just as you would any other panel. Like other windows, the dialog is initially invisible, but you can show it with show() and hide it with hide(). Let's add a dialog to that same example with the pop-up window. Here we'll modify the BaseFrame class once again to include a dialog, and add a new class, TextDialog, which produces a text entry dialog similar to the one shown in file:///G|/ebooks/1575211831/ch14.htm (8 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits Figure 14.5. Figure 14.5 : The Enter Text dialog. To add the dialog to the BaseFrame class, the changes are minor. First you'll need an instance variable to hold the dialog, since you'll be referring to it throughout this class: TextDialog dl; Next you'll add a menu item to the BaseFrame class's constructor method to change the text the pop-up window displays. This new menu item goes just after the Reverse Text item: ... m.add(new CheckboxMenuItem("Reverse Text")); m.add(new MenuItem("Set Text...")); ... In that same constructor method, you can create the dialog (an instance of the new class TextDialog you'll create in a bit), assign it to the dl instance variable, and resize it (as shown in the next two lines of code). You don't want to show it yet because it should only appear when the correct menu item is selected: dl = new TextDialog(this, "Enter Text", true); dl.resize(150,100); To get the dialog to appear at the appropriate time, you'll add a line to the action() method so that when the Set Text menu item is chosen, the dl.show() method is called. You can put this action in the same if-else block as the rest of the actions: ... else if (label.equals("Green")) setBackground(Color.green); else if (label.equals("Set Text...")) dl.show(); else if (label.equals("Reverse Text")) { ... That's the end of the behavior you have to add to the window to create a dialog; the rest of the behavior goes into the TextDialog class, the code for which is shown in Listing 14.4. Listing 14.4. The TextDialog class. 1:import java.awt.*; 2: 3:class TextDialog extends Dialog { 4: TextField tf; 5: BaseFrame3 theFrame; 6: 7: TextDialog(Frame parent, String title, boolean modal) { 8: super(parent, title, modal); 9: 10: theFrame = (BaseFrame3)parent; 11: setLayout(new BorderLayout(10,10)); 12: setBackground(Color.white); 13: tf = new TextField(theFrame.message,20); 14: add("Center", tf); 15: add("South", new Button("OK")); file:///G|/ebooks/1575211831/ch14.htm (9 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:} resize(150,75); } public Insets insets() { return new Insets(30,10,10,10); } public boolean action(Event evt, Object arg) { String label = (String)arg; if (evt.target instanceof Button) { if (label == "OK") { hide(); theFrame.message = tf.getText(); theFrame.repaint(); } } else return false; return true; } In many ways this dialog class is very nearly the same as the BaseFrame class. It has a constructor that sets up the layout of the components, and an action() method to deal with its behavior. This one also has an insets() method for more layout information, but that's not a significant difference. There are a few things to note about this code. First of all, note that the TextDialog class has a reference back up to its parent frame. It needs to reference this so it can update that frame with the new text information. Why does the dialog need to update the frame, rather than the frame figuring out when it needs updating? Because only the dialog knows when it's been dismissed. It's the dialog that deals with the change when the user presses OK, not the frame. So the dialog needs to be able to reach back to the original frame. Line 5 defines an instance variable to hold that reference. The text dialog gets a reference to the parent frame through its constructor. This is actually the standard constructor for dialogs, so nothing new needs to be created here. You can simply call super() to initialize the dialog, and then add other bits to it. The first argument to the constructor is the frame argument. This is that hookup to the frame. But since you're getting a frame object, and you want a BaseFrame object, you'll have to cast it before you can assign it to the theFrame instance variable. Do this in line 10. The remainder of the constructor for this dialog class simply creates the layout: a text field and a button in a border layout. The action() method is what tells the dialog to hide itself. Mouse actions are broadcast to the window on which they occur; they do not percolate across windows, which is why you can't test to see if the OK button in the dialog was pressed from inside the BaseFrame class. Here you'll create an action() method to do two things when the OK button is pressed: hide the dialog and update the text message in the frame. Here's where that frame reference is important; in line 28 you're extracting the text that was entered into the dialog's text field and putting it into the frame's message instance variable. The next time the frame goes to paint (and you tell it to repaint() in line 29), the text message will get updated. Attaching Dialogs to Applets Dialogs can only be attached to frames; to create a dialog you have to pass an instance of the Frame class to one of the dialog's constructor methods. This would imply that you cannot create dialog boxes that are attached to applets. Because applets don't have explicit frames, you cannot give the Dialog class a frame argument. Through a bit of sneaky code, however, you can get ahold of the frame object that contains that applet (often the browser or applet viewer window itself) and then use that object as the file:///G|/ebooks/1575211831/ch14.htm (10 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits dialog's frame. This sneaky code makes use of the getParent() method, defined for all awt components. The getParent() method returns the object that contains this object. The parent of all awt applications, then, must be a frame. Applets behave in this same way; by calling getParent() repeatedly, eventually you should be able to get ahold of an instance of Frame. Here's the sneaky code to do this that you can put inside your applet: Object anchorpoint = getParent() while (! (anchorpoint instanceof Frame)) anchorpoint = ((Component)anchorpoint).getParent(); In the first line of this code, you create a local variable, called anchorpoint, to hold the eventual frame for this applet. The object assigned to anchorpoint may be one of many classes, so we'll declare its type to be Object. The second two lines of this code are a while loop that calls getParent() on each different object up the chain until it gets to an actual Frame object. Note here that since the getParent() method is only defined on objects that inherit from Component, we have to cast the value of anchorpoint to Component each time for the getParent() method to work. After the loop exits, the object contained in the anchorpoint variable will be an instance of the Frame class (or one of its subclasses). You can then create a Dialog object attached to that frame, casting the anchorpoint one more time to make sure you've got a Frame object: TextDialog dl = new TextDialog((Frame)anchorpoint, "Enter Text", true); File Dialog Objects The FileDialog class provides a basic file open/save dialog box that enables you to access the file system. The FileDialog class is system-independent, but depending on the platform, the standard Open File or Save File dialog is brought up. Note For applets, whether or not you can even use instances of FileDialog is dependent on the browser (Netscape simply produces an error). FileDialog is much more useful in standalone applications. To create a file dialog, use the following constructors: q FileDialog(Frame, String) creates an Open File dialog, attached to the given frame, with the given title. This form creates a dialog to load a file. q FileDialog(Frame, String, int) also creates a file dialog, but that integer argument is used to determine whether the dialog is for loading a file or saving a file (the only difference is the labels on the buttons; the file dialog does not actually open or save anything). The possible options for the mode argument are FileDialog.LOAD and FileDialog.SAVE. After you create a FileDialog instance, use show() to display it: FileDialog fd = new FileDialog(this, "FileDialog"); fd.show(); When the reader chooses a file in the File dialog and dismisses it, you can then access the filename they chose by using the getDirectory() and getFile() methods; both return strings indicating the values the reader chose. You can then open that file by using the stream and file handling methods (which you'll learn about next week) and then read from or write to that file. file:///G|/ebooks/1575211831/ch14.htm (11 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits Cursors If you use frames in your applets or applications, you can also set the cursor's icon at given moments in your program's execution, to signal wait conditions or other events happening in your program. The getCursorType() and setCursor() methods are defined in the Frame class. If you can get at a Frame object, you can set the cursor (you'll typically set cursors for windows, but you can also set cursors for applets using the getParent() method that I explained in the section "Attaching Dialogs to Applets"). Both of these methods use a set of predefined cursor types in the Frame class. Table 14.1 shows the cursor types you can use (and test for) in your windows. Note Keep in mind that not all platforms use the same cursors. For example, cursors for resizing windows do not exist on Macintoshes. Table 14.1. Cursor types. Class Variable Frame.CROSSHAIR_CURSOR Frame.DEFAULT_CURSOR Frame.E_RESIZE_CURSOR Frame.HAND_CURSOR Frame.MOVE_CURSOR Frame.N_RESIZE_CURSOR Frame.NE_RESIZE_CURSOR Frame.NW_RESIZE_CURSOR Frame.S_RESIZE_CURSOR Frame.SE_RESIZE_CURSOR Frame.SW_RESIZE_CURSOR Frame.TEXT_CURSOR Frame.W_RESIZE_CURSOR Frame.WAIT_CURSOR Cursor A cross-hair (plus-shaped) cursor The default cursor (usually a pointer or arrow) A cursor to indicate something is being resized A hand-shaped cursor (to move an object or the background) A cursor to indicate that something is being moved The top edge of a window is being resized The top-right corner of a window is being resized The top-left corner of a window is being resized The bottom edge of a window is being resized The bottom-right corner of the window is being resized The bottom-left corner of the window is being resized A text-entry cursor (sometimes called an I-beam) The left edge of a window is being resized A long operation is taking place (usually an icon for a watch or an hourglass) Window Events Yesterday you learned about writing your own event handler methods, and you noted that the Event class defines many standard events for which you can test. Window events are part of that list, so if you use windows, these events may be of interest to you, (for example, to hide a window when it's closed, to stop a thread from running when the window is iconified, or to perform some operation when a file is loaded or saved). You can test the id instance variable of the event object in your handleEvent() method to see if any of these events have occurred: if (evt.id == Event.WINDOW_DESTROY) hide(); file:///G|/ebooks/1575211831/ch14.htm (12 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits Table 14.2. shows the various Window events. Table 14.2. Window events from the Event class. Generated when a window is destroyed using the close box or the Close menu item Generated when the window is brought forward from WINDOW_EXPOSE behind other windows Generated when the window is iconified WINDOW_ICONIFY WINDOW_DEICONIFY Generated when the window is restored from an icon Generated when the window is moved WINDOW_MOVED WINDOW_DESTROY Standalone awt Applications After all the space and time I've devoted to creating applets up to this point, you may be surprised that I'm sticking a description of graphical Java applications here at the end, and in a fairly small section at that. The reason for this is that other than a few simple lines of code and in the environment each runs in, there's not a lot of difference between a Java applet and a graphical Java application. Everything you've learned up to this point about the awt including the graphics methods, animation techniques, events, UI components, and windows and dialogs, can be used the same way in Java applications as they can in applets. And applications have the advantage of being "outside the sandbox"-they have none of the security restrictions that applets have. You can do just about anything you want to with an application. So how do you go about creating a graphical Java application? The code to do it is almost trivial. Your main application class should inherit from Frame. If it uses threads (for animation or other processing), it should also implement Runnable: class MyawtApplication extends Frame implements Runnable { ... } Inside the main() method for your application, you create a new instance of your class-because your class extends Frame, that'll give you a new awt window that you can then resize and show as you would any awt window. Inside the constructor method for your class you'll set up the usual awt features for a window that you might usually do in an init() method for an applet: Set the title, add a layout manager, create and add components such as a menu bar or other UI elements, start up a thread, and so on. Here's a simple example: class MyawtApplication extends Frame implements Runnable { MyawtApplication(String title) { super(title); setLayout(new FlowLayout()); add(new Button("OK")); add(new Button("Reset")); add(new Button("Cancel")); } public static void main(String args) { MyawtApplications app = new MyawtApplication("Hi! app.resize(300,300); app.show(); } } file:///G|/ebooks/1575211831/ch14.htm (13 of 24) [11/06/2000 7:45:36 PM] I'm an application"); Day 14 -- Windows, Networking, and Other Tidbits For the most part, you can use any of the methods you've learned about this week to control and manage your application. The only methods you cannot use are those specific to applets (that is, those defined in java.applet.Applet, which includes methods for retrieving URL information and playing audio clips-see the API documentation for that class for more details). Networking in Java Networking is the capability of making connections from your applet or application to a system over the network. Networking in Java involves classes in the java.net package, which provide cross-platform abstractions for simple networking operations, including connecting and retrieving files by using common Web protocols and creating basic UNIX-like sockets. Used in conjunction with input and output streams (which you'll learn much more about next week), reading and writing files over the network becomes as easy as reading or writing to files on the local disk. There are restrictions, of course. Java applets usually cannot read or write from the disk on the machine where the browser is running. Java applets cannot connect to systems other than the one on which they were originally stored. Even given these restrictions, you can still accomplish a great deal and take advantage of the Web to read and process information over the Net. This section describes three ways you can communicate with systems on the Net: q showDocument(), which enables an applet to tell the browser to load and link to another page on the Web q openStream(), a method that opens a connection to a URL and enables you to extract data from that connection q The socket classes, Socket and ServerSocket, which enable you to open standard socket connections to hosts and read to and write from those connections Creating Links Inside Applets Probably the easiest way to use networking inside an applet is to tell the browser running that applet to load a new page. You can use this, for example, to create animated image maps that, when clicked, load a new page. To link to a new page, you create a new instance of the class URL. You saw some of this when you worked with images, but let's go over it a little more thoroughly here. The URL class represents a uniform resource locator. To create a new URL, you can use one of four different forms: q URL(String, String, int, String) creates a new URL object, given a protocol (http, ftp, gopher, file), a hostname (www.lne.com, ftp.netcom.com), a port number (80 for http), and a filename or pathname. q URL(String, String, String) does the same thing as the previous form, minus the port number. q URL(URL, String) creates a URL, given a base path and a relative path. For the base, you can use getDocumentBase() for the URL of the current HTML file, or getCodeBase() for the URL of the Java applet class file. The relative path will be tacked onto the last directory in those base URLs (just like with images and sounds). q URL(String) creates a URL object from a URL string (which should include the protocol, hostname, optional port name, and filename). For the last one (creating a URL from a string), you have to catch a malformed URL exception, so surround the URL constructor with a try...catch: String url = "http://www.yahoo.com/"; try { theURL = new URL(url); } catch ( MalformedURLException e) { System.out.println("Bad URL: " + theURL); } Getting a URL object is the hard part. Once you have one, all you have to do is pass it to the browser. Do this by using this file:///G|/ebooks/1575211831/ch14.htm (14 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits single line of code, where theURL is the URL object to link to: getAppletContext().showDocument(theURL); The browser that contains the Java applet with this code will then load and display the document at that URL. Listing 14.5 shows two classes: ButtonLink and its helper class Bookmark. ButtonLink is a simple applet that displays three buttons that represent important Web locations (the buttons are shown in Figure 14.6). Clicking on the buttons causes the document to be loaded from the locations to which those buttons refer. Figure 14.6 : Bookmark buttons. Listing 14.5. Bookmark buttons. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: // Buttonlink.java starts here import java.awt.*; import java.net.*; public class ButtonLink extends java.applet.Applet { Bookmark bmlist = new Bookmark[3]; public void init() { bmlist[0] = new Bookmark("Laura's Home Page", "http://www.lne.com/lemay/"); bmlist[1] = new Bookmark("Gamelan", "http://www.gamelan.com"); bmlist[2]= new Bookmark("Java Home Page", "http://java.sun.com"); setLayout(new GridLayout(bmlist.length,1, 10, 10)); for (int i = 0; i < bmlist.length; i++) { add(new Button(bmlist[i].name)); } } public boolean action(Event evt, Object arg) { if (evt.target instanceof Button) { linkTo((String)arg); return true; } else return false; } void linkTo(String name) { URL theURL = null; for (int i = 0; i < bmlist.length; i++) { if (name.equals(bmlist[i].name)) theURL = bmlist[i].url; } if (theURL != null) getAppletContext().showDocument(theURL); } file:///G|/ebooks/1575211831/ch14.htm (15 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits 40: } //ButtonLink.java ends here 41: 42: //Bookmark.java starts here 43: import java.net.URL; 44: import java.net.MalformedURLException; 45: 46: class Bookmark { 47: String name; 48: URL url; 49: 50: Bookmark(String name, String theURL) { 51: this.name = name; 52: try { this.url = new URL(theURL); } 53: catch ( MalformedURLException e) { 54: System.out.println("Bad URL: " + theURL); 55: } 56: } 57:} //Bookmark.java ends here Two classes make up this applet: The first, ButtonLink, implements the actual applet itself; the second, Bookmark, is a class representing a bookmark. Bookmarks have two parts: a name and a URL. This particular applet creates three bookmark instances (lines 10 through 15) and stores them in an array of bookmarks (this applet could be easily modified to accept bookmarks as parameters from an HTML file). For each bookmark, a button is created whose label is the value of the bookmark's name. When the buttons are pressed, the linkTo() method is called. linkTo(), defined in lines 31 to 38, extracts the name of the button from the event, uses it to look up the actual URL from the bookmark object, and then tells the browser to load the URL referenced by that bookmark. Opening Web Connections Rather than asking the browser to just load the contents of a file, sometimes you might want to get hold of that file's contents so that your applet can use them. If the file you want to grab is stored on the Web, and can be accessed using the more common URL forms (http, ftp, and so on), your applet can use the URL class to get it. Note that for security reasons, applets can by default connect back only to the same host from which they originally loaded. This means that if you have your applets stored on a system called www.myhost.com, the only machine your applet can open a connection to will be that same host (and that same hostname, so be careful with host aliases). If the file the applet wants to retrieve is on that same system, using URL connections is the easiest way to get it. This security restriction will change how you've been writing and testing applets up to this point. Because we haven't been dealing with network connections, we've been able to do all our testing on the local disk simply by opening the HTML files in a browser or with the appletviewer tool. You cannot do this with applets that open network connections. In order for those applets to work correctly, you must do one of two things: q Run your browser on the same machine that your Web server is running on. If you don't have access to your Web server, you can often install and run a Web server on your local machine. q Upload your class and HTML files to your Web server each time you want to test them. Then, instead of using Open File to test your applets, use the actual URL of the HTML file instead. You'll know when you're not doing things right in regard to making sure your applet, and the connection it's opening, are on the same server. If you try to load an applet or a file from different servers, you'll get a security exception along with a lot of other scary error messages printed to your screen or to the Java console. file:///G|/ebooks/1575211831/ch14.htm (16 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits That said, let's move on to the methods and classes for retrieving files from the Web. openStream() The URL class defines a method called openStream(), which opens a network connection using the given URL (an HTTP connection for Web URLs, an FTP connection for FTP URLs, and so on) and returns an instance of the class InputStream (part of the java.io package). If you convert that stream to a DataInputStream (with a BufferedInputStream in the middle for better performance), you can then read characters and lines from that stream (you'll learn all about streams on Day 19, "Streams and I/O"). For example, these lines open a connection to the URL stored in the variable theURL, and then read and echo each line of the file to the standard output: try { InputStream in = theURL.openStream(); DataInputStream data = new DataInputStream(new BufferedInputStream(in); String line; while ((line = data.readLine()) != null) { System.out.println(line); } } catch (IOException e) { System.out.println("IO Error: " + e.getMessage()); } Note You need to wrap all those lines in a try...catch statement to catch IOExceptions generated. You'll learn more about IOExceptions and the try and catch statements on Day 17, "Exceptions." Here's an example of an applet that uses the openStream() method to open a connection to a Web site, reads a file from that connection (Edgar Allen Poe's poem "The Raven"), and displays the result in a text area. Listing 14.6 shows the code; Figure 14.7 shows the result after the file has been read. Figure 14.7 : The GetRaven applet. An important note: If you compile this code as written, it won't work-you'll get a security exception. The reason is that this applet opens a connection to the server www.lne.com to get the file raven.txt. When you compile and run this applet, that applet isn't running on www.lne.com (unless you're me, and I already know about this problem). Before you compile this applet, make sure you change line 18 to point to a copy of raven.txt on your server, and install your applet and your HTML files on that same server (you can get raven.txt from the CD or from that very URL). Alternately, you can use your browser to point to the URL http://www.lne.com/Web/JavaProf/GetRaven.html. That Web page loads this very applet and downloads the file correctly. Because both the applet and the text file are on the same server, it works just fine. Listing 14.6. The GetRaven class. 1: 2: 3: 4: 5: import import import import import java.awt.*; java.io.DataInputStream; java.io.BufferedInputStream; java.io.IOException; java.net.URL; file:///G|/ebooks/1575211831/ch14.htm (17 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits 6: import java.net.URLConnection; 7: import java.net.MalformedURLException; 8: 9: public class GetRaven extends java.applet.Applet implements Runnable { 10: URL theURL; 11: Thread runner; 12: TextArea ta = new TextArea("Getting text..."); 13: 14: public void init() { 15: setLayout(new GridLayout(1,1)); 16: 17: // chANGE THIS NEXT LINE BEFORE COMPILING!!! 18: String url = "http://www.lne.com/Web/JavaProf/raven.txt"; 19: try { this.theURL = new URL(url); } 20: catch ( MalformedURLException e) { 21: System.out.println("Bad URL: " + theURL); 22: } 23: add(ta); 24: } 25: 26: public Insets insets() { 27: return new Insets(10,10,10,10); 28: } 29: 30: public void start() { 31: if (runner == null) { 32: runner = new Thread(this); 33: runner.start(); 34: } 35: } 36: 37: public void stop() { 38: if (runner != null) { 39: runner.stop(); 40: runner = null; 41: } 42: } 43: 44: public void run() { 45: URLConnection conn = null; 46: DataInputStream data = null; 47: String line; 48: StringBuffer buf = new StringBuffer(); 49: 50: try { 51: conn = this.theURL.openConnection(); 52: conn.connect(); 53: ta.setText("Connection opened..."); 54: data = new DataInputStream(new BufferedInputStream( 55: conn.getInputStream())); 56: ta.setText("Reading data..."); 57: while ((line = data.readLine()) != null) { 58: buf.append(line + "\n"); file:///G|/ebooks/1575211831/ch14.htm (18 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits 59: 60: 61: 62: 63: 64: 65:} 66:} } ta.setText(buf.toString()); } catch (IOException e) { System.out.println("IO Error:" + e.getMessage()); } The init() method (lines 14 to 24) sets up the URL and the text area in which that file will be displayed. The URL could be easily passed into the applet via an HTML parameter; here, it's just hard coded for simplicity. Because it might take some time to load the file over the network, you put that routine into its own thread and use the familiar start(), stop(), and run() methods to control that thread. Inside run() (lines 44 to 64), the work takes place. Here, you initialize a bunch of variables and then open the connection to the URL (using the openStream() method in line 50). Once the connection is open, you set up an input stream in lines 51 to 55 and read from it, line by line, putting the result into an instance of StringBuffer (a string buffer is a modifiable string). I put all this work into a thread because it may take some time for the connection to open and for the file to be read-particularly across slower connections. There may be other things going on in the applet that need to take place concurrently to the file loading. Once all the data has been read, line 60 converts the StringBuffer object into a real string and then puts that result in the text area. One other thing to note about this example is that the part of the code that opened a network connection, read from the file, and created a string is surrounded by a try and catch statement. If any errors occur while you're trying to read or process the file, these statements enable you to recover from them without the entire program crashing (in this case, the program exits with an error, because there's little else to be done if the applet can't read the file). try and catch give you the capability of handling and recovering from errors. You'll learn more about exceptions on Day 17. Sockets For networking applications beyond what the URL and URLconnection classes offer (for example, for other protocols or for more general networking applications), Java provides the Socket and ServerSocket classes as an abstraction of standard socket programming techniques. You'll learn more about working with Java sockets on Day 26, "Client/Server Networking in Java," but for now here's a very short rundown of the socket capabilities in Java. The Socket class provides a client-side socket interface similar to standard UNIX sockets. To open a connection, create a new instance of Socket (where hostname is the host to connect to, and portnum is the port number): Socket connection = new Socket(hostname, portnum); Note If you use sockets in an applet, you are still subject to the applet security restrictions that prevent you from connecting to any system other than the same one the applet came from. Once the socket is open, you can use input and output streams to read and write from that socket (you'll learn all about input and output streams on Day 19): file:///G|/ebooks/1575211831/ch14.htm (19 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits DataInputStream in = new DataInputStream( new BufferedInputStream(connection.getInputStream())); DataOutputStream out= new DataOutputStream( new BufferedOutputStream(connection.getOutputStream())); Once you're done with the socket, don't forget to close it (this also closes all the input and output streams you may have set up for that socket): connection.close(); Server-side sockets work similarly, with the exception of the accept() method. A server socket listens on a TCP port for a connection from a client; when a client connects to that port, the accept() method accepts a connection from that client. By using both client and server sockets, you can create applications that communicate with each other over the network. To create a server socket and bind it to a port, create a new instance of ServerSocket with the port number: ServerSocket sconnection = new ServerSocket(8888); To listen on that port (and to accept a connection from any clients if one is made), use the accept() method: sconnection.accept(); Once the socket connection is made, you can use input and output streams to read from and write to the client. See the java.net package for more information about Java sockets. Changes to Sockets for Java 1.1 In the 1.0.2 version of Java, the Socket and ServerSocket classes provide a basic abstract socket implementation. You can create new instances of these classes to make or accept connections and to pass data back and forth from a client to a server. The problem comes when you try to extend or change Java's socket behavior. The Socket and ServerSocket classes in the java.net package are final classes, which means you cannot create subclasses of those classes (you'll learn more about finalizing classes on Day 15, "Modifiers, Access Control, and Class Design"). To extend the behavior of the socket classes- for example, to allow network connections to work across a firewall or a proxy, you can use the abstract classes SocketImpl and the interface SocketImplFactory to create a new transport-layer socket implementation. This design fits with the original goal of Java's socket classes: to allow those classes to be portable to other systems with different transport mechanisms. The problem with this mechanism is that while it works for simple cases, it prevents you from adding other protocols on top of TCP (for example, to implement an encryption mechanism such as SSL) or for having multiple socket implementations per Java runtime. For these reasons, in Java 1.1 sockets will change such that the Socket and ServerSocket classes are nonfinal and extendable. You will be able to create subclasses of these classes in Java 1.1, which use either the default socket implementation or one of your own making. This will allow much more flexible network capabilities to Java in 1.1. In addition, Java 1.1 has added several other new features to the java.net package: q New options for sockets, based on BSD's socket options (for example, TCP_NODELAY, IP_MULTICAST_LOOP, SO_BINDADDR) q Many new subclasses of the SocketException class, to represent network errors on a finer level of granularity than in Java 1.0.2 (for example, NoRouteToHostException or ConnectException) For more information about all the networking changes between Java 1.02 and 1.1, see the pages at file:///G|/ebooks/1575211831/ch14.htm (20 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits http://java.sun.com/products/JDK/1.1/designspecs/net/index.html. Other Applet Hints On this, the last section of the last day of the second week, let's finish with some small hints that didn't fit in anywhere else: using showStatus() to print messages in the browser status window, providing applet information, and communicating between multiple applets on the same page. The showStatus() Method The showStatus() method, available in the Applet class, enables you to display a string in the status bar of the browser, which contains the applet. You can use this for printing error, link, help, or other status messages: getAppletContext().showStatus("Change the color"); The getAppletContext() method enables your applet to access features of the browser that contains it. You already saw a use of this with links, wherein you could use the showDocument() method to tell the browser to load a page. showStatus() uses that same mechanism to print status messages. Note showStatus() may not be supported in all browsers, so do not depend on it for your applet's functionality or interface. It is a useful way of communicating optional information to your user-if you need a more reliable method of communication, set up a label in your applet and update it to reflect changes in its message. Applet Information The awt gives you a mechanism for associating information with your applet. Usually, there is a mechanism in the browser viewing the applet to view display information. You can use this mechanism to sign your name or your organization to your applet, or to provide contact information so that users can get hold of you if they want. To provide information about your applet, override the getAppletInfo() method: public String getAppletInfo() { return "GetRaven copyright 1995 Laura Lemay"; } Communicating Between Applets Sometimes you want to have an HTML page that has several different applets on it. To do this, all you have to do is include several different iterations of the applet tag. The browser will create different instances of your applet for each one that appears on the HTML page. What if you want to communicate between those applets? What if you want a change in one applet to affect the other applets in some way? The best way to do this is to use the applet context to get to different applets on the same page. Note file:///G|/ebooks/1575211831/ch14.htm (21 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits Be forewarned that before you do extensive work with inter-applet communication, the mechanism described in this section is implemented differently (and often unreliably) in different browsers and different Java environments. If you need to rely on communicating between applets for your Web pages, make sure you test those applets extensively in different browsers on different platforms. The applet context is defined in a class called, appropriately, AppletContext. To get an instance of this class for you applet, you use the getAppletContext() method. You've already seen the use of the getAppletContext() method for other uses; you can also use it to get hold of the other applets on the page. For example, to call a method named sendMessage() on all the applets on a page (including the current applet), use the getApplets() method and a for loop that looks something like this: for (Enumeration e = getAppletContext().getApplets(); e.hasMoreElements();) { Applet current = (MyAppletSubclass)(e.nextElement()); current.sendMessage(); } The getApplets() method returns an Enumeration object with a list of the applets on the page. Iterating over the Enumeration object in this way enables you to access each element in the Enumeration in turn. Note that each element in the Enumeration object is an instance of the Object class; to get that applet to behave the way you want it to (and accept messages from other applets), you'll have to cast it to be an instance of your applet subclass (here, the class MyAppletSubclass). If you want to call a method in a specific applet, it's slightly more complicated. To do this, you give your applets a name and then refer to them by name inside the body of code for that applet. To give an applet a name, use the NAME attribute to <APPLET> in your HTML file: <P>This applet sends information: <APPLET CODE="MyApplet.class" WIDTH=100 HEIGHT=150 NAME="sender"> </APPLET> <P>This applet receives information from the sender: <APPLET CODE="MyApplet.class" WIDTH=100 HEIGHT=150 NAME="receiver"> </APPLET> To get a reference to another applet on the same page, use the getApplet() method from the applet context with the name of that applet. This gives you a reference to the applet of that name. You can then refer to that applet as if it were just another object: call methods, set its instance variables, and so on. Here's some code to do just that: // get ahold of the receiver applet Applet receiver = (MyAppletSubclass)getAppletContext().getApplet("receiver"); // tell it to update itself. receiver.update(text, value); In this example you use the getApplet() method to get a reference to the applet with the name receiver. Note that the object returned by getApplet is an instance of the generic Applet class; you'll most likely want to cast that object to an instance of your subclass. Given the reference to the named applet, you can then call methods in that applet as if it were just another object in your own environment. Here, for example, if both applets have an update() method, you can tell receiver to update itself by using the information the current applet has. Naming your applets and then referring to them by using the methods described in this section enables your applets to communicate and stay in sync with each other, providing uniform behavior for all the applets on your page. file:///G|/ebooks/1575211831/ch14.htm (22 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits Summary Congratulations! Take a deep breath-you're finished with Week 2. This week has been full of useful information about creating applets and using the Java awt classes to display, draw, animate, process input, and create fully fledged interfaces in your applets. Today you finished exploring applets and the awt by learning about three concepts. First, you learned about windows, frames, menus, and dialogs, which enable you to create a framework for your applets-or enable your Java applications to take advantage of applet features. Second, you had a brief introduction to Java networking through some of the classes in the java.net package. Applet networking includes things as simple as pointing the browser to another page from inside your applet, but can also include retrieving files from the Web by using standard Web protocols (http, ftp, and so on). For more advanced networking capabilities, Java provides basic socket interfaces that can be used to implement many basic network-oriented applets-client/server interactions, chat sessions, and so on. Finally, you finished up with the tidbits-small features of the Java awt and of applets that didn't fit anywhere else, including showStatus(), providing information about your applet, and communicating between multiple applets on a single page. Q&A Q: A: Q: A: Q: A: When I create pop-up windows, they all show up with this big yellow bar that says Warning: applet window. What does this mean? The warning is to tell you (and the users of your applet) that the window being displayed was generated by an applet, and not by the browser itself. This is a security feature to keep an applet programmer from popping up a window that masquerades as a browser window and, for example, asks users for their passwords. There's nothing you can do to hide or obscure the warning. What good is having a file dialog box if you can't read or write files from the local file system? Applets often can't read or write from the local file system (depending on the browser), but because you can use awt components in Java applications as well as applets, the file dialog box is also very useful for them. How can I mimic an HTML form submission in a Java applet? Currently, applets make it difficult to do this. The best (and easiest way) is to use GET notation to get the browser to submit the form contents for you. http://www.blah.com/cgi-bin/myscript?foo=1&bar=2&name=Laura Q: Because the form input is encoded in the URL, you can write a Java applet to mimic a form, get input from the user, and then construct a new URL object with the form data included on the end. Then just pass that URL to the browser by using getAppletContext().showDocument(), and the browser will submit the form results itself. For simple forms, this is all you need. How can I do POST form submissions? file:///G|/ebooks/1575211831/ch14.htm (23 of 24) [11/06/2000 7:45:36 PM] Day 14 -- Windows, Networking, and Other Tidbits A: You'll have to mimic what a browser does to send forms using POST: Open a socket to the server and send the data, which looks something like this (the exact format is determined by the HTTP protocol; this is only a subset of it): POST /cgi-bin/mailto.cgi HTTP/1.0 Content-type: application/x-www-form-urlencoded Content-length: 36 {your encoded form data here} Q: A: Q: A: Q: A: If you've done it right, you get the CGI form output back from the server. It's then up to your applet to handle that output properly. Note that if the output is in HTML, there really isn't a way to pass that output to the browser that is running your applet yet. This capability may end up in future Java releases. If you get back a URL, however, you can redirect the browser to that URL. showStatus() doesn't work in my browser. How can I give my readers status information? As you learned in the section on showStatus(), whether or not a browser supports showStatus() is up to that browser. If you must have status-like behavior in your applet, consider creating a status label in the applet itself that is updated with the information you need to present. I've been trying to communicate between two applets in my Web page using the getAppletContext() and getApplet() methods. My applets keep crashing with NullPointerException errors. What does this mean? The mechanism I described for communicating between applets is how Sun and the Java class library says it's supposed to work. However, like showStatus(), whether or not a browser implements that mechanism, or implements it correctly, depends on that browser. Version of Netscape before 3.0 and Internet Explorer both have strange problems with inter-applet communication. It looks like the openStream() method and the Socket classes implement TCP sockets. Does Java support UDP (User Datagram Protocol, often just called datagram) sockets? The JDK 1.0 provides two classes, DatagramSocket and DatagramPacket, which implement UDP sockets. The DatagramSocket class operates similarly to the Socket class. Use instances of DatagramPacket for each packet you send or receive over the socket. See the API documentation for the java.net package for more information. file:///G|/ebooks/1575211831/ch14.htm (24 of 24) [11/06/2000 7:45:36 PM] file:///G|/ebooks/1575211831/f14-1.gif file:///G|/ebooks/1575211831/f14-1.gif [11/06/2000 7:45:37 PM] file:///G|/ebooks/1575211831/f14-2.gif file:///G|/ebooks/1575211831/f14-2.gif [11/06/2000 7:45:37 PM] file:///G|/ebooks/1575211831/f14-3.gif file:///G|/ebooks/1575211831/f14-3.gif [11/06/2000 7:45:38 PM] file:///G|/ebooks/1575211831/f14-4.gif file:///G|/ebooks/1575211831/f14-4.gif [11/06/2000 7:45:38 PM] file:///G|/ebooks/1575211831/f14-5.gif file:///G|/ebooks/1575211831/f14-5.gif [11/06/2000 7:45:38 PM] file:///G|/ebooks/1575211831/f14-6.gif file:///G|/ebooks/1575211831/f14-6.gif [11/06/2000 7:45:39 PM] Day 19 -- Streams and I/O Day 19 Streams and I/O by Charles L. Perkins and Laura Lemay CONTENTS q What Are Streams? q The java.io Package q Input Streams r r ByteArrayInputStream r FileInputStream r FilterInputStream r PipedInputStream r SequenceInputStream r q The Abstract Class InputStream StringBufferInputStream Output Streams r The Abstract Class OutputStream r ByteArrayOutputStream r FileOutputStream r FilterOutputStream r PipedOutputStream q Related Classes q Object Serialization (Java 1.1) q Summary q Q&A The package java.io, part of the standard Java class library, provides a large number of classes designed for handling input and output to files, network connections, and other sources. These I/O classes are known as streams, and provide functionality for reading and writing data in various ways. You got a glimpse of these classes on Day 14, "Windows, Networking, and Other Tidbits," when we opened a network connection to a file and read the contents into an applet. Today you'll explore Java's input and output classes: q Input streams-and how to create, use, and detect the end of them-and filtered input streams, which can be nested to great effect q Output streams, which are mostly analogous to (but the inverse of) input streams You'll also learn about two stream interfaces that make the reading and writing of typed streams much easier (as well as about several utility classes used to access the file system). file:///G|/ebooks/1575211831/ch19.htm (1 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O What Are Streams? A stream is a path of communication between the source of some information and its destination. This information can come from a file, the computer's memory, or even from the Internet. In fact, the source and destination of a stream are completely arbitrary producers and consumers of bytes, respectively-you don't need to know about the source of the information when reading from a stream, and you don't need to know about the final destination when writing to one. A stream is a path of communication between a source of information and its destination. For example, an input stream allows you to read data from a source, and an output stream allows you to write data to a destination. General-purpose methods that can read from any source accept a stream argument to specify that source; general-purpose methods for writing accept a stream to specify the destination. Arbitrary processors of data commonly have two stream arguments. They read from the first, process the data, and write the results to the second. These processors have no idea of either the source or the destination of the data they are processing. Sources and destinations can vary widely: from two memory buffers on the same local computer, to the ELF (extremely low frequency) transmissions to and from a submarine at sea, to the real-time data streams of a NASA probe in deep space. By decoupling the consuming, processing, or producing of data from the sources and destinations of that data, you can mix and match any combination of them at will as you write your program. In the future, when new, previously nonexistent forms of source or destination (or consumer, processor, or producer) appear, they can be used within the same framework, with no changes to your classes. In addition, new stream abstractions, supporting higher levels of interpretation "on top of" the bytes, can be written completely independently of the underlying transport mechanisms for the bytes themselves. The java.io Package All the classes you will learn about today are part of the package java.io. To use any of these classes in your own programs, you will need to import each individual class or to import the entire java.io package, like this: import java.io.InputStream; import java.io.FilteredInputStream; import java.io.FileOutputStream; import java.io.*; All the methods you will explore today are declared to throw IOExceptions. This new subclass of Exception conceptually embodies all the possible I/O errors that might occur while using streams. Several subclasses of it define a few, more specific exceptions that can be thrown as well. For now, it is enough to know that you must either catch an IOException, or be in a method that can "pass it along," to be a well-behaved user of streams. The foundations of this stream framework in the Java class hierarchy are the two abstract classes, InputStream and OutputStream. Inheriting from these classes is a virtual cornucopia of categorized subclasses, demonstrating the wide range of streams in the system, but also demonstrating an extremely well-designed hierarchy of relationships between these streams-one well worth learning from. Let's begin with the parents, InputStream and OutputStream, and then work our way down this bushy tree. Input Streams Input streams are streams that allow you to read data from a source. These include the root abstract class InputStream, filtered streams, buffered streams, and streams that read from files, strings, and byte arrays. file:///G|/ebooks/1575211831/ch19.htm (2 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O The Abstract Class InputStream InputStream is an abstract class that defines the fundamental ways in which a destination (consumer) reads a stream of bytes from some source. The identity of the source, and the manner of the creation and transport of the bytes, is irrelevant. When using an input stream, you are the destination of those bytes, and that's all you need to know. Note All input streams descend from InputStream. All share in common the few methods described in this section. Thus, the streams used in these examples can be any of the more complex input streams described in the next few sections. read() The most important method to the consumer of an input stream is the one that reads bytes from the source. This method, read(), comes in many flavors, and each is demonstrated in an example in today's lesson. Each of these read() methods is defined to "block" (wait) until all the input requested becomes available. Don't worry about this limitation; because of multithreading, you can do as many other things as you like while this one thread is waiting for input. In fact, it is a common idiom to assign a thread to each stream of input (and for each stream of output) that is solely responsible for reading from it (or writing to it). These input threads might then "hand off" the information to other threads for processing. This naturally overlaps the I/O time of your program with its compute time. Here's the first form of read(): InputStream byte s = getAnInputStreamFromSomewhere(); buffer = new byte[1024]; // any size will do if (s.read(buffer) != buffer.length) System.out.println("I got less than I expected."); Note Here and throughout the rest of today's lesson, assume that either an import java.io.* appears before all the examples or that you mentally prefix all references to java.io classes with the prefix java.io. This form of read() attempts to fill the entire buffer given. If it cannot (usually due to reaching the end of the input stream), it returns the actual number of bytes that were read into the buffer. After that, any further calls to read() return -1, indicating that you are at the end of the stream. Note that the if statement still works even in this case, because -1 != 1024 (this corresponds to an input stream with no bytes in it at all). Note Don't forget that, unlike in C, the -1 case in Java is not used to indicate an error. Any I/O errors throw instances of IOException (which you're not catching yet). You learned on Day 17, "Exceptions," that all uses of distinguished values can be replaced by the use of exceptions, and so they should. The -1 in the last example is a bit of a historical anachronism. You'll soon see a better approach to indicating the end of the stream using the class DataInputStream. file:///G|/ebooks/1575211831/ch19.htm (3 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O You can also read into a "slice" of your buffer by specifying the offset into the buffer, and the length desired, as arguments to read(): s.read(buffer, 100, 300); This example tries to fill in bytes 100 through 399 and behaves otherwise exactly the same as the previous read() method. Finally, you can read in bytes one at a time: InputStream byte int s = getAnInputStreamFromSomewhere(); b; byteOrMinus1; while ((byteOrMinus1 = s.read()) != -1) { b = (byte) byteOrMinus1; ... // process the byte b } ... // reached end of stream Note Because of the nature of integer promotion in Java in general, and because in this case the read() method returns an int, using the byte type in your code may be a little frustrating. You'll find yourself constantly having to explicitly cast the result of arithmetic expressions, or of int return values, back to your size. Because read() really should be returning a byte in this case, we feel justified in declaring and using it as such (despite the pain)-it makes the size of the data being read clearer. In cases where you feel that the range of a variable is naturally limited to a byte (or a short) rather than an int, please take the time to declare it that way and pay the small price necessary to gain the added clarity. By the way, a lot of the Java class library code simply stores the result of read() in an int. skip() What if you want to skip over some of the bytes in a stream, or start reading a stream from other than its beginning? A method similar to read() does the trick: if (s.skip(1024) != 1024) System.out.println("I skipped less than I expected."); This example skips over the next 1024 bytes in the input stream. However, the implementation of skip() in InputStream may skip fewer bytes than the given argument, and so it returns a long integer representing the number of bytes it actually skipped. In this example, therefore, a message is printed if the actual number of bytes skipped is less than 1024. Note file:///G|/ebooks/1575211831/ch19.htm (4 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O The API documentation for skip() in the InputStream class says that skip() behaves this way for "a variety of reasons." Subclasses of InputStream should override this default implementation of skip() if they want to handle skipping more properly. available() If for some reason you would like to know how many bytes are in the stream right now, you can ask the following: if (s.available() < 1024) System.out.println("Too little is available right now."); This tells you the number of bytes that you can read without blocking. Because of the abstract nature of the source of these bytes, streams may or may not be able to tell you a reasonable answer to this question. For example, some streams always return 0. Unless you use specific subclasses of InputStream that you know provide a reasonable answer to this question, it's not a good idea to rely on this method. Remember that multithreading eliminates many of the problems associated with blocking while waiting for a stream to fill again. Thus, one of the strongest rationales for the use of available() goes away. mark() and reset() Some streams support the notion of marking a position in the stream and then later resetting the stream to that position to reread the bytes there. Clearly, the stream would have to "remember" all those bytes, so there is a limitation on how far apart in a stream the mark and its subsequent reset can occur. There's also a method that asks whether the stream supports the notion of marking at all. Here's an example: InputStream s = getAnInputStreamFromSomewhere(); if (s.markSupported()) { // does s support the notion? ... // read the stream for a while s.mark(1024); ... // read less than 1024 more bytes s.reset(); ... // we can now re-read those bytes } else { ... // no, perform some alternative } When marking a stream, you specify the maximum number of bytes you intend to allow to pass before resetting it. This allows the stream to limit the size of its byte "memory." If this number of bytes goes by and you have not yet used reset(), the mark becomes invalid, and attempting to use reset() will throw an exception. Marking and resetting a stream is most valuable when you are attempting to identify the type of the stream (or the next part of the stream), but to do so, you must consume a significant piece of it in the process. Often, this is because you have several black-box parsers that you can hand the stream to, but they will consume some (unknown to you) number of bytes before making up their mind about whether the stream is of their type. Set a large size for the limit in mark(), and let each parser run until it either throws an error or completes a successful parse. If an error is thrown, use reset() and try the next parser. close() Because you don't know what resources an open stream represents, nor how to deal with them properly when you're file:///G|/ebooks/1575211831/ch19.htm (5 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O finished reading the stream, you should (usually) explicitly close down a stream so that it can release these resources. Of course, garbage collection and a finalization method can do this for you, but what if you need to reopen that stream or those resources before they have been freed by this asynchronous process? At best, this is annoying or confusing; at worst, it introduces an unexpected, obscure, and difficult-to-track-down bug. Because you're interacting with the outside world of external resources, it's safer to be explicit about when you're finished using them: InputStream s = alwaysMakesANewInputStream(); try { ... // use s to your heart's content } finally { s.close(); } Get used to this idiom (using finally); it's a useful way to be sure something (such as closing the stream) always gets done. Of course, you're assuming that the stream is always successfully created. If this is not always the case, and null is sometimes returned instead, here's the correct way to be safe: InputStream s = tryToMakeANewInputStream(); if (s != null) { try { ... } finally { s.close(); } } ByteArrayInputStream The "inverse" of some of the previous examples would be to create an input stream from an array of bytes. This is exactly what ByteArrayInputStream does: byte buffer = new byte[1024]; fillWithUsefulData(buffer); InputStream s = new ByteArrayInputStream(buffer); Readers of the new stream s see a stream 1024 bytes long, containing the bytes in the array buffer. Just as read() has a form that takes an offset and a length, so does this class's constructor: InputStream s = new ByteArrayInputStream(buffer, 100, 300); Here the stream is 300 bytes long and consists of bytes 100-399 from the array buffer. Note Finally, you've seen your first examples of the creation of a stream. These new streams are attached to the simplest of all possible sources of data: an array of bytes in the memory of the local computer. ByteArrayInputStreams simply implement the standard set of methods that all input streams do. Here, however, the available() method has a particularly simple job-it returns 1024 and 300, respectively, for the two instances of file:///G|/ebooks/1575211831/ch19.htm (6 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O ByteArrayInputStream you created previously, because it knows exactly how many bytes are available. Finally, calling reset() on a ByteArrayInputStream resets it to the beginning of the stream (buffer), no matter where the mark is set. FileInputStream One of the most common uses of streams, and historically the earliest, is to attach them to files in the file system. Here, for example, is the creation of such an input stream on a UNIX system: InputStream s = new FileInputStream("/some/path/and/fileName"); Warning Applets attempting to open, read, or write streams based on files in the file system will usually cause security exceptions to be thrown from the browser. If you're developing applets, you won't be able to depend on files at all, and you'll have to use your server to hold shared information. (Standalone Java programs have none of these problems, of course.) You also can create the stream from a previously opened file descriptor (an instance of the FileDescriptor class). Usually, you get file descriptors using the getFD() method on FileInputStream or FileOutputStream classes, so, for example, you could use the same file descriptor to open a file for reading and then reopen it for writing: FileDescriptor InputStream s fd = someFileStream.getFD(); = new FileInputStream(fd); In either case, because it's based on an actual (finite length) file, the input stream created can implement available() precisely and can skip like a champ (just as ByteArrayInputStream can, by the way). In addition, FileInputStream knows a few more tricks: FileInputStream FileDescriptor aFIS = new FileInputStream("aFileName"); myFD = aFIS.getFD(); // get a file descriptor aFIS.finalize(); // will call close() when automatically called by GC Tip To call these new methods, you must declare the stream variable aFIS to be of type FileInputStream, because plain InputStreams don't know about them. The first is obvious: getFD() returns the file descriptor of the file on which the stream is based. The second, though, is an interesting shortcut that allows you to create FileInputStreams without worrying about closing them later. FileInputStream's implementation of finalize(), a protected method, closes the stream. Unlike in the contrived call in comments, you almost never can nor should call a finalize() method directly. The garbage collector calls it after noticing that the stream is no longer in use, but before actually destroying the stream. Thus, you can go merrily along using the stream, never closing it, and all will be well. The system takes care of closing it (eventually). You can get away with this because streams based on files tie up very few resources, and these resources cannot be file:///G|/ebooks/1575211831/ch19.htm (7 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O accidentally reused before garbage collection (these were the things worried about in the previous discussion of finalization and close()). Of course, if you were also writing to the file, you would have to be more careful. (Reopening the file too soon after writing might make it appear in an inconsistent state because the finalize()-and thus the close()-might not have happened yet.) Just because you don't have to close the stream doesn't mean you might not want to do so anyway. For clarity, or if you don't know precisely what type of an InputStream you were handed, you might choose to call close() yourself. FilterInputStream This "abstract" class simply provides a "pass-through" for all the standard methods of InputStream. (It's "abstract," in quotes, because it's not technically an abstract class; you can create instances of it. In most cases, however, you'll use one of the more useful subclasses of FilterInputStream instead of FilterInputStream itself.) FilterInputStream holds inside itself another stream, by definition one further "down" the chain of filters, to which it forwards all method calls. It implements nothing new but allows itself to be nested: InputStream FilterInputStream FilterInputStream FilterInputStream s s1 s2 s3 = = = = getAnInputStreamFromSomewhere(); new FilterInputStream(s); new FilterInputStream(s1); new FilterInputStream(s2); ... s3.read() ... Whenever a read is performed on the filtered stream s3, it passes along the request to s2, then s2 does the same to s1, and finally s is asked to provide the bytes. Subclasses of FilterInputStream will, of course, do some nontrivial processing of the bytes as they flow past. The rather verbose form of "chaining" in the previous example can be made more elegant: s3 = new FilterInputStream(new FilterInputStream(new FilterInputStream(s))); You should use this idiom in your code whenever you can. It clearly expresses the nesting of chained filters, and can easily be parsed and "read aloud" by starting at the innermost stream s and reading outward-each filter stream applying to the one within-until you reach the outermost stream s3. Now let's examine each of the subclasses of FilterInputStream in turn. BufferedInputStream This is one of the most valuable of all streams. It implements the full complement of InputStream's methods, but it does so by using a buffered array of bytes that acts as a cache for future reading. This decouples the rate and the size of the "chunks" you're reading from the more regular, larger block sizes in which streams are most efficiently read (from, for example, peripheral devices, files in the file system, or the network). It also allows smart streams to read ahead when they expect that you will want more data soon. Because the buffering of BufferedInputStream is so valuable, and it's also the only class to handle mark() and reset() properly, you might wish that every input stream could somehow share its valuable capabilities. Normally, because those stream classes do not implement them, you would be out of luck. Fortunately, you already saw a way that filter streams can wrap themselves "around" other streams. Suppose that you would like a buffered FileInputStream that can handle marking and resetting correctly. Et voilà: InputStream s = new BufferedInputStream(new FileInputStream("foo")); You have a buffered input stream based on the file foo that can use mark() and reset(). Now you can begin to see the power of nesting streams. Any capability provided by a filter input stream (or output file:///G|/ebooks/1575211831/ch19.htm (8 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O stream, as you'll see soon) can be used by any other basic stream via nesting. Of course, any combination of these capabilities, and in any order, can be as easily accomplished by nesting the filter streams themselves. DataInputStream All the methods that instances of this class understand are defined in a separate interface, which both DataInputStream and RandomAccessFile (another class in java.io) implement. This interface is general-purpose enough that you might want to use it yourself in the classes you create. It is called DataInput. The DataInput Interface When you begin using streams to any degree, you'll quickly discover that byte streams are not a really helpful format into which to force all data. In particular, the primitive types of the Java language embody a rather nice way of looking at data, but with the streams you've been defining thus far in this book, you could not read data of these types. The DataInput interface specifies a higher-level set of methods that, when used for both reading and writing, can support a more complex, typed stream of data. Here are the methods this interface defines: void void int readFully(byte readFully(byte skipBytes(int n) buffer) buffer, int offset, int boolean byte int short int char int long float double readBoolean() readByte() readUnsignedByte() readShort() readUnsignedShort() readChar() readInt() readLong() readFloat() readDouble() throws throws throws throws throws throws throws throws throws throws String String readLine() readUTF() throws IOException; length) throws IOException; throws IOException; IOException; IOException; IOException; IOException; IOException; IOException; IOException; IOException; IOException; IOException; throws IOException; throws IOException; The first three methods are simply new names for skip() and the two forms of read() you've seen previously. Each of the next 10 methods reads in a primitive type or its unsigned counterpart (useful for using every bit efficiently in a binary stream). These latter methods must return an integer of a wider size than you might think; because integers are signed in Java, the unsigned value does not fit in anything smaller. The final two methods read a newline ('\r', '\n', or "\r\n") terminated string of characters from the stream-the first in ASCII, and the second in Unicode. Now that you know what the interface that DataInputStream implements looks like, let's see it in action: DataInputStream long s = new DataInputStream(myRecordInputStream()); size = s.readLong(); while (size-- > 0) { if (s.readBoolean()) { int anInteger int magicBitFlags double aDouble // the number of items in the stream // should I process this item? = s.readInt(); = s.readUnsignedShort(); = s.readDouble(); file:///G|/ebooks/1575211831/ch19.htm (9 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O if ((magicBitFlags & 0100000) != 0) { ... // high bit set, do something special } ... // process anInteger and aDouble } } Because the class implements an interface for all its methods, you can also use the following interface: DataInput String d = new DataInputStream(new FileInputStream("anything")); line; while ((line = d.readLine()) != null) { ... // process the line } EOFException One final point about most of DataInputStream's methods: When the end of the stream is reached, the methods throw an EOFException. This is tremendously useful and, in fact, allows you to rewrite all the kludgey uses of -1 you saw earlier today in a much nicer fashion: DataInputStream s = new DataInputStream(getAnInputStreamFromSomewhere()); try { while (true) { byte b = (byte) s.readByte(); ... // process the byte b } } catch (EOFException e) { ... // reached end of stream } finally { s.close(); } This works just as well for all but the last two of the read methods of DataInputStream. Warning skipBytes() does nothing at all on end of stream, readLine() returns null, and readUTF() might throw a UTFDataFormatException, if it notices the problem at all. LineNumberInputStream In an editor or a debugger, line numbering is crucial. To add this valuable capability to your programs, use the filter stream LineNumberInputStream, which keeps track of line numbers as its stream "flows through" it. It's even smart enough to remember a line number and later restore it, during a mark() and reset(). You might use this class as follows: LineNumberInputStream aLNIS; aLNIS = new LineNumberInputStream(new FileInputStream("source")); DataInputStream s = new DataInputStream(aLNIS); file:///G|/ebooks/1575211831/ch19.htm (10 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O String line; while ((line = s.readLine()) != null) { ... // process the line System.out.println("Did line number: " + aLNIS.getLineNumber()); } Here, two filter streams are nested around the FileInputStream actually providing the data-the first to read lines one at a time and the second to keep track of the line numbers of these lines as they go by. You must explicitly name the intermediate filter stream, aLNIS, because if you did not, you couldn't call getLineNumber() later. Note that if you invert the order of the nested streams, reading from DataInputStream does not cause LineNumberInputStream to "see" the lines. You must put any filter streams acting as "monitors" in the middle of the chain and "pull" the data from the outermost filter stream so that the data will pass through each of the monitors in turn. In the same way, buffering should occur as far inside the chain as possible, because the buffered stream won't be able to do its job properly unless most of the streams that need buffering come after it in the flow. For example, here's a silly order: new BufferedInputStream(new LineNumberInputStream( _new DataInputStream(new FileInputStream("foo")); and here's a much better order: new DataInputStream(new LineNumberInputStream( _new BufferedInputStream(new FileInputStream("foo")); LineNumberInputStreams can also be told setLineNumber(), for those few times when you know more than they do. PushbackInputStream The filter stream class PushbackInputStream is commonly used in parsers, to "push back" a single character in the input (after reading it) while trying to determine what to do next-a simplified version of the mark() and reset() utility you learned about earlier. Its only addition to the standard set of InputStream methods is unread(), which, as you might guess, pretends that it never read the byte passed in as its argument, and then gives that byte back as the return value of the next read(). Listing 19.1 shows a simple implementation of readLine() using this class: Listing 19.1. A simple line reader. 1:import java.io; 2: 3:public class SimpleLineReader { 4: private FilterInputStream s; 5: 6: public SimpleLineReader(InputStream anIS) { 7: s = new DataInputStream(anIS); 8: } 9: 10: ... // other read() methods using stream s 11: 12: public String readLine() throws IOException { 13: char buffer = new char[100]; file:///G|/ebooks/1575211831/ch19.htm (11 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O 14: 15: 16: 17: 18:loop: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: } 43:} int byte offset = 0; thisByte; try { while (offset < buffer.length) { switch (thisByte = (byte) s.read()) { case '\n': break loop; case '\r': byte nextByte = (byte) s.read(); if (nextByte != '\n') { if (!(s instanceof PushbackInputStream)) { s = new PushbackInputStream(s); } ((PushbackInputStream) s).unread(nextByte); } break loop; default: buffer[offset++] = (char) thisByte; break; } } } catch (EOFException e) { if (offset == 0) return null; } return String.copyValueOf(buffer, 0, offset); This example demonstrates numerous things. For the purpose of this example, the readLine() method is restricted to reading the first 100 characters of the line. In this respect, it demonstrates how not to write a general-purpose line processor (you should be able to read a line of any size). This example does, however, show you how to break out of an outer loop (using the loop label in line 18 and the break statements in lines 21 and 31), and how to produce a String from an array of characters (in this case, from a "slice" of the array of characters). This example also includes standard uses of InputStream's read() for reading bytes one at a time, and of determining the end of the stream by enclosing it in a DataInputStream and catching EOFException. One of the more unusual aspects of the example is the way PushbackInputStream is used. To be sure that '\n' is ignored following '\r', you have to "look ahead" one character; but if it is not a '\n', you must push back that character. Look at the lines 26 through 29 as if you didn't know much about the stream s. The general technique used is instructive. First, you see whether s is already an instance of some kind of PushbackInputStream. If so, you can simply use it. If not, you enclose the current stream (whatever it is) inside a new PushbackInputStream and use this new stream. Now, let's jump back into the context of the example. Line 29 following that if statement in line 26 wants to call the method unread(). The problem is that s has a compile-time type of FilterInputStream, and thus doesn't understand that method. The previous three lines (26) have guaranteed, however, that the runtime type of the stream in s is PushbackInputStream, so you can safely cast it to that type and then safely call unread(). Note file:///G|/ebooks/1575211831/ch19.htm (12 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O This example was done in an unusual way for demonstration purposes. You could have simply declared a PushbackInputStream variable and always enclosed the DataInputStream in it. (Conversely, SimpleLineReader's constructor could have checked whether its argument was already of the right class, the way PushbackInputStream did, before creating a new DataInputStream.) The interesting thing about this approach of wrapping a class only when needed is that it works for any InputStream that you hand it, and it does additional work only if it needs to. Both of these are good general design principles. All the subclasses of FilterInputStream have now been described. It's time to return to the direct subclasses of InputStream. PipedInputStream This class, along with its brother class PipedOutputStream, are covered later today (they need to be understood and demonstrated together). For now, all you need to know is that together they create a simple, two-way communication conduit between threads. SequenceInputStream Suppose you have two separate streams and you would like to make a composite stream that consists of one stream followed by the other (like appending two Strings together). This is exactly what SequenceInputStream was created for: InputStream InputStream s1 = new FileInputStream("theFirstPart"); s2 = new FileInputStream("theRest"); InputStream s ... s.read() ... = new SequenceInputStream(s1, s2); // reads from each stream in turn You could have "faked" this example by reading each file in turn-but what if you had to hand the composite stream s to some other method that was expecting only a single InputStream? Here's an example (using s) that line-numbers the two previous files with a common numbering scheme: LineNumberInputStream aLNIS = new LineNumberInputStream(s); ... aLNIS.getLineNumber() ... Note Stringing together streams this way is especially useful when the streams are of unknown length and origin and were just handed to you by someone else. What if you want to string together more than two streams? You could try the following: Vector v = new Vector(); ... // set up all the streams and add each to the Vector InputStream s1 = new SequenceInputStream(v.elementAt(0), v.elementAt(1)); file:///G|/ebooks/1575211831/ch19.htm (13 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O InputStream InputStream ... s2 = new SequenceInputStream(s1, v.elementAt(2)); s3 = new SequenceInputStream(s2, v.elementAt(3)); Note A Vector is a growable array of objects that can be filled, referenced (with elementAt()), and enumerated. However, it's much easier to use a different constructor that SequenceInputStream provides: InputStream s = new SequenceInputStream(v.elements()); This constructor takes one argument-an object of type Enumeration (in this example, we got that object using Vector's elements() method). The resulting SequenceInputStream object contains all the streams you want to combine and returns a single stream that reads through the data of each in turn. StringBufferInputStream StringBufferInputStream is exactly like ByteArrayInputStream, but instead of being based on a byte array, it's based on an array of characters (a String): String InputStream buffer = "Now is the time for all good men to come..."; s = new StringBufferInputStream(buffer); All comments that were made about ByteArrayInputStream apply here as well. Note StringBufferInputStream is a bit of a misnomer because this input stream is actually based on a String. It should really be called StringInputStream. Output Streams An output stream is the reverse of an input stream; whereas with an input stream you read data from the stream, with output streams you write data to the stream. Most of the InputStream subclasses you've already seen have their equivalent OutputStream brother classes. If an InputStream performs a certain operation, the brother OutputStream performs the inverse operation. You'll see more of what this means soon. The Abstract Class OutputStream OutputStream is the abstract class that defines the fundamental ways in which a source (producer) writes a stream of bytes to some destination. The identity of the destination, and the manner of the transport and storage of the bytes, is irrelevant. When using an output stream, you are the source of those bytes, and that's all you need to know. write() The most important method to the producer of an output stream is the one that writes bytes to the destination. This method, write(), comes in many flavors, each demonstrated in the following examples: Note file:///G|/ebooks/1575211831/ch19.htm (14 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O Every one of these write() methods is defined to block until all the output requested has been written. You don't need to worry about this limitation-see the note under InputStream's read() method if you don't remember why. OutputStream byte s = getAnOutputStreamFromSomewhere(); buffer = new byte[1024]; // any size will do fillInData(buffer); s.write(buffer); // the data we want to output You also can write a "slice" of your buffer by specifying the offset into the buffer, and the length desired, as arguments to write(): s.write(buffer, 100, 300); This example writes out bytes 100 through 399 and behaves otherwise exactly the same as the previous write() method. Finally, you can write out bytes one at a time: while (thereAreMoreBytesToOutput()) { byte b = getNextByteForOutput(); s.write(b); } flush() Because you don't know what an output stream is connected to, you might be required to "flush" your output through some buffered cache to get it to be written (in a timely manner, or at all). OutputStream's version of this method does nothing, but it is expected that subclasses that require flushing (for example, BufferedOutputStream and PrintStream) will override this version to do something nontrivial. close() Just like for an InputStream, you should (usually) explicitly close down an OutputStream so that it can release any resources it may have reserved on your behalf. (All the same notes and examples from InputStream's close() method apply here, with the prefix In replaced everywhere by Out.) All output streams descend from the abstract class OutputStream. All share the previous few methods in common. ByteArrayOutputStream The inverse of ByteArrayInputStream, which creates an input stream from an array of bytes, is ByteArrayOutputStream, which directs an output stream into an array of bytes: OutputStream s = new ByteArrayOutputStream(); s.write(123); ... The size of the (internal) byte array grows as needed to store a stream of any length. You can provide an initial capacity file:///G|/ebooks/1575211831/ch19.htm (15 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O as an aid to the class, if you like: OutputStream s = new ByteArrayOutputStream(1024 * 1024); // 1 Megabyte Note You've just seen your first examples of the creation of an output stream. These new streams were attached to the simplest of all possible destinations of data, an array of bytes in the memory of the local computer. Once the ByteArrayOutputStream object, stored in the variable s, has been "filled," it can be output to another output stream: OutputStream ByteArrayOutputStream anotherOutputStream = getTheOtherOutputStream(); s = new ByteArrayOutputStream(); fillWithUsefulData(s); s.writeTo(anotherOutputStream); It also can be extracted as a byte array or converted to a String: byte String String buffer = s.toByteArray(); bufferString = s.toString(); bufferUnicodeString = s.toString(upperByteValue); Note The last method allows you to "fake" Unicode (16-bit) characters by filling in their lower bytes with ASCII and then specifying a common upper byte (usually 0) to create a Unicode String result. ByteArrayOutputStreams have two utility methods: One simply returns the current number of bytes stored in the internal byte array, and the other resets the array so that the stream can be rewritten from the beginning: int sizeOfMyByteArray = s.size(); s.reset(); s.write(123); ... // s.size() would now return 0 FileOutputStream One of the most common uses of streams is to attach them to files in the file system. Here, for example, is the creation of such an output stream on a UNIX system: OutputStream s = new FileOutputStream("/some/path/and/fileName"); Warning file:///G|/ebooks/1575211831/ch19.htm (16 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O Applets attempting to open, read, or write streams based on files in the file system will cause security violations. See the note under FileInputStream for more details. As with FileInputStream, you also can create the stream from a previously opened file descriptor: FileDescriptor OutputStream s fd = someFileStream.getFD(); = new FileOutputStream(fd); FileOutputStream is the inverse of FileInputStream, and it knows the same tricks: FileOutputStream FileDescriptor aFOS = new FileOutputStream("aFileName"); aFOS.finalize(); myFD = aFOS.getFD(); // get a file descriptor // will call close() when automatically called by GC Note To call the new methods, you must declare the stream variable aFOS to be of type FileOutputStream, because plain OutputStreams don't know about them. The first is obvious: getFD() simply returns the file descriptor for the file on which the stream is based. The second, commented, contrived call to finalize() is there to remind you that you may not have to worry about closing this type of stream-it is done for you automatically. FilterOutputStream This abstract class simply provides a "pass-through" for all the standard methods of OutputStream. It holds inside itself another stream, by definition one further "down" the chain of filters, to which it forwards all method calls. It implements nothing new but allows itself to be nested: OutputStream FilterOutputStream FilterOutputStream FilterOutputStream s s1 s2 s3 = = = = getAnOutputStreamFromSomewhere(); new FilterOutputStream(s); new FilterOutputStream(s1); new FilterOutputStream(s2); ... s3.write(123) ... Whenever a write is performed on the filtered stream s3, it passes along the request to s2. Then s2 does the same to s1, and finally s is asked to output the bytes. Subclasses of FilterOutputStream, of course, do some nontrivial processing of the bytes as they flow past. This chain can be tightly nested-see its brother class, FilterInputStream, for more. Now let's examine each of the subclasses of FilterOutputStream in turn. BufferedOutputStream BufferedOutputStream is one of the most valuable of all streams. All it does is implement the full complement of OutputStream's methods, but it does so by using a buffered array of bytes that acts as a cache for writing. This decouples the rate and the size of the "chunks" you're writing from the more regular, larger block sizes in which streams file:///G|/ebooks/1575211831/ch19.htm (17 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O are most efficiently written (to peripheral devices, files in the file system, or the network, for example). BufferedOutputStream is one of two classes in the Java library to implement flush(), which pushes the bytes you've written through the buffer and out the other side. Because buffering is so valuable, you might wish that every output stream could somehow be buffered. Fortunately, you can surround any output stream in such a way as to achieve just that: OutputStream s = new BufferedOutputStream(new FileOutputStream("foo")); You now have a buffered output stream based on the file foo that can be flushed. Just as for filter input streams, any capability provided by a filter output stream can be used by any other basic stream via nesting, and any combination of these capabilities, in any order, can be as easily accomplished by nesting the filter streams themselves. DataOutputStream All the methods that instances of this class understand are defined in a separate interface, which both DataOutputStream and RandomAccessFile implement. This interface is general-purpose enough that you might want to use it yourself in the classes you create. It is called DataOutput. The DataOutput Interface In cooperation with its brother inverse interface, DataInput, DataOutput provides a higher-level, typed-stream approach to the reading and writing of data. Rather than dealing with bytes, this interface deals with writing the primitive types of the Java language directly: void void void write(int i) write(byte write(byte void void void void void void void void writeBoolean(boolean b) writeByte(int i) writeShort(int i) writeChar(int i) writeInt(int i) writeLong(long l) writeFloat(float f) writeDouble(double d) void void void writeBytes(String s) throws IOException; writeChars(String s) throws IOException; writeUTF(String s) throws IOException; buffer) buffer, int offset, int throws IOException; throws IOException; length) throws IOException; throws throws throws throws throws throws throws throws IOException; IOException; IOException; IOException; IOException; IOException; IOException; IOException; Most of these methods have counterparts in the interface DataInput. The first three methods mirror the three forms of write() you saw previously. Each of the next eight methods writes out a primitive type. The final three methods write out a string of bytes or characters to the stream: the first one as 8-bit bytes; the second, as 16-bit Unicode characters; and the last, as a special Unicode stream (readable by DataInput's readUTF()). Note file:///G|/ebooks/1575211831/ch19.htm (18 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O The unsigned read methods in DataInput have no counterparts here. You can write out the data they need via DataOutput's signed methods because they accept int arguments and also because they write out the correct number of bits for the unsigned integer of a given size as a side effect of writing out the signed integer of that same size. It is the method that reads this integer that must interpret the sign bit correctly; the writer's job is easy. Now that you know what the interface that DataOutputStream implements looks like, let's see it in action: DataOutputStream long s = new DataOutputStream(myRecordOutputStream()); size = getNumberOfItemsInNumericStream(); s.writeLong(size); for (int i = 0; i < size; ++i) { if (shouldProcessNumber(i)) { s.writeBoolean(true); // should process this item s.writeInt(theIntegerForItemNumber(i)); s.writeShort(theMagicBitFlagsForItemNumber(i)); s.writeDouble(theDoubleForItemNumber(i)); } else s.writeBoolean(false); } This is the exact inverse of the example that was given for DataInput. Together, they form a pair that can communicate a particular array of structured primitive types across any stream (or "transport layer"). Use this pair as a jumping-off point whenever you need to do something similar. In addition to the preceding interface, the class itself implements one (self-explanatory) utility method: int theNumberOfBytesWrittenSoFar = s.size(); Processing a File One of the most common idioms in file I/O is to open a file, read and process it line-by-line, and output it again to another file. Here's a prototypical example of how that would be done in Java: DataInput DataOutput String aDI = new DataInputStream(new FileInputStream("source")); aDO = new DataOutputStream(new FileOutputStream("dest")); line; while ((line = aDI.readLine()) != null) { StringBuffer modifiedLine = new StringBuffer(line); ... // process modifiedLine in place aDO.writeBytes(modifiedLine.toString()); } aDI.close(); aDO.close(); If you want to process it byte-by-byte, use this: file:///G|/ebooks/1575211831/ch19.htm (19 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O try { while (true) { byte b = (byte) aDI.readByte(); ... // process b in place aDO.writeByte(b); } } finally { aDI.close(); aDO.close(); } Here's a cute two-liner that just copies the file: try { while (true) aDO.writeByte(aDI.readByte()); } finally { aDI.close(); aDO.close(); } Warning Many of the examples in today's lesson (as well as the last two) are assumed to appear inside a method that has IOException in its throws clause, so they don't have to worry about catching those exceptions and handling them more reasonably. Your code should be a little less cavalier. PrintStream You may not realize it, but you're already intimately familiar with the use of two methods of the PrintStream class. That's because whenever you use these method calls: System.out.print(. . .) System.out.println(. . .) you are actually using a PrintStream instance located in System's class variable out to perform the output. System.err is also a PrintStream, and System.in is an InputStream. Note On UNIX systems, these three streams will be attached to standard output, standard error, and standard input, respectively. PrintStream is uniquely an output stream class (it has no brother class). Because it is usually attached to a screen output device of some kind, it provides an implementation of flush(). It also provides the familiar close() and write() methods, as well as a plethora of choices for outputting the primitive types and Strings of Java: public public public public void void void void write(int b); write(byte buffer, int flush(); close(); public public public public void void void void print(Object o); print(String s); print(char buffer); print(char c); file:///G|/ebooks/1575211831/ch19.htm (20 of 24) [11/06/2000 7:45:43 PM] offset, int length); Day 19 -- Streams and I/O public public public public public void void void void void print(int i); print(long l); print(float f); print(double d); print(boolean b); public public public public public public public public public void void void void void void void void void println(Object o); println(String s); println(char buffer); println(char c); println(int i); println(long l); println(float f); println(double d); println(boolean b); public void println(); // output a blank line PrintStream can also be wrapped around any output stream, just like a filter class: PrintStream s = new PrintStream(new FileOutputStream("foo")); s.println("Here's the first line of text in the file foo."); If you provide a second argument to the constructor for PrintStream, that second argument is a boolean that specifies whether the stream should auto-flush. If true, a flush() is sent after each newline character is written. Here's a simple sample program that operates like the UNIX command cat, taking the standard input, line-by-line, and outputting it to the standard output: import java.io.*; // the one time in the chapter we'll say this public class Cat { public static void main(String argv) { DataInput d = new DataInputStream(System.in); String line; try { while ((line = d.readLine()) != null) System.out.println(line); } catch (IOException ignored) { } } } PipedOutputStream Along with PipedInputStream, this pair of classes supports a UNIX-pipe-like connection between two threads, implementing all the careful synchronization that allows this sort of "shared queue" to operate safely. Use the following to set up the connection: PipedInputStream PipedOutputStream sIn = PipedInputStream(); sOut = PipedOutputStream(sIn); One thread writes to sOut; the other reads from sIn. By setting up two such pairs, the threads can communicate safely file:///G|/ebooks/1575211831/ch19.htm (21 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O in both directions. Related Classes The other classes and interfaces in java.io supplement the streams to provide a complete I/O system. Three of them are described here. The File class abstracts files in a platform-independent way. Given a filename, it can respond to queries about the type, status, and properties of a file or directory in the file system. A RandomAccessFile is created given a file, a filename, or a file descriptor. It combines in one class implementations of the DataInput and DataOutput interfaces, both tuned for "random access" to a file in the file system. In addition to these interfaces, RandomAccessFile provides certain traditional UNIX-like facilities, such as seeking to a random point in the file. Finally, the StreamTokenizer class takes an input stream and produces a sequence of tokens. By overriding its various methods in your own subclasses, you can create powerful lexical parsers. You can learn more about any and all of these other classes from the full (online) API descriptions in your Java release. Object Serialization (Java 1.1) A topic to streams, and one that will be available in the core Java library with Java 1.1, is object serialization. Serialization is the ability to write a Java object to a stream such as a file or a network connection, and then read it and reconstruct that object on the other side. Object serialization is crucial for the ability to save Java objects to a file (what's called object persistence), or to be able to accomplish network-based applications that make use of Remote Method Invocation (RMI)-a capability you'll learn more of on Day 27, "The Standard Extension APIs." At the heart of object serialization are two streams classes: ObjectInputStream, which inherits from DataInputStream, and ObjectOutputStream, which inherits from DataOutputStream. Both of these classes will be part of the java.io package and will be used much in the same way as the standard input and output streams are. In addition, two interfaces, ObjectOutput and ObjectInput, which inherit from DataInput and DataOutput, respectively, will provide abstract behavior for reading and writing objects. To use the ObjectInputStream and ObjectOutputStream classes, you create new instances much in the same way you do ordinary streams, and then use the readObject() and writeObject() methods to read and write objects to and from those streams. ObjectOutputStream's writeObject() method, which takes a single object argument, serializes that object as well as any object it has references to. Other objects written to the same stream are serialized as well, with references to already-serialized objects kept track of and circular references preserved. ObjectInputStream's readObject() method takes no arguments and reads an object from the stream (you'll need to cast that object to an object of the appropriate class). Objects are read from the stream in the same order in which they are written. Here's a simple example from the object serialization specification that writes a date to a file (actually, it writes a string label, "Today", and then a Date object): FileOutputStream f = new FileOutputStream("tmp"); ObjectOutput s = new ObjectOutputStream(f); s.writeObject("Today"); s.writeObject(new Date()); s.flush(); file:///G|/ebooks/1575211831/ch19.htm (22 of 24) [11/06/2000 7:45:43 PM] Day 19 -- Streams and I/O To deserialize the object (read it back in again), use this code: FileInputStream in = new FileInputStream("tmp"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject(); One other feature of object serialization to note is the transient modifier. Used in instance variable declarations as other modifiers are, the transient modifier means that the value of that object should not be stored when the object is serialized-that its value is temporary or will need to be re-created from scratch once the object is reconstructed. Use transient variables for environment-specific information (such as file handles that may be different from one side of the serialization to the other) or for values that can be easily recalculated to save space in the final serialized object. To declare a transient variable, use the transient modifier the way you do other modifiers such as public, private, or abstract: public transient int transientValue = 4; At the time of this writing, object serialization is available as an additional package for Java 1.0.2 as part of the RMI package. You can find out more about it, including full specifications and downloadable software, from the Java RMI Web site at http://chatsubo.javasoft.com/current/. Summary Today you have learned about the general idea of streams and have met input streams based on byte arrays, files, pipes, sequences of other streams, and string buffers, as well as input filters for buffering, typed data, line numbering, and pushing-back characters. You have also met the analogous brother output streams for byte arrays, files, and pipes, output filters for buffering and typed data, and the unique output filter used for printing. Along the way, you have become familiar with the fundamental methods all streams understand (such as read() and write()), as well as the unique methods many streams add to this repertoire. You have learned about catching IOExceptions-especially the most useful of them, EOFException. Finally, the twice-useful DataInput and DataOutput interfaces formed the heart of RandomAccessFile, one of the several utility classes that round out Java's I/O facilities. Java streams provide a powerful base on which you can build multithreaded, streaming interfaces of the most complex kinds, and the programs (such as HotJava) to interpret them. The higher-level Internet protocols and services of the future that your applets can build on this base are really limited only by your imagination. Q&A Q: In an early read() example, you did something with the variable byteOrMinus1 that seemed a little clumsy. Isn't there a better way? If not, why recommend the cast later? file:///G|/ebooks/1575211831/ch19.htm (23 of 24) [11/06/2000 7:45:44 PM] Day 19 -- Streams and I/O A: Yes, there is something a little odd about those statements. You might be tempted to try something like this instead: while ((b = (byte) s.read()) != -1) { ... // process the byte b } Q: A: Q: A: Q: A: The problem with this shortcut occurs if read() returns the value 0xFF (0377). Because of the way values are cast, it will appear to be identical to the integer value -1 that indicates end of stream. Only saving that value in a separate integer variable, and then casting it later, will accomplish the desired result. The cast to byte is recommended in the note for slightly different reasons than this, however-storing integer values in correctly sized variables is always good style (and besides, read() really should be returning something of byte size here and throwing an exception for end of stream). What input streams in java.io actually implement mark(), reset(), and markSupported()? InputStream itself does-and in their default implementations, markSupported() returns false, mark() does nothing, and reset() throws an exception. The only input stream in the current release that correctly supports marking is BufferedInputStream, which overrides these defaults. LineNumberInputStream actually implements mark() and reset(), but in the current release, it doesn't answer markSupported() correctly, so it looks as if it does not. Why is available() useful, if it sometimes gives the wrong answer? First, for many streams, it gives the right answer. Second, for some network streams, its implementation might be sending a special query to discover some information you couldn't get any other way (for example, the size of a file being transferred by ftp). If you are displaying a "progress bar" for network or file transfers, for example, available() will often give you the total size of the transfer, and when it does not-usually by returning 0-it will be obvious to you (and your users). What's a good example of the use of the DataInput/DataOutput pair of interfaces? One common use of such a pair is when objects want to "pickle" themselves for storage or movement over a network. Each object implements read and write methods using these interfaces, effectively converting itself to a stream that can later be reconstituted "on the other end" into a copy of the original object. file:///G|/ebooks/1575211831/ch19.htm (24 of 24) [11/06/2000 7:45:44 PM] Day 17 -- Exceptions Day 17 Exceptions by Charles L. Perkins and Laura Lemay CONTENTS q Exceptions, the Old and Confusing Way q Java Exceptions q Managing Exceptions r r Protecting Code and Catching Exceptions r q Exception Consistency Checking The finally Clause Declaring Methods That Might Throw Exceptions r r Which Exceptions Should You Throw? r Passing On Exceptions r q The throws Clause throws and Inheritance Creating and Throwing Your Own Exceptions r r Creating Your Own Exceptions r q Throwing Exceptions Doing It All: Combining throws, try, and throw When and When Not to Use Exceptions r When to Use Exceptions r When Not to Use Exceptions r Bad Style Using Exceptions q Summary q Q&A Programmers in any language endeavor to write bug-free programs, programs that never crash, programs that can handle any situation with grace and that can recover from unusual situations without causing the user any undue stress. Good intentions aside, programs like this don't exist. In real programs, errors occur, either because the programmer didn't anticipate every situation your code would get into (or didn't have the time to test the program enough), or because of situations out of the programmer's control-bad data from users, corrupt files that don't have the right data in them, network connections that don't file:///G|/ebooks/1575211831/ch17.htm (1 of 16) [11/06/2000 7:45:46 PM] Day 17 -- Exceptions connect, hardware devices that don't respond, sun spots, gremlins, whatever. In Java, these sorts of strange events that may cause a program to fail are called exceptions. And Java defines a number of language features to deal with exceptions, including q How to handle them in your code and recover gracefully from potential problems q How to tell Java and users of your methods that you're expecting a potential exception q How to create an exception if you detect one q How your code is limited, yet made more robust by them Exceptions are unusual things that can happen in your Java programs outside the normal or desired behavior of that program. Exceptions include errors that could be fatal to your program but also include other unusual situations. By managing exceptions, you can manage errors and possibly work around them. Exceptions, the Old and Confusing Way Programming languages have long labored to solve the following common problem: int status = callSomethingThatAlmostAlwaysWorks(); if (status == FUNNY_RETURN_VALUE) { ... // something unusual happened, handle it switch(someGlobalErrorIndicator) { . . . // handle more specific problems } } else { ... // all is well, go your merry way } What this bit of code is attempting to do is to run a method that should work, but might not for some unusual reason. The status might end up being some unusual return value, in which case the code attempts to figure out what happened and work around it. Somehow this seems like a lot of work to do to handle a rare case. And if the function you called returns an int as part of its normal answer, you'll have to distinguish one special integer (FUNNY_RETURN_VALUE) as an error. Alternatively, you could pass in a special return value pointer, or use a global variable to store those errors, but then problems arise with keeping track of multiple errors with the same bit of code, or of the original error stored in the global being overwritten by a new error before you have a chance to deal with it. Once you start creating larger systems, error management can become a major problem. Different programmers may use different special values for handling errors, and may not document them overly well, if at all. You may inconsistently use errors in your own programs. Code to manage these kinds of errors can often obscure the original intent of the program, making that code difficult to read and to maintain. And, finally, if you try dealing with errors in this kludgey way, there's no easy way for the compiler to check for consistency the way it can check to make sure you called a method with the right arguments. For all these reasons, Java has exceptions to deal with managing, creating, and expecting errors and other unusual situations. Through a combination of special language features, consistency checking at compile time and a set of extensible exception classes, errors and other unusual conditions in Java programs can be much more easily managed. Given these features, you can now add a whole new dimension to the behavior and file:///G|/ebooks/1575211831/ch17.htm (2 of 16) [11/06/2000 7:45:46 PM] Day 17 -- Exceptions design of your classes, of your class hierarchy, and of your overall system. Your class and interface definitions describe how your program is supposed to behave given the best circumstances. By integrating exception handling into your program design, you can consistently describe how the program will behave when circumstances are not quite as good, and allow people who use your classes to know what to expect in those cases. Java Exceptions At this point in the book, chances are you've run into at least one Java exception-perhaps you mistyped a method name or made a mistake in your code that caused a problem. And chances are that your program quit and spewed a bunch of mysterious errors to the screen. Those mysterious errors are exceptions. When your program quits, it's because an exception was "thrown." Exceptions can be thrown by the system or thrown by you, and they can be caught as well (catching an exception involves dealing with it so your program doesn't crash. You'll learn more about this later). "An exception was thrown" is the proper Java terminology for "an error happened." Exceptions don't occur, they are thrown. Java throws an exception in response to an unusual situation. You can also throw your own exceptions, or catch an exception to gracefully manage errors. The heart of the Java exception system is the exception itself. Exceptions in Java are actual objects, instances of classes that inherit from the class Throwable. When an exception is thrown, an instance of a Throwable class is created. Figure 17.1 shows a partial class hierarchy for exceptions. Figure 17.1 : The exception class hierarchy. Throwable has two subclasses: Error and Exception. Instances of Error are internal errors in the Java runtime environment (the virtual machine). These errors are rare and usually fatal; there's not much you can do about them (either to catch them or to throw them yourself), but they exist so that Java can use them if it needs to. The class Exception is more interesting. Subclasses of Exception fall into two general groups: q Runtime exceptions (subclasses of the class RuntimeException) such as ArrayIndexOutofBounds, SecurityException, or NullPointerException. q Other exceptions such as EOFException and MalformedURLException. Runtime exceptions usually occur because of code that isn't very robust. An ArrayIndexOutofBounds exception, for example, should never be thrown if you're properly checking to make sure your code doesn't extend past the end of an array. NullPointerException exceptions won't happen if you don't try to reference the values of a variable that doesn't actually hold an object. If your program is causing runtime exceptions under any circumstances whatsoever, you should be fixing those problems before you even begin to deal with exception management. The final group of exceptions is the most interesting because these are the exceptions that indicate that something very strange and out of control is happening. EOFExceptions, for example, happen when you're reading from a file and the file ends before you expect it to. MalformedURLExceptions happen when a URL isn't in the right format (perhaps your user typed it wrong). This group includes exceptions that you yourself create to signal unusual cases that may occur in your own programs. Exceptions are arranged in a hierarchy like other classes, where the Exception superclasses are more general errors, and subclasses are more specific errors. This organization will become more important to you file:///G|/ebooks/1575211831/ch17.htm (3 of 16) [11/06/2000 7:45:46 PM] Day 17 -- Exceptions as you deal with exceptions in your own code. Most of the exception classes are part of the java.lang package (including Throwable, Exception, and RuntimeException). But many of the other packages define other exceptions, and those exceptions are used throughout the class library. For example, the java.io package defines a general exception class called IOException, which is subclassed not only in the java.io package for input and output exceptions (EOFException, FileNotFoundException), but also in the java.net classes for networking exceptions such as MalFormedURLException. Managing Exceptions So now that you know what an exception is, how do you deal with them in your own code? In many cases, the Java compiler enforces exception management when you try to use methods that use exceptions; you'll need to deal with those exceptions in your own code or it simply won't compile. In this section you'll learn about that consistency checking and how to use the try, catch, and finally language keywords to deal with exceptions that may or may not occur. Exception Consistency Checking The more you work with the Java class libraries, the more likely it is that you'll run into a compiler error (an exception!) similar to this one: TestProg.java:32: Exception java.lang.InterruptedException must be caught or it must be declared in the throws clause of this method. What on earth does that mean? In Java, a method can indicate the kinds of errors it might possibly throw. For example, methods that read from files might potentially throw IOException errors, so those methods are declared with a special modifier that indicates potential errors. When you use those methods in your own Java programs, you have to protect your code against those exceptions. This rule is enforced by the compiler itself, the same way that the compiler checks to make sure that you're using methods with the right number of arguments and that all your variable types match the thing you're assigning to them. Why is this check in place? By having methods declare the exceptions they throw, and by forcing you to handle those exceptions in some way, the potential for fatal errors in a program occurring because you simply didn't know they could occur is minimized. You no longer have to carefully read the documentation or the code of an object you're going to use to make sure you've dealt with all the potential problems-Java does the checking for you. And, on the other side, if you define your methods so that they indicate the exceptions they can throw, then Java can tell users of your objects to handle those errors. Protecting Code and Catching Exceptions Let's assume that you've been happily coding and during a test compile you ran into that exception message. According to the message, you have to either catch the error or declare that your method throws it. Let's deal with the first case: catching potential exceptions. To catch an exception, you do two things: q You protect the code that contains the method that might throw an exception inside a try block. file:///G|/ebooks/1575211831/ch17.htm (4 of 16) [11/06/2000 7:45:46 PM] Day 17 -- Exceptions q You test for and deal with an exception inside a catch block. What try and catch effectively mean is "try this bit of code that might cause an exception. If it executes okay, go on with the program. If it doesn't, catch the exception and deal with it." You've seen try and catch once before, when we dealt with threads. On Day 10, "Simple Animation and Threads," you learned about an applet that created a digital clock, and the animation paused once a second using this bit of code: try { Thread.sleep(1000) } catch (InterruptedException e) {} While this example uses try and catch, it's not a very good use of it. Here, the Thread.sleep() class method could potentially throw an exception of type InterruptedException (for when the thread is interrupted from running). So we've put the call to sleep() inside the try clause to catch that exception if it happens. And inside catch (inside the parentheses), we indicate that we're specifically looking for InterruptedException exceptions. The problem here is that there isn't anything inside the catch clause-in other words, we'll catch the exception if it happens, but then we'll drop it on the floor and pretend we didn't see it. In all but the simplest cases (such as this one, where the exception really doesn't matter), you're going to want to put something inside the braces after catch to try to do something responsible to clean up after the exception happens. The part of the catch clause inside the parentheses is similar to the parameter list of a method definition; it contains the class of the exception to be caught and a variable name (e is very commonly used). Inside the body of the catch clause, you can then refer to the exception object, for example, to get to the detailed error message contained in the getMessage() method: catch (InterruptedException e) { System.out.println("Ooops. Error: " + e.getMessage()); } Here's another example. Say you have a program that reads from a file. This program most likely uses one of the streams classes you'll learn about on Day 19, "Streams and I/O," but the basic idea here is that you open a connection to a file and then use the read() method to get data from it. What if some strange disk error happens and the read() method can't read anything? What if the file is truncated and has fewer bytes in it than you expected? In either of these instances, the read() method will throw an IOException which, if you didn't catch it, would cause your program to stop executing and possibly crash. By putting your read() method inside a try clause, you can then deal gracefully with that error inside catch to clean up after the error and return to some safe state, to patch things up enough to be able to proceed, or, if all else fails, to save as much of the current program's state as possible and to exit. This example does just that; it tries to read from the file, and catches exceptions if they happen: try { while (numbytes <= mybuffer.length) { myinputstream.read(mybuffer); numbytes;++ } } catch (IOException e) { System.out.println("Ooops, IO Exception. file:///G|/ebooks/1575211831/ch17.htm (5 of 16) [11/06/2000 7:45:46 PM] Only read " + numbytes."); Day 17 -- Exceptions // other cleanup code } Here, the "other cleanup code" can be anything you want it to be; perhaps you can go on with the program using the partial information you got from the file, or perhaps you want to put up a dialog saying that the file is corrupt and to let the user try to select another file or do some other operation. Note that because the Exception classes are organized into hierarchies as other classes are, and because of the rule that you can use a subclass anywhere a superclass is expected, you can catch "groups" of exceptions and handle them with the same catch code. For example, although there are several different types of IOExceptions (EOFException, FileNotFoundException, and so on-see the java.io package for examples), by catching IOException you also catch instances of any subclass of IOException. What if you do want to catch very different kinds of exceptions that aren't related by inheritance? You can use multiple catch clauses for a given try, like this: try { // protected code } catch (OneKindOfException e) { ... } catch (AnotherKindOfException e2) { .... } catch (YetAnotherException e3) { ... } catch (StilMoreException e4) { .... } Note that because the scope of local variables inside catch is the same as the scope of the outer block (the method definition or a loop if you're inside one), you'll have to use different local variables for each individual catch. Because the first catch clause that matches is executed, you can build chains such as the following: try { someReallyExceptionalMethod(); } catch (NullPointerException n) { ... } catch (RuntimeException r) { ... } catch (IOException i) { ... } catch (MyFirstException m) { ... } catch (Exception e) { ... } catch (Throwable t) { . . . // Errors, plus anything } file:///G|/ebooks/1575211831/ch17.htm (6 of 16) [11/06/2000 7:45:46 PM] // a subclass of RuntimeException // a subclass of Exception // a subclass of Exception // our subclass of Exception // a subclass of Throwable not caught above are caught here Day 17 -- Exceptions By listing subclasses before their parent classes, the parent catches anything it would normally catch that's also not one of the subclasses above it. By juggling chains like these, you can express almost any combination of tests. The finally Clause Suppose there is some action in your code that you absolutely must do, no matter what happens, whether an exception is thrown or not. Usually, this is to free some external resource after acquiring it, to close a file after opening it, or something similar. While you could put that action both inside a catch and outside it, that would be duplicating the same code in two different places. Instead, put one copy of that code inside a special optional part of the try...catch clause, called finally: SomeFileClass f = new SomeFileClass(); if (f.open("/a/file/name/path")) { try { someReallyExceptionalMethod(); { catch (IOException e) { // deal with errors } finally { f.close(); } } The finally clause is actually useful outside exceptions; you can also use it to execute cleanup code after a return, a break, or a continue inside loops. For the latter cases, you can use a try clause with a finally but without a catch clause. Here's a fairly complex example of how this might work: int mysteriousState = getContext(); while (true) { System.out.print("Who "); try { System.out.print("is "); if (mysteriousState == 1) return; System.out.print("that "); if (mysteriousState == 2) break; System.out.print("strange "); if (mysteriousState == 3) continue; System.out.print("but kindly "); if (mysteriousState == 4) throw new UncaughtException(); System.out.print("not at all "); file:///G|/ebooks/1575211831/ch17.htm (7 of 16) [11/06/2000 7:45:46 PM] Day 17 -- Exceptions } finally { System.out.print("amusing man?\n"); } System.out.print("I'd like to meet the man"); } System.out.print("Please tell me.\n"); Here is the output produced depending on the value of mysteriousState: 1 2 3 4 5 Who Who Who Who Who I'd is amusing man? Please tell me. is that amusing man? Please tell me. is that strange amusing man? Who is that strange .... is that strange but kindly amusing man? Please tell me. is that strange but kindly not at all amusing man? like to meet that man. Who is that strange ... Note In cases 3 and 5, the output never ends until you quit the program. In 4, an error message generated by the UncaughtException is also printed. Declaring Methods That Might Throw Exceptions In the previous example you learned how to deal with methods that might possibly throw exceptions by protecting code and catching any exceptions that occur. The Java compiler will check to make sure you've somehow dealt with a method's exceptions-but how did it know which exceptions to tell you about in the first place? The answer is that the original method indicated in its signature the exceptions that it might possibly throw. You can use this mechanism in your own methods-in fact, it's good style to do so to make sure that other users of your classes are alerted to the errors your methods may come across. To indicate that a method may possibly throw an exception, you use a special clause in the method definition called throws. The throws Clause To indicate that some code in the body of your method may throw an exception, simply add the throws keyword after the signature for the method (before the opening brace) with the name or names of the exception that your method throws: public boolean myMethod (int x, int y) throws AnException { ... } If your method may possibly throw multiple kinds of exceptions, you can put all of them in the throws clause, separated by commas: file:///G|/ebooks/1575211831/ch17.htm (8 of 16) [11/06/2000 7:45:46 PM] Day 17 -- Exceptions public boolean myOtherMethod (int x, int y) throws AnException, AnotherExeption, AThirdException { ... } Note that as with catch you can use a superclass of a group of exceptions to indicate that your method may throw any subclass of that exception: public void YetAnotherMethod() throws IOException { ... } Keep in mind that adding a throws method to your method definition simply means that the method might throw an exception if something goes wrong, not that it actually will. The throws clause simply provides extra information to your method definition about potential exceptions and allows Java to make sure that your method is being used correctly by other people. Think of a method's overall description as a contract between the designer of that method (or class) and the caller of the method (you can be either side of that contract, of course). Usually, the description indicates the types of a method's arguments, what it returns, and the general semantics of what it normally does. Using throws, you add information about the abnormal things it can do as well. This new part of the contract helps to separate and make explicit all the places where exceptional conditions should be handled in your program, and that makes large-scale design easier. Which Exceptions Should You Throw? Once you decide to declare that your method might throw an exception, you have to decide which exceptions it might throw (and actually throw them or call a method that will throw them-you'll learn about throwing your own exceptions in the next section). In many instances, this will be apparent from the operation of the method itself. Perhaps you're creating and throwing your own exceptions, in which case you'll know exactly which exceptions to throw. You don't really have to list all the possible exceptions that your method could throw; some exceptions are handled by the runtime itself and are so common (well, not common, but ubiquitous) that you don't have to deal with them. In particular, exceptions of either class Error or RuntimeException (or any of their subclasses) do not have to be listed in your throws clause. They get special treatment because they can occur anywhere within a Java program and are usually conditions that you, as the programmer, did not directly cause. One good example is OutOfMemoryError, which can happen anywhere, at any time, and for any number of reasons. These two kinds of exceptions are called implicit exceptions, and you don't have to worry about them. Implicit exceptions are exceptions that are subclasses of the classes RuntimeException and Error. Implicit exceptions are usually thrown by the Java runtime itself. You do not have to declare that your method throws them. Note file:///G|/ebooks/1575211831/ch17.htm (9 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions You can, of course, choose to list these errors and runtime exceptions in your throws clause if you like, but the callers of your methods will not be forced to handle them; only non-runtime exceptions must be handled. All other exceptions are called explicit exceptions and are potential candidates of a throws clause in your method. Passing On Exceptions In addition to declaring methods that throw exceptions, there's one other instance in which your method definition may include a throws clause. In this case, you want to use a method that throws an exception, but you don't want to catch that exception or deal with it. In many cases, it might make more sense for the method that calls your method to deal with that exception rather than for you to deal with it. There's nothing wrong with this; it's a fairly common occurrence that you won't actually deal with an exception, but will pass it back to the method that calls yours. At any rate, it's a better idea to pass on exceptions to calling methods than to catch them and ignore them. Rather than using the try and catch clauses in the body of your method, you can declare your method with a throws clause such that it, too, might possibly throw the appropriate exception. Then it's the responsibility of the method that calls your method to deal with that exception. This is the other case that will satisfy the Java compiler that you have done something with a given method. Here's another way of implementing an example that reads characters from a stream: public void readTheFile(String filename) throws IO Exception { // open the file, init the stream, etc. while (numbytes <= mybuffer.length) { myinputstream.read(mybuffer); numbytes;++ } This example is similar to the example used previously today; remember that the read() method was declared to throw an IOException, so you had to use try and catch to use it. Once you declare your method to throw an exception, however, you can use other methods that also throw those exceptions inside the body of this method, without needing to protect the code or catch the exception. Note You can, of course, deal with other exceptions using try and catch in the body of your method in addition to passing on the exceptions you listed in the throws clause. You can also both deal with the exception in some way and then re-throw it so that your method's calling method has to deal with it anyhow. You'll learn how to throw methods in the next section. throws and Inheritance If your method definition overrides a method in a superclass that includes a throws clause, there are special rules for how your overridden method deals with throws. Unlike with the other parts of the method file:///G|/ebooks/1575211831/ch17.htm (10 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions signature, your new method does not have to have the same set of exceptions listed in the throws clause. Because there's a potential that your new method may deal better with exceptions, rather than just throwing them, your subclass's method can potentially throw fewer types of exceptions than its superclass's method definition, up to and including throwing no exceptions at all. That means that you can have the following two class definitions and things will work just fine: public class Fruit { public void ripen() throws RotException { ... } } public class WaxFruit extends Fruit { public void ripen() { ... } } The converse of this rule is not true; a subclass's method cannot throw more exceptions (either exceptions of different types or more general exception classes) than its superclass's method. Creating and Throwing Your Own Exceptions There are two sides to every exception: the side that throws the exception and the side that catches it. An exception can be tossed around a number of times to a number of methods before it's caught, but eventually it'll be caught and dealt with. But who does the actual throwing? Where do exceptions come from? Many exceptions are thrown by the Java runtime, or by methods inside the Java classes themselves. You can also throw any of the standard exceptions that the Java class libraries define, or you can create and throw your own exceptions. This section describes all these things. Throwing Exceptions Declaring that your method throws an exception is useful only to users of your method and to the Java compiler, which checks to make sure all your exceptions are being dealt with. But the declaration itself doesn't do anything to actually throw that exception should it occur; you have to do that yourself in the body of the method. Remember that exceptions are all instances of some exception class, of which there are many defined in the standard Java class libraries. In order to throw an exception, therefore, you'll need to create a new instance of an exception class. Once you have that instance, use the throw statement to throw it (could this be any easier?). The simplest way to throw an exception is simply like this: throw new ServiceNOteAvailableException(); Technical Note file:///G|/ebooks/1575211831/ch17.htm (11 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions You can only throw objects that are instances of subclasses of Throwable. This is different from C++'s exceptions, which allow you to throw objects of any type. Depending on the exception class you're using, the exception may also have arguments to its constructor that you can use. The most common of these is a string argument, which lets you describe the actual problem in greater detail (which can be very useful for debugging purposes). Here's an example: throw new ServiceNotAvailableException("Exception: service not available, database is offline."); Once an exception is thrown, the method exits immediately, without executing any other code (other than the code inside finally, if that clause exists) and without returning a value. If the calling method does not have a try or catch surrounding the call to your method, the program may very well exit based on the exception you threw. Creating Your Own Exceptions Exceptions are simply classes, just like any other classes in the Java hierarchy. Although there are a fair number of exceptions in the Java class library that you can use in your own methods, there is a strong possibility that you may want to create your own exceptions to handle different kinds of errors your programs might run into. Fortunately, creating new exceptions is easy. Your new exception should inherit from some other exception in the Java hierarchy. Look for an exception that's close to the one you're creating; for example, an exception for a bad file format would logically be an IOException. If you can't find a closely related exception for your new exception, consider inheriting from Exception, which forms the "top" of the exception hierarchy for explicit exceptions (remember that implicit exceptions, which include subclasses of Error and RuntimeException, inherit from Throwable). Exception classes typically have two constructors: The first takes no arguments and the second takes a single string as an argument. In the latter case you'll want to call super() in that constructor to make sure the string is applied to the right place in the exception. Beyond those three rules, exception classes look just like other classes. You can put them in their own source files and compile them just as you would other classes: public class SunSpotException extends Exception { public SunSpotException() {} public SunSpotExceotion(String msg) { super(msg); } } Doing It All: Combining throws, try, and throw What if you want to combine all the approaches shown so far? In your method, you'd like to handle incoming exceptions yourself, but also you'd like to pass the exception up to your caller. Simply using try and catch doesn't pass on the exception, and simply adding a throws clause doesn't give you a chance to deal with the exception. If you want to both manage the exception and pass it on to the caller, use all three mechanisms: the file:///G|/ebooks/1575211831/ch17.htm (12 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions throws clause, the try statement, and by explicitly rethrowing the exception: public void responsibleExceptionalMethod() throws MyFirstException { MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass(); try { aMFEC.anExceptionalMethod(); } catch (MyFirstException m) { ... // do something responsible throw m; // re-throw the exception } } This works because exception handlers can be nested. You handle the exception by doing something responsible with it, but decide that it is too important to not give an exception handler that might be in your caller a chance to handle it as well. Exceptions float all the way up the chain of method callers this way (usually not being handled by most of them) until at last the system itself handles any uncaught ones by aborting your program and printing an error message. In a standalone program, this is not such a bad idea; but in an applet, it can cause the browser to crash. Most browsers protect themselves from this disaster by catching all exceptions themselves whenever they run an applet, but you can never tell. If it's possible for you to catch an exception and do something intelligent with it, you should. When and When Not to Use Exceptions To finish up today's lesson, here's a quick summary and some advice on when to use exceptions…and when not to use them. When to Use Exceptions Because throwing, catching, and declaring exceptions are interrelated concepts and can be very confusing, here's a quick summary of when to do what. If your method uses someone else's method, and that method has a throws clause, you can do one of three things: q Deal with the exception using try and catch statements. q Pass the exception up the calling chain by adding your own throws clause to your method definition. q Do both of the above by catching the exception using catch and then explicitly rethrowing it using throw. In cases where a method throws more than one exception, you can, of course, handle each of those exceptions differently. For example, you might catch some of those exceptions while allowing others to pass up the calling chain. If your method throws its own exceptions, you should declare that it throws those methods using the throws clause. If your method overrides a superclass's method that has a throws clause, you can throw the same types of exceptions or subclasses of those exceptions; you cannot throw any different types of exceptions. And, finally, if your method has been declared with a throws clause, don't forget to actually throw the file:///G|/ebooks/1575211831/ch17.htm (13 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions exception in the body of your method using throw. When Not to Use Exceptions Exceptions are cool. But they aren't that cool. There are several cases in which you should not use exceptions, even though they may seem appropriate at the time. First, you should not use exceptions if the exception is something that you expect and a simple test to avoid that exceptional condition would make much more sense. For example, although you can rely on an ArrayIndexOutofBounds exception to tell you when you've gone past the end of the array, a simple test of the length of the array in your code to make sure you don't get that exception in the first place is a much better idea. Or if your users are going to enter data that you need to be a letter, testing to make sure that data is a letter is a much better idea than throwing an exception and dealing with it somewhere else. Exceptions take up a lot of processing time for your Java program. Whereas you may find exceptions stylistically interesting for your own code, a simple test or series of tests will run much faster and make your program that much more efficient. Exceptions, as I mentioned earlier, should only be used for truly exceptional cases that are out of your control. It's also easy to get carried away with exceptions and to try to make sure that all your methods have been declared to throw all the possible exceptions that they can possibly throw. In addition to making your code more complex in general, if other people will be using your code, they'll have to deal with handling all the exceptions that your methods might throw. You're making more work for everyone involved when you get carried away with exceptions. Declaring a method to throw either few or lots of exceptions is a trade-off; the more exceptions your method potentially throws, the more complex that method is to use. Declare only the exceptions that have a reasonably fair chance of happening and that make sense for the overall design of your classes. Bad Style Using Exceptions When you first start using exceptions, it might be appealing to work around the compiler errors that result when you use a method that declared a throws clause. While it is legal to add an empty catch clause or to add a throws clause to your own method (and there are appropriate reasons for doing both of these things), intentionally dropping exceptions on the floor and subverting the checks the Java compiler does for you is very bad style. The Java exception system was designed so that if a potential error can occur, you're warned about it. Ignoring those warnings and working around them makes it possible for fatal errors to occur in your program-errors that you could have avoided with a few lines of code. And, even worse, adding throws clauses to your methods to avoid exceptions means that the users of your methods (objects further up in the calling chain) will have to deal with them. You've just made more work for someone else and made your methods more difficult to use for other people. Compiler errors regarding exceptions are there to remind you to reflect on these issues. Take the time to deal with the exceptions that may affect your code. This extra care will richly reward you as you reuse your classes in later projects and in larger and larger programs. Of course, the Java class library has been written with exactly this degree of care, and that's one of the reasons it's robust enough to be used in constructing all your Java projects. file:///G|/ebooks/1575211831/ch17.htm (14 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions Summary Today you have learned about how exceptions aid your program's design and robustness. Exceptions give you a way of managing potential errors in your programs and of alerting users of your programs that potential errors can occur. The Java class library has a vast array of exceptions defined and thrown, and also allows you to define and throw your own exceptions. Using try, catch, and finally you can protect code that may result in exceptions, catch and handle those exceptions if they occur, and execute code whether or not an exception was generated. Handling exceptions is only half of the equation; the other half is generating and throwing exceptions yourself. Today you have learned about the throws clause, which tells users of your method that the method might throw an exception. throws can also be used to "pass on" an exception from a method call in the body of your method. In addition to the information given by the throws clause, you learned how to actually create and throw your own methods be defining new exception classes and by throwing instances of any exception classes using throw. And, finally, Java's reliance on strict exception handling does place some restrictions on the programmer, but you have learned that these restrictions are light compared to the rewards. Q&A Q: A: Q: A: I'm still not sure I understand the differences between exceptions, errors, and runtime exceptions. Is there another way of looking at them? Errors are caused by dynamic linking, or virtual machine problems, and are thus too low-level for most programs to care about-or be able to handle even if they did care about them. Runtime exceptions are generated by the normal execution of Java code, and although they occasionally reflect a condition you will want to handle explicitly, more often they reflect a coding mistake by the programmer and thus simply need to print an error to help flag that mistake. Exceptions that are not runtime exceptions (IOException exceptions, for example) are conditions that, because of their nature, should be explicitly handled by any robust and well-thought-out code. The Java class library has been written using only a few of these, but those few are extremely important to using the system safely and correctly. The compiler helps you handle these exceptions properly via its throws clause checks and restrictions. Is there any way to "get around" the strict restrictions placed on methods by the throws clause? Yes. Suppose you have thought long and hard and have decided that you need to circumvent this restriction. This is almost never the case, because the right solution is to go back and redesign your methods to reflect the exceptions that you need to throw. Imagine, however, that for some reason a system class has you in a straitjacket. Your first solution is to subclass RuntimeException to make up a new, exempt exception of your own. Now you can throw it to your heart's content, because the throws clause that was annoying you does not need to include this new exception. If you need a lot of such exceptions, an elegant approach is to mix in some novel exception interfaces to your new Runtime classes. You're free to choose whatever subset of these new interfaces you want to catch (none of the normal Runtime exceptions need be caught), while any leftover (new) Runtime exceptions are (legally) allowed to go through that otherwise annoying standard method in the library. file:///G|/ebooks/1575211831/ch17.htm (15 of 16) [11/06/2000 7:45:47 PM] Day 17 -- Exceptions Q: A: Q: I'm still a little confused by long chains of catch clauses. Can you label the previous example with which exceptions are handled by each line of code? Certainly. Here it is: try { someReallyExceptionalMethod(); } catch (NullPointerException n) { . . . // handles NullPointerExceptions } catch (RuntimeException r) { . . . // handles RuntimeExceptions //that are not NullPointerExceptions } catch (IOException I) { . . . // handles IOExceptions } catch (MyFirstException m) { . . . // handles MyFirstExceptions } catch (Exception e) { . . . // handles Exceptions that are not // RuntimeExceptions nor IOExceptions // nor MyFirstExceptions } catch (Throwable t) { . . . // handles Throwables that // are not Exceptions (i.e., Errors) } Given how annoying it can sometimes be to handle exceptional conditions properly, what's stopping me from surrounding any method as follows: try { thatAnnoyingMethod(); } catch (Throwable t) { } A: and simply ignoring all exceptions? Nothing, other than your own conscience. In some cases, you should do nothing, because it is the correct thing to do for your method's implementation. Otherwise, you should struggle through the annoyance and gain experience. Good style is a struggle even for the best of programmers, but the rewards are rich indeed. file:///G|/ebooks/1575211831/ch17.htm (16 of 16) [11/06/2000 7:45:47 PM] file:///G|/ebooks/1575211831/f17-1.gif file:///G|/ebooks/1575211831/f17-1.gif [11/06/2000 7:45:47 PM] Day 10 -- Simple Animation and Threads Day 10 Simple Animation and Threads by Laura Lemay CONTENTS q Creating Animation in Java r r Starting and Stopping an Applet's Execution r The Missing Link: Threads r q Painting and Repainting Putting It Together Threads: What They Are and Why You Need Them r r q Writing Applets with Threads Another Look at the Digital Clock Reducing Animation Flicker r Flicker and How to Avoid It r How to Override update() r Solution One: Don't Clear the Screen r Solution Two: Redraw Only What You Have To q Summary q Q&A The first thing I ever saw Java do was an animation: a large red Hi there! that ran across the screen from the right to left. Even that simple form of animation was enough to make me stop and think, "this is really cool." That sort of simple animation takes only a few methods to implement in Java, but those few methods are the basis for any Java applet that you want to update the screen dynamically-for something as simple as flashy animation applets, or for more complex applets that may need to be updated based on data they get from the user, from databases connected to over the network, or from any other source. Animation in Java is accomplished through various interrelated parts of the Java Abstract Windowing Toolkit (awt). Today you'll learn the fundamentals of animation in Java: how the various parts of the system all work together so that you can create moving figures and dynamically updatable applets. Specifically, you'll explore the following: q How to create animation in Java-the paint() and repaint() methods, starting and stopping dynamic applets, and how to use and override these methods in your own applets q Threads-what they are and how they can make your applets more well-behaved with other applets and with other parts of the awt q Reducing animation flicker, which is a common problem with animation in Java Throughout today, you'll also work with lots of examples of real applets that create animation or perform some kind of dynamic movement. file:///G|/ebooks/1575211831/ch10.htm (1 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads Creating Animation in Java Animation in Java involves two basic steps: constructing a frame of animation, and then asking Java to paint that frame. You repeat these steps as necessary to create the illusion of movement. The basic, static graphical applets that you created yesterday taught you how to accomplish the first part; all that's left is how to tell Java to paint a frame. Painting and Repainting The paint() method, as you learned yesterday, is called whenever an applet needs to be painted-when the applet is initially drawn, when the window containing it is moved, or when another window is moved from over it. You can also, however, ask Java to repaint the applet at a time you choose. So, to change the appearance of what is on the screen, you construct the image or "frame" you want to paint, and then ask Java to paint this frame. If you do this repeatedly, and fast enough, you get animation inside your Java applet. That's all there is to it. Where does all this take place? Not in the paint() method itself. All paint() does is put dots on the screen. paint(), in other words, is responsible only for the current frame of the animation. The real work of changing what paint() does, of modifying the frame for an animation, actually occurs somewhere else in the definition of your applet. In that "somewhere else," you construct the frame (set variables for paint() to use, create Color or Font or other objects that paint() will need), and then call the repaint() method. repaint() is the trigger that causes Java to call paint() and causes your frame to get drawn. Technical Note Because a Java applet can contain many different components that all need to be painted (as you'll learn later this week), and in fact, applets can be embedded inside a larger Java application that also paints to the screen in similar ways, when you call repaint() (and therefore paint()) you're not actually immediately drawing to the screen as you do in other window or graphics toolkits. Instead, repaint() is a request for Java to repaint your applet as soon as it can. Also, if too many repaint() requests are made in a short amount of time, the system may only call repaint() once for all of them. Much of the time, the delay between the call and the actual repaint is negligible. However, for very tight loops, the awt may collapse several calls to repaint() into one. Keep this in mind as you create your own animation. Starting and Stopping an Applet's Execution Remember start() and stop() from Day 8, "Java Applet Basics"? These are the methods that trigger your applet to start and stop running. You didn't use start() and stop() yesterday because the applets on that day did nothing except paint once. With animation and other Java applets that are actually processing and running over time, you'll need to make use of start() and stop() to trigger the start of your applet's execution, and to stop it from running when you leave the page that contains that applet. For many applets, you'll want to override start() and stop() for just this reason. The start() method triggers the execution of the applet. You can either do all the applet's work inside that method, or you can call other object's methods in order to do so. Usually, start() is used to create and begin execution of a thread so the applet can run in its own time. stop(), on the other hand, suspends an applet's execution so when you move off the page on which the applet is displaying, it doesn't keep running and using up system resources. Most of the time when you create a start() file:///G|/ebooks/1575211831/ch10.htm (2 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads method, you should also create a corresponding stop(). The Missing Link: Threads There's one more part to the animation mix that you'll have to know about, and that's threads. I'm going to discuss threads in a lot greater detail later on in this lesson (and in even more detail on Day 18, "Multithreading") but for now here's the basic idea: Anything you do in a Java program that runs continually and takes up a lot of processing time should run in its own thread. Animation is one of these things. To accomplish animation in Java, therefore, you use the start() method to start a thread, and then do all your animation processing inside the thread's run() method. This allows the animation to run on its own without interfering with any other parts of the program. Putting It Together Explaining how to do Java animation is more of a task than actually showing you how it works in code. An example will help make the relationship between all these methods clearer. Listing 10.1 shows a sample applet that uses basic applet animation techniques to display the date and time and constantly updates it every second, creating a very simple animated digital clock (a frame from that clock is shown in Figure 10.1). Figure 10.1 : The digital clock. This applet uses the paint(), repaint(), start(), and stop() methods. It also uses threads. For this discussion, we'll focus on the animation parts of the applet and won't worry so much about how the threads work. We'll take another look at this applet later, after we've discussed threads in greater detail. Listing 10.1. The DigitalClock applet. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 21: 22: 23: 24: import java.awt.Graphics; import java.awt.Font; import java.util.Date; public class DigitalClock extends java.applet.Applet implements Runnable { Font theFont = new Font("TimesRoman",Font.BOLD,24); Date theDate; Thread runner; public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void stop() { if (runner != null) { runner.stop(); runner = null; } } file:///G|/ebooks/1575211831/ch10.htm (3 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:} public void run() { while (true) { theDate = new Date(); repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } public void paint(Graphics g) { g.setFont(theFont); g.drawString(theDate.toString(),10,50); } Analysis We'll look at this applet from the perspective of the actual animation parts in this section, and deal with the parts that manage threads later on. Lines 7 and 8 define two basic instance variables: theFont and theDate, which hold objects representing the current font and the current date, respectively. You'll learn more about these later. The start() and stop() methods here start and stop a thread; the bulk of the applet's work goes on in the run() methods (lines 25 to 32). Inside run() is where the animation actually takes place. Note the while loop inside this method (line 26); given that the test (true) always returns true, the loop never exits. A single animation frame is constructed inside that while loop, with the following steps: q The Date class represents a date and time (Date is part of the java.util package-note that it was specifically imported in line 3). Line 27 creates a new instance of the Date class, which holds the current date and time, and assigns it to the theDate instance variable. q The repaint() method is called (line 28) to repaint the applet. q Lines 14 and 15, as complicated as they look, do nothing except pause for 1000 milliseconds (1 second) before the loop repeats. The sleep() method there, part of the Thread class, is what causes the applet to pause. Without a specific sleep() method, the applet would run as fast as it possibly could, which, for most computer systems, would be too fast for the eye to see. The sleep() method controls exactly how fast the animation takes place. The try and catch stuff around it enables Java to manage errors if they occur. try and catch handle exceptions and are described on Day 17, "Exceptions." On to the paint() method in lines 34 through 37. Here, inside paint(), all that happens is that the current font (in the variable theFont) is set, and the date itself is printed to the screen (note that you have to call the toString() method to convert the date to a string). Because paint() is called repeatedly with whatever value happens to be in theDate, the string is updated every second to reflect the new date. There are a few things to note about this example. First, you might think it would be easier to create the new Date object inside the paint() method. That way you could use a local variable and not need an instance variable to pass the Date object around. Although doing things that way creates cleaner code, it also results in a less efficient program. The paint() method is called every time a frame needs to be changed. In this case, it's not that important, but in an animation that needs to change frames very quickly, the paint() method has to pause to create that new object every time. By leaving paint() to do what it does best-painting the screen-and calculating new objects beforehand, you can make painting as efficient as possible. This is precisely the same reason why the Font object is also in an instance file:///G|/ebooks/1575211831/ch10.htm (4 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads variable. Threads: What They Are and Why You Need Them So what are these threads all about? Why are they important to animation? Threads are a very important part of Java and of programming Java. The larger your Java programs get and the more things they do, the more likely it is that you'll want to use threads. Depending on your experience with operating systems and with environments within those systems, you may or may not have run into the concept of threads, so let's start from the beginning. First, the analogy. A group of students is on a bus, on a field trip somewhere. To pass the time, the teachers are leading a sing-along. As the trip progresses, the students sing one song, then when that song is done, they sing another song. While different parts of the bus could sing different songs, it wouldn't sound very good, so the singing of one song monopolizes the time until its done, at which time another song can start. Now let's say you have two busses; both are on the same route to the field trip, both are going at the same speed, and both are full of students singing songs. But the songs being sung by the students in the second bus don't interfere with the songs being sung in the first bus; in this way you can get twice as many songs sung in the same amount of time by singing them in parallel. Threads are like that. In a regular single-threaded program, the program starts executing, runs its initialization code, calls methods or procedures, and continues running and processing until it's complete or until the program is exited. That program runs in a single thread-it's the one bus with all the students. Multithreading, as in Java, means that several different parts of the same program can run at the same time, in parallel, without interfering with each other. Multiple threads, each running by itself, are like multiple busses with different things going on in each bus. Here's a simple example. Suppose you have a long computation near the start of a program's execution. This long computation may not be needed until later in the program's execution-it's actually tangential to the main point of the program, but it needs to get done eventually. In a single-threaded program, you have to wait for that computation to finish before the rest of the program can continue running. In a multithreaded system, you can put that computation into its own thread, and the rest of the program can continue to run independently. Animation is an example of the kind of task that needs its own thread. Take, for example, that digital clock applet, which has an endless while() loop. If you didn't use threads, while() would run in the default Java system thread, which is also responsible for handling painting the screen, dealing with user input like mouse clicks, and keeping everything internally up- to-date. Unfortunately, however, if you run that while() loop in the main system thread, it will monopolize all Java's resources and prevent anything else-including painting-from happening. You'd never actually see anything on the screen because Java would be sitting and waiting for the while() loop to finish before it did anything else. And that's not what you want. Using threads in Java, you can create parts of an applet (or application) that run in their own threads, and those parts will happily run all by themselves without interfering with anything else. Depending on how many threads you have, you may eventually tax the system so that all of them will run slower, but all of them will still run independently. Even if you don't use lots of them, using threads in your applets is a good Java programming practice. The general rule of thumb for well-behaved applets: Whenever you have any bit of processing that is likely to continue for a long time (such as an animation loop, or a bit of code that takes a long time to execute), put it in a thread. file:///G|/ebooks/1575211831/ch10.htm (5 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads Writing Applets with Threads Creating applets that use threads is very easy. In fact, many of the basic things you need to do to use threads are just boilerplate code that you can copy and paste from one applet to another. Because it's so easy, there's almost no reason not to use threads in your applets, given the benefits. There are four modifications you need to make to create an applet that uses threads: q Change the signature of your applet class to include the words implements Runnable. q Include an instance variable to hold the applet's thread object. q Create a start() method that does nothing but create a thread and start it running. q Create a stop() method that stops the thread. q Create a run() method that contains the actual code that controls the applet. The first change is to the first line of your class definition. You've already got something like this: public class MyAppletClass extends java.applet.Applet { ... } You need to change it to the following: public class MyAppletClass extends java.applet.Applet ... } implements Runnable { What does this do? It includes support for the Runnable interface in your applet. If you think way back to Day 2, "Object-Oriented Programming and Java," you'll remember that interfaces are a way to collect method names common to different classes, which can then be mixed in and implemented inside different classes that need to implement that behavior. Here, the Runnable interface defines the behavior your applet needs to run a thread; in particular, it gives you a default definition for the run() method. By implementing Runnable, you tell others that they can call the Run() method on your instances. The second step is to add an instance variable to hold this applet's thread. Call it anything you like; it's a variable of the type Thread (Thread is a class in java.lang, so you don't have to import it): Thread runner; Third, add a start() method or modify the existing one so that it does nothing but create a new thread and start it running. Here's a typical example of a start() method: public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } If you modify start() to do nothing but spawn a thread, where does the code that drives your applet go? It goes into a new method, run(), which looks like this: public void run() { // what your applet actually does file:///G|/ebooks/1575211831/ch10.htm (6 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads } Your run() method actually overrides the default version of run(), which you get when you include the Runnable interface with your applet. run() is one of those standard methods, like start() and paint(), that you override in your own classes to get standard behavior. run() can contain anything you want to run in the separate thread: initialization code, the actual loop for your applet, or anything else that needs to run in its own thread. You also can create new objects and call methods from inside run(), and they'll also run inside that thread. The run() method is the real heart of your applet. Finally, now that you've got threads running and a start() method to start them, you should add a stop() method to suspend execution of that thread (and therefore whatever the applet is doing at the time) when the reader leaves the page. stop(), like start(), is usually something along these lines: public void stop() { if (runner != null) { runner.stop(); runner = null; } } The stop() method here does two things: It stops the thread from executing and also sets the thread's variable runner to null. Setting the variable to null makes the Thread object it previously contained available for garbage collection so that the applet can be removed from memory after a certain amount of time. If the reader comes back to this page and this applet, the start() method creates a new thread and starts up the applet once again. And that's it! Four basic modifications, and now you have a well-behaved applet that runs in its own thread. Another Look at the Digital Clock Let's take another look at that DigitalClock applet, this time from the standpoint of threads. Listing 10.2 shows that applet's code once again. Listing 10.2. The DigitalClock applet, revisited. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: import java.awt.Graphics; import java.awt.Font; import java.util.Date; public class DigitalClock extends java.applet.Applet implements Runnable { Font theFont = new Font("TimesRoman",Font.BOLD,24); Date theDate; Thread runner; public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } file:///G|/ebooks/1575211831/ch10.htm (7 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads 19: 20: 21: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:} public void stop() { if (runner != null) { runner.stop(); runner = null; } } public void run() { while (true) { theDate = new Date(); repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } public void paint(Graphics g) { g.setFont(theFont); g.drawString(theDate.toString(),10,50); } Analysis Let's look at the lines of this applet that create and manage threads. First, look at the class definition itself in lines 5 and 6; note that the class definition includes the Runnable interface. Any classes you create that use threads must include Runnable. Line 10 defines a third instance variable for this class called runner of type Thread, which will hold the thread object for this applet. Lines 12 through 23 define the boilerplate start() and stop() methods that do nothing except create and destroy threads. These method definitions can essentially be exactly the same from class to class because all they do is set up the infrastructure for the thread itself. And, finally, the bulk of your applet's work goes on inside the run() method in lines 25 through 32, as we already discussed the last time we looked at this applet. Inside this method is the endless while loop, the calls to repaint(), and the sleep() method, which pauses things so they only run once a second. Reducing Animation Flicker If you've been following along with this lesson and trying the examples as you go, rather than reading this book on an airplane or in the bathtub, you may have noticed that when the digital clock program runs, every once in a while there's an annoying flicker in the animation. (Not that there's anything wrong with reading this book in the bathtub, but you won't see the flicker if you do that, so just trust me-there's a flicker.) This isn't a mistake or an error in the program; in fact, that flicker is a side effect of creating animation in Java. Because it is really annoying, you'll learn how to reduce flicker in this part of today's lesson so that your animations run cleaner and look better on the screen. file:///G|/ebooks/1575211831/ch10.htm (8 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads Flicker and How to Avoid It Flicker is caused by the way Java paints and repaints each frame of an applet. At the beginning of today's lesson, you learned that when you call the repaint() method, repaint() calls paint(). That's not precisely true. A call to paint() does indeed occur in response to a repaint(), but what actually happens are the following steps: 1. The call to repaint() results in a call to the method update(). 2. The update() method clears the screen of any existing contents (in essence, fills it with the current background color), and then calls paint(). 3. The paint() method then draws the contents of the current frame. It's step 2, the call to update(), that causes animation flicker. Because the screen is cleared between frames, the parts of the screen that don't change alternate rapidly between being painted and being cleared. Hence, flickering. There are two major ways to avoid flicker in your Java applets: q Override update() either not to clear the screen at all, or to clear only the parts of the screen you've changed. q Override both update() and paint(), and use double-buffering. If the second way sounds complicated, that's because it is. Double-buffering involves drawing to an offscreen graphics surface and then copying that entire surface to the screen. Because it's more complicated, you'll explore that one tomorrow. Today let's cover the easier solution: overriding update(). How to Override update() The cause of flickering lies in the update() method. To reduce flickering, therefore, override update(). Here's what the default version of update() does (comes from the Component class, is part of the awt, and is one of the superclasses of the applet class. You'll learn more about it on Day 13, "Creating User Interfaces with the awt"): public void update(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, width, height); g.setColor(getForeground()); paint(g); } Basically, update() clears the screen (or, to be exact, fills the applet's bounding rectangle with the background color), sets things back to normal, and then calls paint(). When you override update(), you have to keep these two things in mind and make sure that your version of update() does something similar. In the next two sections, you'll work through some examples of overriding update() in different cases to reduce flicker. Solution One: Don't Clear the Screen The first solution to reducing flicker is not to clear the screen at all. This works only for some applets, of course. Here's an example of an applet of this type. The ColorSwirl applet prints a single string to the screen ("All the Swirly Colors"), but that string is presented in different colors that fade into each other dynamically. This applet flickers terribly when it's run. Listing 10.3 shows the initial source for this applet, and Figure 10.2 shows the result. Figure 10.2 : The ColorSwirl applet. Listing 10.3. The ColorSwirl applet. 1: 2: import java.awt.Graphics; import java.awt.Color; file:///G|/ebooks/1575211831/ch10.htm (9 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads 3: import java.awt.Font; 4: 5: public class ColorSwirl extends java.applet.Applet 6: implements Runnable { 7: 8: Font f = new Font("TimesRoman",Font.BOLD,48); 9: Color colors = new Color[50]; 10: Thread runThread; 11: 12: public void start() { 13: if (runThread == null) { 14: runThread = new Thread(this); 15: runThread.start(); 16: } 17: } 18: 19: public void stop() { 20: if (runThread != null) { 21: runThread.stop(); 22: runThread = null; 23: } 24: } 25: 26: public void run() { 27: 28: // initialize the color array 29: float c = 0; 30: for (int i = 0; i < colors.length; i++) { 31: colors[i] = 32: Color.getHSBColor(c, (float)1.0,(float)1.0); 33: c += .02; 34: } 35: 36: // cycle through the colors 37: int i = 0; 38: while (true) { 39: setForeground(colors[i]); 40: repaint(); 41: i++; 42: try { Thread.sleep(50); } 43: catch (InterruptedException e) { } 44: if (i == colors.length ) i = 0; 45: } 46: } 47: 48: public void paint(Graphics g) { 49: g.setFont(f); 50: g.drawString("All the Swirly Colors", 15, 50); 51: } 52: }] file:///G|/ebooks/1575211831/ch10.htm (10 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads Analysis There are three new things to note about this applet that might look strange to you: q q q Line 9 defines an instance variable Colors, which is an array of 50 elements. When the applet starts, the first thing you do in the run() method (in lines 28 through 34) is to fill up that array with color objects. By creating all the colors beforehand, you can then jusxt draw text in that color, one at a time; it's easier to precompute all the colors at once (and, in fact, this for loop might make more sense in an init() method because it only needs to happen once). Note that I arbitrarily picked the number 50 for the number of colors we'll be using; we could just as easily cycle through 20 or 250 colors. To create the different color objects, we used a method in the Color class called getHSBColor(), rather than just using new with various RGB values. The getHSBColor() class method creates a color object based on values for hue, saturation, and brightness, rather than the standard red, green, and blue. HSB is simply a different way of looking at colors, and by incrementing the hue value and keeping saturation and brightness constant, you can create a range of colors without having to know the RGB for each one. If you don't understand this, don't worry about it; it's just a quick and easy way to create the color array. To create the animation, the applet cycles through the array of colors, setting the foreground color to each color object in turn and calling repaint(). When it gets to the end of the array, it starts over again (line 44), so the process repeats over and over ad infinitum. Now that you understand what the applet does, let's fix the flicker. Flicker here results because each time the applet is painted, there's a moment where the screen is cleared. Instead of the text cycling neatly from red to a nice pink to purple, it's going from red to gray, to pink to gray, to purple to gray, and so on-not very nice looking at all. Because the screen clearing is all that's causing the problem, the solution is easy: Override update() and remove the part where the screen gets cleared. It doesn't really need to get cleared anyhow, because nothing is changing except the color of the text. With the screen clearing behavior removed from update(), all update needs to do is call paint(). Here's what the update() method looks like in this applet (you'll want to add it after the paint() method after line 51): public void update(Graphics g) { paint(g); } With that-with one small three-line addition-no more flicker. Wasn't that easy? Note If you're following along with the examples on the CD, the ColorSwirl.java file contains the original applet with the flicker; ColorSwirl2.java has the fixed version. Solution Two: Redraw Only What You Have To For some applets, it won't be quite as easy as just not clearing the screen. With some kinds of animation, clearing the screen is necessary for the animation to work properly. Here's another example. In this applet, called Checkers, a red oval (a checker piece) moves from a black square to a white square, as if on a checkerboard. Listing 10.4 shows the code for this applet, and Figure 10.3 shows the applet itself. Figure 10.3 : The Checkers applet. Listing 10.4. The Checkers applet. file:///G|/ebooks/1575211831/ch10.htm (11 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: import java.awt.Graphics; import java.awt.Color; public class Checkers extends java.applet.Applet implements Runnable { Thread runner; int xpos; public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void stop() { if (runner != null) { runner.stop(); runner = null; } } public void run() { setBackground(Color.blue); while (true) { for (xpos = 5; xpos <= 105; xpos+=4) { repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { } } xpos = 5; } } public void paint(Graphics g) { // Draw background g.setColor(Color.black); g.fillRect(0, 0, 100, 100); g.setColor(Color.white); g.fillRect(101, 0, 100, 100); // Draw checker g.setColor(Color.red); g.fillOval(xpos, 5, 90, 90); } } Analysis file:///G|/ebooks/1575211831/ch10.htm (12 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads Here's a quick run-through of what this applet does: An instance variable, xpos, keeps track of the current starting position of the checker (because it moves horizontally, the y stays constant and only the x changes; we don't need to keep track of the y position). In the run() method, you change the value of x and repaint, waiting 100 milliseconds between each move. The checker then appears to move from the left side of the screen to the right, resetting back at its original position once it hits the right side of the screen. In the actual paint() method, the background squares are painted (one black and one white), and then the checker is drawn at its current position. This applet, like the ColorSwirl applet, also has a terrible flicker. (In line 25, I changed the background color to blue to emphasize it, so if you run this applet, you'll definitely see the flicker.) However, the solution to solving the flicker problem for this applet is more difficult than for the last one, because you actually do want to clear the screen before the next frame is drawn. Otherwise, the red checker won't have the appearance of leaving one position and moving to another; it'll just leave a red smear from one side of the checkerboard to the other. How do you get around this? You still clear the screen, in order to get the animation effect, but, rather than clearing the entire screen each time, you clear only the part that has actually changed from one frame to the next. By limiting the redraw to only a small area, you can eliminate some of the flicker you get from redrawing the entire screen. To limit what gets redrawn, you need a couple things. First, you need a way to restrict the drawing area so that each time paint() is called, only the part that needs to get redrawn actually gets redrawn. Fortunately, this is easy by using a mechanism called clipping. Clipping, part of the graphics class, enables you to restrict the drawing area to a small portion of the full screen; although the entire screen may get instructions to redraw, only the portions inside the clipping area are actually drawn. New Term Clipping restricts the drawing area to some smaller portion of the screen. The second thing you need is a way to keep track of the actual area to redraw. Both the left and right edges of the drawing area change for each frame of the animation (one side to draw the new oval, the other to erase the bit of the oval left over from the previous frame), so to keep track of those two x values, you need instance variables for both the left side and the right. With those two concepts in mind, let's start modifying the Checkers applet to redraw only what needs to be redrawn. First, you'll add instance variables for the left and right edges of the drawing area. Let's call those instance variables ux1 and ux2 (u for update), where ux1 is the left side of the area to draw and ux2 the right: int ux1,ux2; Now let's modify the run() method so that it keeps track of the actual area to be drawn, which you would think is easy-just update each side for each iteration of the animation. Here, however, things can get complicated because of the way Java uses paint() and repaint(). The problem with updating the edges of the drawing area with each frame of the animation is that for every call to repaint() there may not be an individual corresponding paint(). If system resources get tight (because of other programs running on the system or for any other reason), paint() may not get executed immediately and several calls to paint() may queue up waiting for their turn to change the pixels on the screen. In this case, rather than trying to make all those calls to paint() in order (and be potentially behind all the time), Java catches up by executing only the most recent call to paint() and skips all the others. file:///G|/ebooks/1575211831/ch10.htm (13 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads This poses a difficult problem in the Checkers applet. If you update the edges of the drawing area with each call to repaint(), and a couple calls to paint() are skipped, you end up with bits of the drawing surface not being updated at all or bits of the oval (colloquially called "turds") left behind. Because of how repaint() and paint() work in Java, you cannot guarantee that every single clipping region will eventually get painted-some may be skipped. The way to solve this is not to reset the clipping region to something new every single pass, but instead to reset the region only if that region was indeed updated. This way, if a couple of calls to paint() get skipped, the area to be updated will get larger for each frame, and when paint() finally gets caught up, everything will get repainted correctly. Yes, this is horrifyingly complex. If I could have written this applet more simply, I would have (and, in fact, I did make it as simple as I could after much rewriting), but without this mechanism the applet will not get repainted correctly (my first try at this applet left turds all over the place). Let's step through it slowly in the code so you can get a better grasp of what's going on at each step. Let's start with run(), where each frame of the animation takes place. Here's where you calculate each side of the clipping area based on the old position of the oval and the new position of the oval. The value of ux1 (the left side of the drawing area) is the previous oval's x position (xpos), and the value of ux2 is the x position of the current oval plus the width of that oval (90 pixels in this example). Here's what the old run() method looked like: public void run() { setBackground(Color.blue); while (true) { for (xpos = 5; xpos <= 105; xpos += 4) { repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { } } xpos = 5; } } For each step in which the oval moves toward the right, you first update ux2 (the right edge of the drawing area): ux2 = xpos + 90; Then, after the repaint() has occurred, you can update ux1 to reflect the old x position of the oval. However, you want to update this value only if the paint actually happened, so you don't end up skipping bits of the screen. How can you tell if the paint actually happened? You can reset ux1 in paint() to a given value (say 0), and then test inside run() to see whether you can update that value or whether you have to wait for the paint() to occur: if (ux1 == 0) ux1 = xpos; Finally, there's one other change to make. When the oval reaches the right side of the screen and resets back to its original position, there's one frame where you want to redraw the whole screen rather than create a clipping region (otherwise, the image of the oval would remain on the right side of the screen). So, in this one case, you want to set ux2 to be the full width of the applet. Here we'll modify the line we just put in to set the value of ux2, using an if statement to test to see if the oval is at the left side of the screen: if (xpos == 5) ux2 = size().width; else ux2 = xpos + 90; file:///G|/ebooks/1575211831/ch10.htm (14 of 18) [11/06/2000 7:45:50 PM] Day 10 -- Simple Animation and Threads The size() method is used to get the dimensions of the applet; size().width gives the full width of the applet so that the entire drawing surface will be updated. Here's the new version of run() with those changes in place: public void run() { setBackground(Color.blue); while (true) { for (xpos = 5; xpos <= 105; xpos+=4) { if (xpos == 5) ux2 = size().width; else ux2 = xpos + 90; repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { } if (ux1 == 0) ux1 = xpos; } xpos = 5; } } Those are the only modifications run() needs. Let's override update() to limit the region that is being painted to the left and right edges of the drawing area that you set inside run(). To clip the drawing area to a specific rectangle, use the clipRect() method. clipRect(), like drawRect(), fillRect(), and clearRect(), is defined for Graphics objects and takes four arguments: x and y starting positions, and the width and height of the region. Here's where ux1 and ux2 come into play. ux1 is the x point of the top corner of the region; then use ux2 to get the width of the region by subtracting ux1 from that value. The y values are the standard y values for the oval, which don't vary at all (the oval starts at y position 5 and ends at 95). Finally, to finish update(), you call paint(): public void update(Graphics g) { g.clipRect(ux1, 5, ux2 - ux1, 95); paint(g); } Note that with the clipping region in place, you don't have to do anything to the actual paint() method. paint() goes ahead and draws to the entire screen each time, but only the areas inside the clipping region actually get changed onscreen. You will need to make one change to paint(), however. You need to update the trailing edge of each drawing area inside paint() in case several calls to paint() were skipped. Because you are testing for a value of 0 inside run(), inside paint() you can merely reset ux1 and ux2 to 0 after drawing everything: ux1 = ux2 = 0; Those are the only changes you have to make to this applet in order to draw only the parts of the applet that changed (and to manage the case where some frames don't get updated immediately). Although this doesn't totally eliminate flickering in the animation, it does reduce it a great deal. Try it and see. Listing 10.5 shows the final code for the Checkers applet (called Checkers2.java). Listing 10.5. The final Checkers applet. 1: import java.awt.Graphics; 2: import java.awt.Color; file:///G|/ebooks/1575211831/ch10.htm (15 of 18) [11/06/2000 7:45:51 PM] Day 10 -- Simple Animation and Threads 3: 4: public class Checkers2 extends java.applet.Applet implements Runnable { 5: 6: Thread runner; 7: int xpos; 8: int ux1,ux2; 9: 10: public void start() { 11: if (runner == null) { 12: runner = new Thread(this); 13: runner.start(); 14: } 15: } 16: 17: public void stop() { 18: if (runner != null) { 19: runner.stop(); 20: runner = null; 21: } 22: } 23: 24: public void run() { 25: setBackground(Color.blue); 26: while (true) { 27: for (xpos = 5; xpos <= 105; xpos+=4) { 28: if (xpos == 5) ux2 = size().width; 29: else ux2 = xpos + 90; 30: repaint(); 31: try { Thread.sleep(100); } 32: catch (InterruptedException e) { } 33: if (ux1 == 0) ux1 = xpos; 34: } 35: xpos = 5; 36: } 37: } 38: 39: public void update(Graphics g) { 40: g.clipRect(ux1, 5, ux2 - ux1, 95); 41: paint(g); 42: } 43: 44: public void paint(Graphics g) { 45: // Draw background 46: g.setColor(Color.black); 47: g.fillRect(0, 0, 100, 100); 48: g.setColor(Color.white); 49: g.fillRect(101, 0, 100, 100); 50: 51: // Draw checker 52: g.setColor(Color.red); 53: g.fillOval(xpos, 5, 90, 90); 54: file:///G|/ebooks/1575211831/ch10.htm (16 of 18) [11/06/2000 7:45:51 PM] Day 10 -- Simple Animation and Threads 55: 56: 57: 58:} // reset the drawing area ux1 = ux2 = 0; } Summary Congratulations on getting through Day 10! This day was a bit rough; you've learned a lot, and it all might seem overwhelming. You learned about a plethora of methods to use and override-start(), stop(), paint(), repaint(), run(), and update()-and you got a basic foundation in creating and using threads. Other than handling bitmap images, which you'll learn about tomorrow, you now have the basic background to create just about any animation you want in Java. Q&A Q: A: Q: A: Q: A: Q: A: Q: A: Why all the indirection with paint(), repaint(), update(), and all that? Why not have a simple paint method that puts stuff on the screen when you want it there? The Java awt enables you to nest drawable surfaces within other drawable surfaces. When a paint() takes place, all the parts of the system are redrawn, starting from the outermost surface and moving downward into the most nested one. Because the drawing of your applet takes place at the same time everything else is drawn, your applet doesn't get any special treatment. Your applet will be painted when everything else is painted. Although with this system you sacrifice some of the immediacy of instant painting, it enables your applet to coexist with the rest of the system more cleanly. Are Java threads like threads on other systems? Java threads have been influenced by other thread systems, and if you're used to working with threads, many of the concepts in Java threads will be very familiar to you. You learned the basics today; you'll learn more next week on Day 18. When an applet uses threads, I just have to tell the thread to start and it starts, and tell it to stop and it stops? That's it? I don't have to test anything in my loops or keep track of its state? It just stops? It just stops. When you put your applet into a thread, Java can control the execution of your applet much more readily. By causing the thread to stop, your applet just stops running, and then resumes when the thread starts up again. Yes, it's all automatic. Neat, isn't it? The ColorSwirl applet seems to display only five or six colors, which isn't very swirly. What's going on here? This is the same problem you ran into yesterday. On some systems, there might not be enough colors available to be able to display all of them reliably. If you're running into this problem, besides upgrading your hardware, you might try quitting other applications running on your system that use color. Other browsers or color tools in particular might be hogging colors that Java wants to be able to use. Even with the changes you made, the Checkers applet still flickers. And, unfortunately, it will continue to do so. Reducing the size of the drawing area by using clipping does reduce the flickering, but it doesn't stop it entirely. For many applets, using either of the methods described today may be enough to reduce animation flicker to the point where your applet looks good. To get totally flicker-free animation, you'll need to use a technique called double-buffering, which you'll learn about tomorrow. file:///G|/ebooks/1575211831/ch10.htm (17 of 18) [11/06/2000 7:45:51 PM] Day 10 -- Simple Animation and Threads file:///G|/ebooks/1575211831/ch10.htm (18 of 18) [11/06/2000 7:45:51 PM] Day 18 -- Multithreading Day 18 Multithreading by Charles L. Perkins and Michael Morrison CONTENTS q Thread Fundamentals q The Problem with Parallelism q Thinking Multithreaded r r q Points About Points Protecting a Class Variable Creating and Using Threads r The Runnable Interface r ThreadTester r NamedThreadTester q Knowing When a Thread Has Stopped q Thread Scheduling r Preemptive Versus Nonpreemptive r Testing Your Scheduler q Summary q Q&A One of the major features in the Java programming environment and runtime system is the multithreaded architecture shared by both. Multithreading, which is a fairly recent construct in the computer science world, is a very powerful means of enhancing and controlling program execution. Today's lesson takes a look at how the Java language supports multithreading through the use of threads. You'll learn all about the different classes that enable Java to be a threaded language, along with many of the issues surrounding the effective use of threads. To better understand the importance of threads, imagine that you're using your favorite text editor on a large file. When it starts up, does it need to examine the entire file before it lets you begin editing? Does it need to make a copy of the file? If the file is huge, this can be a nightmare. Wouldn't it be nicer for it to show you the first page, allowing you to begin editing, and somehow (in the background) complete the slower tasks necessary for initialization? Threads allow exactly this kind of within-the-program parallelism. Perhaps the best example of threading (or lack of it) is a Web browser. Can your browser download an indefinite number of files and Web pages at once while still enabling you to continue browsing? While these pages are downloading, can your browser download all the pictures, sounds, and so forth in parallel, interleaving the fast and slow download times of multiple Internet servers? Multithreaded browsers can do all these things by virtue of their internal usage of threads. Today you'll learn about the following primary issues surrounding threads: q Thread fundamentals q How to "think multithreaded" q How to protect your methods and variables from unintended thread conflicts file:///G|/ebooks/1575211831/ch18.htm (1 of 17) [11/06/2000 7:45:53 PM] Day 18 -- Multithreading q q How to create, start, and stop threads and threaded classes How the scheduler works in Java Let's begin today's lesson by defining what a thread is. Thread Fundamentals The multithreading support in Java revolves around the concept of a thread. So what exactly is a thread? Put simply, a thread is a single stream of execution within a process. Okay, maybe that wasn't so simple. It might be better to start off by explaining exactly what a process is. A process is a program executing within its own address space. Java is a multiprocessing system, meaning that it supports many processes running concurrently in their own address spaces. You may be more familiar with the term multitasking, which describes a scenario very similar to multiprocessing. As an example, consider the variety of applications typically running at once in a graphical environment. Most Windows 95 users typically run a variety of applications together at once, such as Microsoft Word, CD Player, Windows Messaging, Volume Control, and of course Solitaire. These applications are all processes executing within the Windows 95 environment. So you can think of processes as being analogous to applications, or standalone programs; each process in a system is given its own space in memory to execute. q A process is a program executing within its own address space. q A thread is a single stream of execution within a process. A thread is a sequence of code executing within the context of a process. As a matter of fact, threads cannot execute on their own; they require the overhead of a parent process to run. Within each of the processes typically running, there are no doubt a variety of threads executing. For example, Word may have a thread in the background automatically checking the spelling of what is being written, while another thread may be automatically saving changes to the document. Like Word, each application (process) can be running many threads that are performing any number of tasks. The significance here is that threads are always associated with a particular process. Judging by the fact that I've described threads and processes using Windows 95 as an example, you've probably guessed that Java isn't the first system to employ the use of threads. That's true, but Java is the first major programming language to incorporate threads at the heart of the language itself. Typically, threads are implemented at the system level, requiring a platform-specific programming interface separate from the core programming language. Since Java is presented as both a language and a runtime system, the Sun architects were able to integrate threads into both. The end result is that you are able to make use of Java threads in a standard, cross-platform fashion. The Problem with Parallelism If threading is so wonderful, why doesn't every system have it? Many modern operating systems have the basic primitives needed to create and run threads, but they are missing a key ingredient: The rest of their environment is not thread safe. A thread-safe environment is one that allows threads to safely coexist with each other peacefully. Imagine that you are in a thread, one of many, and each of you is sharing some important data managed by the system. If you were managing that data, you could take steps to protect it (as you'll see later today), but the system is managing it. Now visualize a piece of code in the system that reads some crucial value, thinks about it for a while, and then adds 1 to the value: if (crucialValue > 0) { ... crucialValue += 1; } // think about what to do Remember that any number of threads may be calling on this part of the system at once. The disaster occurs when two threads have both executed the if test before either has incremented crucialValue. In that case, the value is clobbered by them both with the same crucialValue += 1, and one of the increments has been lost. This may not seem so bad on the surface, but imagine if the crucial value affects the state of the screen as it is being displayed. Now, unfortunate file:///G|/ebooks/1575211831/ch18.htm (2 of 17) [11/06/2000 7:45:53 PM] Day 18 -- Multithreading ordering of the threads can cause the screen to be updated incorrectly. In the same way, mouse or keyboard events can be lost, databases can be inaccurately updated, and general havoc can ensue. This disaster is inescapable if any significant part of the system has not been written with threads in mind. Therein lies the reason why there are few mainstream threaded environments-the large effort required to rewrite existing libraries for thread safety. Luckily, Java was written from scratch with this is mind, and every Java class in its library is thread safe. Thus, you now have to worry only about your own synchronization and thread-ordering problems because you can assume that the Java system will do the right thing. Synchronized sections of code are called critical sections, implying that access to them is critical to the successful threaded execution of the program. Critical sections are also sometimes referred to as atomic operations, meaning that they appear to other threads as if they occur at once. In other words, just as an atom is a discrete unit of matter, atomic operations effectively act like a discrete operation to other threads, even though they may really contain many operations inside. Critical sections, or atomic operations, are synchronized sections of code that appear to happen "all at once"-exactly at the same time-to other threads. This results in only one thread being able to access code in a critical section at a time. Note Some readers may wonder what the fundamental problem really is. Can't you just make the ... area in the previous example smaller and smaller to reduce or eliminate the problem? Without atomic operations, the answer is no. Even if the ... took zero time, you must first look at the value of some variable to make any decision and then change something to reflect that decision. These two steps can never be made to happen at the same time without an atomic operation. Unless you're given one by the system, it's literally impossible to create your own. Even the one line crucialValue += 1 involves three steps: get the current value, add one to it, and store it back. (Using ++crucialValue doesn't help either.) All three steps need to happen "all at once" (atomically) to be safe. Special Java primitives, at the lowest levels of the language, provide you with the basic atomic operations you need to build safe, threaded programs. Thinking Multithreaded Getting used to threads takes a little while and a new way of thinking. Rather than imagining that you always know exactly what's happening when you look at a method you've written, you have to ask yourself some additional questions. What will happen if more than one thread calls into this method at the same time? Do you need to protect it in some way? What about your class as a whole? Are you assuming that only one of its methods is running at the same time? Often you make such assumptions, and a local instance variable will be messed up as a result. Since common wisdom dictates that we learn from our mistakes, let's make a few mistakes and then try to correct them. First, here's the simplest case: public class ThreadCounter { int crucialValue; public void countMe() { crucialValue += 1; } public int howMany() { return crucialValue; file:///G|/ebooks/1575211831/ch18.htm (3 of 17) [11/06/2000 7:45:53 PM] Day 18 -- Multithreading } } This code shows a class used to count threads that suffers from the most pure form of the "synchronization problem": The += takes more than one step, and you may miscount the number of threads as a result. (Don't worry about how threads are created yet; just imagine that a whole bunch of them are able to call countMe(), at once, but at slightly different times.) Java allows you to fix this situation: public class SafeThreadCounter { int crucialValue; public synchronized void countMe() { crucialValue += 1; } public int howMany() { return crucialValue; } } The synchronized keyword tells Java to make the block of code in the method thread safe. This means that only one thread will be allowed inside this method at once, and others will have to wait until the currently running thread is finished with it before they can begin running it. This implies that synchronizing a large, long-running method is almost always a bad idea. All your threads would end up stuck at this bottleneck, waiting single file to get their turn at this one slow method. It's even worse than you might think for unsynchronized variables. Because the compiler can keep them around in CPU registers during computations, and a thread's registers can't be seen by other threads, a variable can be updated in such a way that no possible order of thread updates could have produced the result. This is completely incomprehensible to the programmer, but it can happen. To avoid this bizarre case, you can label a variable volatile, meaning that you know it will be updated asynchronously by multiprocessor-like threads. Java then loads and stores it each time it's needed and does not use CPU registers. Note All variables are assumed to be thread safe unless you specifically mark them as volatile. Keep in mind that using volatile is an extremely rare event. In fact, in the 1.0.2 release, the Java API does not use volatile anywhere. Points About Points The method howMany() in the last example doesn't need to be synchronized because it simply returns the current value of an instance variable. A method higher in the call chain-one that uses the value returned from howMany()- may need to be synchronized, though. Listing 18.1 contains an example of a thread in need of this type of synchronization. Listing 18.1. The Point class. 1: public class Point { 2: private float x, y; 3: 4: public float x() { 5: return x; 6: } 7: //redefines class Point from package java.awt //OK since we're in a different package here // needs no synchronization file:///G|/ebooks/1575211831/ch18.htm (4 of 17) [11/06/2000 7:45:53 PM] Day 18 -- Multithreading 8: public float y() { // ditto 9: return y; 10: } 11: ... // methods to set and change x and y 12: } 13: 14: public class UnsafePointPrinter { 15: public void print(Point p) { 16: System.out.println("The point's x is " + p.x() 17: + " and y is " + p.y() + "."); 18: } 19: } The methods analogous to howMany() are x() and y(). They need no synchronization because they just return the values of member variables. It is the responsibility of the caller of x() and y() to decide whether it needs to synchronize itself-and in this case, it does. Although the method print() simply reads values and prints them out, it reads two values. This means that there is a chance that some other thread, running between the call to p.x() and the call to p.y(), could have changed the value of x and y stored inside the Point p. Remember, you don't know how many other threads have a way to reach and call methods in this Point object! "Thinking multithreaded" comes down to being careful any time you make an assumption that something has not happened between two parts of your program (even two parts of the same line, or the same expression, such as the string + expression in this example). TryAgainPointPrinter You could try to make a safe version of print() by simply adding the synchronized keyword modifier to it, but instead, let's try a slightly different approach: public class TryAgainPointPrinter { public void print(Point p) { float safeX, safeY; synchronized(this) { safeX = p.x(); // these two lines now safeY = p.y(); // happen atomically } System.out.print("The point's x is " + safeX + " y is " + safeY); } } The synchronized statement takes an argument that says what object you would like to lock to prevent more than one thread from executing the enclosed block of code at the same time. Here, you use this (the instance itself), which is exactly the object that would have been locked by the synchronized method as a whole if you had changed print() to be like your safe countMe() method. You have an added bonus with this new form of synchronization: You can specify exactly what part of a method needs to be safe, and the rest can be left unsafe. Notice how you took advantage of this freedom to make the protected part of the method as small as possible, while leaving the String creations, concatenations, and printing (which together take a small but finite amount of time) outside the "protected" area. This is both good style (as a guide to the reader of your code) and more efficient, because fewer threads get stuck waiting to get into protected areas. SafePointPrinter The astute reader, though, may still be worried by the last example. It seems as if you made sure that no one executes your file:///G|/ebooks/1575211831/ch18.htm (5 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading calls to x() and y() out of order, but have you prevented the Point p from changing out from under you? If the answer is no, you still have not completely solved the problem. It turns out that you really do need the full power of the synchronized statement: public class SafePointPrinter { public void print(Point p) { float safeX, safeY; synchronized(p) { safeX = p.x(); safeY = p.y(); } System.out.print("The // no one can change p // while these two lines // are happening atomically point's x is " + safeX + " y is " + safeY); } } Now you've got it! You actually needed to protect the Point p from changes, so you lock it by providing it as the argument to your synchronized statement. Now when x() and y() are called together, they can be sure to get the current x and y of the Point p, without any other thread being able to call a modifying method between. You're still assuming, however, that the Point p has properly protected itself. You can always assume this about system classes-but you wrote this Point class. You can make sure it's okay by writing the only method that can change x and y inside p yourself: public class Point { private float x, y; ... // the x() and y() methods public synchronized void setXAndY(float x = newX; y = newY; } newX, float newY) { } By making synchronized the only "set" method in Point, you guarantee that any other thread trying to grab the Point p and change it out from under you has to wait. You've locked the Point p with your synchronized(p) statement, and any other thread has to lock the same Point p via the implicit synchronized(this) statement that is executed when p enters setXAndY(). So at last you are thread safe. Note By the way, if Java had some way of returning more than one value at once, you could write a synchronized getXAndY() method for Point that returns both values safely. In the current Java language, such a method could return a new, unique Point to guarantee to its callers that no one else has a copy that might be changed. This sort of trick can be used to minimize the parts of the system that need to worry about synchronization. Protecting a Class Variable Suppose you want a class variable to collect some information across all a class's instances: file:///G|/ebooks/1575211831/ch18.htm (6 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading public class StaticCounter { private static int crucialValue; public synchronized void countMe() { crucialValue += 1; } } Is this safe? If crucialValue were an instance variable, it would be. Because it's a class variable, however, and there is only one copy of it for all instances; you can still have multiple threads modifying it by using different instances of the class. (Remember that the synchronized modifier locks the this object-an instance.) Luckily, you now know the technique required to solve this: public class StaticCounter { private static int crucialValue; public void countMe() { synchronized(getClass()) { crucialValue += 1; } } // can't directly name StaticCounter // the (shared) class is now locked } The trick is to "lock" on a different object-not on an instance of the class, but on the class itself. Because a class variable is "inside" a class, just as an instance variable is inside an instance, this shouldn't be all that unexpected. In a similar way, classes can provide global resources that any instance (or other class) can access directly by using the class name and lock by using that same class name. In the last example, crucialValue was used from within an instance of StaticCounter, but if crucialValue were declared public instead, from anywhere in the program, it would be safe to say the following: synchronized(Class.forName("StaticCounter")) { StaticCounter.crucialValue += 1; } Note The direct use of another class's (object's) member variable is really bad style-it's used here simply to demonstrate a point quickly. StaticCounter would normally provide a countMe()-like class method of its own to do this sort of dirty work. You can now begin to appreciate how much work the Java team has done for you by thinking all these hard thoughts for each and every class (and method!) in the Java class library. Creating and Using Threads Now that you understand the power (and the dangers) of having many threads running at once, how are those threads actually created? Warning file:///G|/ebooks/1575211831/ch18.htm (7 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading The system itself always has a few daemon threads running, one of which is constantly doing the tedious task of garbage collection for you in the background. There is also a main user thread that listens for events from your mouse and keyboard. If you're not careful, you can sometimes lock up this main thread. If you do, no events are sent to your program and it appears to be dead. A good rule of thumb is that whenever you're doing something that can be done in a separate thread, it probably should be. Threads in Java are relatively cheap to create, run, and destroy, so don't use them too sparingly. Because there is a class java.lang.Thread, you might guess that you could create a thread of your own by subclassing it-and you are right: public class MyFirstThread extends Thread { // a.k.a., java.lang.Thread public void run() { ... // do something useful } } You now have a new type of thread called MyFirstThread, which does something useful when its run() method is called. Of course, no one has created this thread or called its run() method, so at this point it is just a class eager to become a thread. To actually create and run an instance of your new thread class, you write the following: MyFirstThread aMFT = new MyFirstThread(); aMFT.start(); // calls our run() method What could be simpler? You create a new instance of your thread class and then ask it to start running. Whenever you want to stop the thread, you do this: aMFT.stop(); Besides responding to start() and stop(), a thread can also be temporarily suspended and later resumed: Thread t = new Thread(); t.suspend(); ... // do something special while t isn't running t.resume(); A thread will automatically suspend() and then resume() when it's first blocked at a synchronized point and then later unblocked (when it's that thread's "turn" to run). The Runnable Interface This is all well and good if every time you want to create a thread you have the luxury of being able to place it under the Thread class in the single-inheritance Java class tree. But what if it more naturally belongs under some other class, from which it needs to inherit most of its implementation? The interfaces you learned about on Day 16, "Packages and Interfaces," come to the rescue: public class MySecondThread extends ImportantClass implements Runnable { public void run() { ... // do something useful } } file:///G|/ebooks/1575211831/ch18.htm (8 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading By implementing the interface Runnable, you declare your intention to run in a separate thread. In fact, the Thread class is itself an implementation of this interface, as you might expect from the design discussions on Day 16. As you also might guess from the example, the Runnable interface defines only one method: run(). As in MyFirstThread, you expect someone to create an instance of a thread and somehow call your run() method. Here's how this is accomplished using the interface approach to thread creation: MySecondThread aMST = new MySecondThread(); Thread aThread = new Thread(aMST); aThread.start(); // calls our run() method, indirectly First, you create an instance of MySecondThread. Then, by passing this instance to the constructor creating the new thread, you make it the target of that thread. Whenever that new thread starts up, its run() method calls the run() method of the target it was given (assumed by the thread to be an object that implements the Runnable interface). When start() is called on aThread, your run() method is indirectly called. You can stop aThread with stop(). If you don't need to use the Thread object or instance of MySecondThread explicitly, here's a one-line shortcut: new Thread(new MySecondThread()).start(); Note As you can see, the class name MySecondThread is a bit of a misnomer-it does not descend from Thread, nor is it actually the thread that you start() and stop(). It could have been called MySecondThreadedClass or ImportantRunnableClass to be more clear on this point. ThreadTester Listing 18.2 contains a longer example of creating and using threads. Listing 18.2. The SimpleRunnable class. 1: public class SimpleRunnable implements Runnable { 2: public void run() { 3: System.out.println("in thread named '" 4: + Thread.currentThread().getName() + "'"); 5: } // any other methods run() calls are in current thread as well 6: } 7: 8: public class ThreadTester { 9: public static void main(String argv) { 10: SimpleRunnable aSR = new SimpleRunnable(); 11: 12: while (true) { 13: Thread t = new Thread(aSR); 14: 15: System.out.println("new Thread() " + (t == null ? 16: "fail" : "succeed") + "ed."); 17: t.start(); 18: try { t.join(); } catch (InterruptedException ignored) { } 19: // waits for thread to finish its run() method 20: } 21: } file:///G|/ebooks/1575211831/ch18.htm (9 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading 22: } Note You may be worried that only one instance of the class SimpleRunnable is created, but many new threads are using it. Don't they get confused? Remember to separate in your mind the aSR instance (and the methods it understands) from the various threads of execution that can pass through it. aSR's methods provide a template for execution, and the multiple threads created are sharing that template. Each remembers where it is executing and whatever else it needs to make it distinct from the other running threads. They all share the same instance and the same methods. That's why you need to be so careful, when adding synchronization, to imagine numerous threads running rampant over each of your methods. The class method currentThread() can be called to get the thread in which a method is currently executing. If the SimpleRunnable class were a subclass of Thread, its methods would know the answer already (it is the thread running). Because SimpleRunnable simply implements the interface Runnable, however, and counts on someone else (ThreadTester's main()) to create the thread, its run() method needs another way to get its hands on that thread. Often, you'll be deep inside methods called by your run() method when suddenly you need to get the current thread. The class method shown in the example works, no matter where you are. The example then calls getName() on the current thread to get the thread's name (usually something helpful, such as Thread-23) so it can tell the world in which thread run() is running. The final thing to note is the use of the method join(), which, when sent to a thread, means "I'm planning to wait forever for you to finish your run() method." You don't want to use this approach without good reason: If you have anything else important you need to get done in your thread any time soon, you can't count on how long the joined thread might take to finish. In the example, the run() method is short and finishes quickly, so each loop can safely wait for the previous thread to die before creating the next one. Here's the output produced: new Thread() succeeded. in thread named 'Thread-1' new Thread() succeeded. in thread named 'Thread-2' new Thread() succeeded. in thread named 'Thread-3' ^C Incidentally, Ctrl+C was pressed to interrupt the program, because it otherwise would continue on forever. Warning You can do some reasonably disastrous things with your knowledge of threads. For example, if you're running in the main thread of the system and, because you think you are in a different thread, you accidentally say the following: Thread.currentThread().stop(); it has unfortunate consequences for your (soon-to-be-dead) program! NamedThreadTester If you want your threads to have particular names, you can assign them yourself by using another form of Thread's constructor: file:///G|/ebooks/1575211831/ch18.htm (10 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading public class NamedThreadTester { public static void main(String argv) { SimpleRunnable aSR = new SimpleRunnable(); for (int i = 1; true; ++i) { Thread t = new Thread(aSR, "" + (100 - i) + " threads on the wall..."); System.out.println("new Thread() " + (t == null ? "fail" : "succeed") + "ed."); t.start(); try { t.join(); } catch (InterruptedException ignored) { } } } } This version of Thread's constructor takes a target object, as before, and a string, which names the new thread. Here's the output: new Thread() succeeded. in thread named '99 threads on the wall...' new Thread() succeeded. in thread named '98 threads on the wall...' new Thread() succeeded. in thread named '97 threads on the wall...' ^C Naming a thread is one easy way to pass it some information. This information flows from the parent thread to its new child. It's also useful, for debugging purposes, to give threads meaningful names (such as network input) so that when they appear during an error-in a stack trace, for example-you can easily identify which thread caused the problem. You might also think of using names to help group or organize your threads, but Java actually provides you with a ThreadGroup class to perform this function. The ThreadGroup class is used to manage a group of threads as a single unit. This provides you with a means to finely control thread execution for a series of threads. For example, the ThreadGroup class provides stop, suspend, and resume methods for controlling the execution of all the threads in the group. Thread groups can also contain other thread groups, allowing for a nested hierarchy of threads. Another benefit to using thread groups is that they can keep threads from being able to affect other threads, which is useful for security. Knowing When a Thread Has Stopped Let's imagine a different version of the last example, one that creates a thread and then hands the thread off to other parts of the program. Suppose the program would then like to know when that thread dies so that it can perform some cleanup operation. If SimpleRunnable were a subclass of Thread, you might try to catch stop() whenever it's sent-but look at Thread's declaration of the stop() method: public final void stop() { . . . } The final here means that you can't override this method in a subclass. In any case, SimpleRunnable is not a subclass of Thread, so how can this imagined example possibly catch the death of its thread? The answer is to use the following magic: file:///G|/ebooks/1575211831/ch18.htm (11 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading public class SingleThreadTester { public static void main(String argv) { Thread t = new Thread(new SimpleRunnable()); try { t.start(); someMethodThatMightStopTheThread(t); } catch (ThreadDeath aTD) { ... // do some required cleanup throw aTD; // re-throw the error } } } You understand most of this magic from yesterday's lesson. All you need to know is that if the thread created in the example dies, it throws an error of class ThreadDeath. The code catches that error and performs the required cleanup. It then rethrows the error, allowing the thread to die. The cleanup code is not called if the thread exits normally (its run() method completes), but that's fine; you posited that the cleanup was needed only when stop() was used on the thread. Note Threads can die in other ways-for example, by throwing exceptions that no one catches. In these cases, stop() is never called and the previous code is not sufficient. Because unexpected exceptions can come out of nowhere to kill a thread, multithreaded programs that carefully catch and handle all their exceptions are more predictable and robust, and they're easier to debug. Thread Scheduling You might be wondering how any software system can be truly threaded when running on a machine with a single CPU. If there is only one physical CPU in a computer system, it's impossible for more than one machine code instruction to be executed at a time. This means that no matter how hard you try to rationalize the behavior of a multithreaded system, only one thread is really being executed at a particular time. The reality is that multithreading on a single CPU system, like the systems most of us use, is at best a good illusion. The good news is that the illusion works so well most of the time that we feel pretty comfortable in the fact that multiple threads are really running in parallel. The illusion of parallel thread execution on a system with a single CPU is often managed by giving each thread an opportunity to execute a little bit of code at regular intervals. This approach is known as timeslicing, which refers to the way each thread gets a little of the CPU's time to execute code. When you speed up this whole scenario to millions of instructions per second, the whole effect of parallel execution comes across pretty well. The general task of managing and executing multiple threads in an environment such as this is known as scheduling. Likewise, the part of the system that decides the real-time ordering of threads is called the scheduler. Preemptive Versus Nonpreemptive Normally, any scheduler has two fundamentally different ways of looking at its job: nonpreemptive scheduling and preemptive time slicing. With nonpreemptive scheduling, the scheduler runs the current thread forever, requiring that thread to explicitly tell it when it is safe to start a different thread. With preemptive time slicing, the scheduler runs the current thread until it has used up a certain tiny fraction of a second, and then "preempts" it, suspends it, and resumes another thread for the next tiny fraction of a second. file:///G|/ebooks/1575211831/ch18.htm (12 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading Nonpreemptive scheduling is very courtly, always asking for permission to schedule, and is quite valuable in extremely time-critical real-time applications where being interrupted at the wrong moment, or for too long, could mean crashing an airplane. However, most modern schedulers use preemptive time slicing because it generally has made writing multithreaded programs much easier. For one thing, it does not force each thread to decide exactly when it should "yield" control to another thread. Instead, every thread can just run blindly on, knowing that the scheduler will be fair about giving all the other threads their chance to run. However, it turns out that this approach is still not the ideal way to schedule threads; you've given up a little too much control to the scheduler. The final touch many modern schedulers add is to allow you to assign each thread a priority. This creates a total ordering of all threads, making some threads more "important" than others. Being higher priority often means that a thread gets run more often or for a longer period of time, but it always means that it can interrupt other, lower-priority threads, even before their "time slice" has expired. A good example of a low-priority thread is the garbage collection thread in the Java runtime system. Even though garbage collection is a very important function, it is not something you want hogging the CPU. Since the garbage collection thread is a low-priority thread, it chugs along in the background, freeing up memory as the processor allows it. This may result in memory being freed a little slower, but it allows more time-critical threads, such as the user input handling thread, full access to the CPU. You may be wondering what happens if the CPU stays busy and the garbage collector never gets to clean up memory. Does the runtime system run out of memory and crash? No. This brings up one of the neat aspects of threads and how they work. If a high-priority thread can't access a resource it needs, such as memory, it enters a wait state until memory becomes available. When all memory is gone, all the threads running will eventually go into a wait state, thereby freeing up the CPU to execute the garbage collection thread, which in turn frees up memory. And the circle of threaded life continues! The current Java release (1.0.2) does not precisely specify the behavior of its scheduler. Threads can be assigned priorities, and when a choice is made between several threads that all want to run, the highest-priority thread wins. However, among threads that are all the same priority, the behavior is not well defined. In fact, the different platforms on which Java currently runs have different behaviors-some behaving more like a preemptive scheduler, and some more like a nonpreemptive scheduler. Note This incomplete specification of the scheduler is terribly annoying and, presumably, will be corrected in a later release. Not knowing the fine details of how scheduling occurs is perfectly all right, but not knowing whether equal-priority threads must explicitly yield or face running forever is not all right. For example, all the threads you have created so far are equal-priority threads so you don't know their basic scheduling behavior! Testing Your Scheduler To find out what kind of scheduler you have on your system, try out the following code: public class RunnablePotato implements Runnable { public void run() { while (true) System.out.println(Thread.currentThread().getName()); } } public class PotatoThreadTester { public static void main(String argv) { file:///G|/ebooks/1575211831/ch18.htm (13 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading RunnablePotato aRP = new RunnablePotato(); new Thread(aRP, "one potato").start(); new Thread(aRP, "two potato").start(); } } If your system employs a nonpreemptive scheduler, this code results in the following output: one one one .. potato potato potato . This output will go on forever or until you interrupt the program. For a preemptive scheduler that uses time slicing, this code will repeat the line one potato a few times, followed by the same number of two potato lines, over and over: one one ... one two two ... two .. potato potato potato potato potato potato . This output will also go on forever or until you interrupt the program. What if you want to be sure the two threads will take turns, regardless of the type of system scheduler? You rewrite RunnablePotato as follows: public class RunnablePotato implements Runnable { public void run() { while (true) { System.out.println(Thread.currentThread().getName()); Thread.yield(); // let another thread run for a while } } } Tip Normally you would have to use Thread.currentThread().yield() to get your hands on the current thread, and then call yield(). Because this pattern is so common, however, the Thread class can be used as a shortcut. The yield() method explicitly gives any other threads that want to run a chance to begin running. (If there are no threads waiting to run, the thread that made the yield() simply continues.) In our example, there's another thread that's just dying to run, so when you now execute the class ThreadTester, it should output the following: one two one two potato potato potato potato file:///G|/ebooks/1575211831/ch18.htm (14 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading one potato two potato ... This output will be the same regardless of the type of scheduler you have. To see whether thread priorities are working on your system, try this code: public class PriorityThreadTester { public static void main(String argv) { RunnablePotato aRP = new RunnablePotato(); Thread t1 = new Thread(aRP, "one potato"); Thread t2 = new Thread(aRP, "two potato"); t2.setPriority(t1.getPriority() + 1); t1.start(); t2.start(); // at priority Thread.NORM_PRIORITY + 1 } } Tip The values representing the lowest, normal, and highest priorities that threads can be assigned are stored in constant class members of the Thread class: Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, and Thread.MAX_PRIORITY. The system assigns new threads, by default, the priority Thread.NORM_PRIORITY. Priorities in Java are currently defined in a range from 1 to 10, with 5 being normal, but you shouldn't depend on these values; use the class variables or tricks like the one shown in this example. If one potato is the first line of output, your system does not preempt using thread priorities. Why? Imagine that the first thread (t1) has just begun to run. Even before it has a chance to print anything, along comes a higher-priority thread (t2) that wants to run as well. That higher-priority thread should preempt (interrupt) the first and get a chance to print two potato before t1 finishes printing anything. In fact, if you use the RunnablePotato class that never yield()s, t2 stays in control forever, printing two potato lines, because it's a higher priority than t1 and it never yields control. If you use the latest RunnablePotato class (with yield()), the output is alternating lines of one potato and two potato as before, but starting with two potato. Listing 18.3 contains a good, illustrative example of how complex threads behave. Listing 18.3. The ComplexThread class. 1: public class ComplexThread extends Thread { 2: private int delay; 3: 4: ComplexThread(String name, float seconds) { 5: super(name); 6: delay = (int) seconds * 1000; // delays are in milliseconds 7: start(); // start up ourself! 8: } 9: 10: public void run() { file:///G|/ebooks/1575211831/ch18.htm (15 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: } while (true) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(delay); } catch (InterruptedException e) { return; } } } public static void main(String argv) { new ComplexThread("one potato", 1.1F); new ComplexThread("two potato", 1.3F); new ComplexThread("three potato", 0.5F); new ComplexThread("four", 0.7F); } This example combines the thread and its tester into a single class. Its constructor takes care of naming and starting itself because it is now a thread. The main() method creates new instances of its own class because the class is a subclass of Thread. The run() method is also more complicated because it now uses, for the first time, a method that can throw an unexpected exception. The Thread.sleep() method forces the current thread to yield() and then waits for at least the specified amount of time to elapse before allowing the thread to run again. It might be interrupted by another thread, however, while it's sleeping. In such a case, it throws an InterruptedException. Now, because run() is not defined as throwing this exception, you must "hide" the fact by catching and handling it yourself. Because interruptions are usually requests to stop, you should exit the thread, which you can do by simply returning from the run() method. This program should output a repeating but complex pattern of four different lines, where every once in a great while you see the following: ... one potato two potato three potato four ... You should study the pattern output to prove to yourself that true parallelism is going on inside Java programs. You may also begin to appreciate that, if even this simple set of four threads can produce such complex behavior, many more threads must be capable of producing near chaos if not carefully controlled. Luckily, Java provides the synchronization and thread-safe libraries you need to control that chaos. Summary Today you have learned that multithreading is desirable and powerful, but introduces many new problems-methods and variables now need to be protected from thread conflicts-that can lead to chaos if not carefully controlled. By "thinking multithreaded," you can detect the places in your programs that require synchronized statements (or modifiers) to make them thread safe. A series of Point examples demonstrates the various levels of safety you can achieve, and ThreadTesters shows how subclasses of Thread, or classes that implement the Runnable interface, are created and run to generate multithreaded programs. file:///G|/ebooks/1575211831/ch18.htm (16 of 17) [11/06/2000 7:45:54 PM] Day 18 -- Multithreading You have also learned today how to use yield(), start(), stop(), suspend(), and resume() on your threads, and how to catch ThreadDeath whenever it happens. You have learned about preemptive and nonpreemptive scheduling, both with and without priorities, and how to test your Java system to see which of them your scheduler is using. You are now armed with enough information to write the most complex of programs: multithreaded ones. As you get more comfortable with threads, you may begin to use the ThreadGroup class or the enumeration methods of Thread to get your hands on all the threads in the system and manipulate them. Don't be afraid to experiment; you can't permanently break anything, and you only learn by trying. Q&A Q: A: Q: A: Q: A: Q: A: If they're so important to Java, why haven't threads appeared throughout the entire book? Actually, they have. Every standalone program written so far has "created" at least one thread, the one in which it is running. (Of course the system created that thread for it automatically.) How exactly do these threads get created and run? What about applets? When a simple standalone Java program starts up, the system creates a main thread, and its run() method calls your main() method to start your program-you do nothing to get that thread. Likewise, when a simple applet loads into a Java-enabled browser, a thread has already been created by the browser, and its run() method calls your init() and start() methods to start your program. In either case, a new thread of some kind was created somewhere by the Java environment itself. I know the current Java release is still a little fuzzy about the scheduler's behavior, but what's the word from Sun? Here's the scoop, as relayed by Arthur van Hoff at Sun: The way Java schedules threads "…depends on the platform. It is usually preemptive, but not always time sliced. Priorities are not always observed, depending on the underlying implementation." This final clause gives you a hint that all this confusion is an implementation problem, and that in some future release, the design and implementation will both be clear about scheduling behavior. My parallel friends tell me I should worry about something called "deadlock." Should I? Not for simple multithreaded programs. However, in more complicated programs, one of the biggest worries does become one of avoiding a situation in which one thread has locked an object and is waiting for another thread to finish, while that other thread is waiting for the first thread to release that same object before it can finish. That's a deadlock-both threads will be stuck forever. Mutual dependencies like this involving more than two threads can be quite intricate, convoluted, and difficult to locate, much less rectify. They are one of the main challenges in writing complex multithreaded programs. file:///G|/ebooks/1575211831/ch18.htm (17 of 17) [11/06/2000 7:45:54 PM] Day 16 -- Packages and Interfaces Day 16 Packages and Interfaces by Laura Lemay and Charles L. Perkins CONTENTS q Programming in the Large and Programming in the Small q What Are Packages? q Using Packages r r The import Command r Name Conflicts r q Full Package and Class Names A Note About CLASSPATH and Where Classes Are Located Creating Your Own Packages r r Create the Directory Structure r Use package to Add Your Class to a Package r q Pick a Package Name Packages and Class Protection What Are Interfaces? r r Abstract Design and Concrete Implementation r q The Problem of Single Inheritance Interfaces and Classes Implementing and Using Interfaces r r Implementing Multiple Interfaces r q The implements Keyword Other Uses of Interfaces Creating and Extending Interfaces r New Interfaces r Methods Inside Interfaces r Extending Interfaces r An Example: Enumerating Linked Lists q Summary q Q&A Packages and interfaces are two capabilities that allow you greater control and flexibility in designing sets of interrelated classes. Packages allow you to combine groups of classes and control which of those classes are available to the outside world; interfaces provide a way of grouping abstract method definitions and sharing them among classes that may not necessarily acquire those methods through inheritance. Today you'll learn how to design with, use, and create your own packages and interfaces. Specific topics you'll learn about today include file:///G|/ebooks/1575211831/ch16.htm (1 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces q q q q q q A discussion of designing classes versus coding classes and how to approach each What packages are and why they are useful for class design Using other people's packages in your own classes Creating your own packages What interfaces buy you in terms of code reuse and design Designing and working with interfaces Programming in the Large and Programming in the Small When you examine a new language feature, you should ask yourself two questions: q How can I use it to better organize the methods and classes of my Java program? q How can I use it while writing the actual Java code? The first is often called programming in the large, and the second, programming in the small. Bill Joy, a founder of Sun Microsystems, likes to say that Java feels like C when programming in the small and like Smalltalk when programming in the large. What he means by that is that Java is familiar and powerful like any C-like language while you're coding individual lines, but has the extensibility and expressive power of a pure object-oriented language like Smalltalk while you're designing. The separation of "designing" from "coding" was one of the most fundamental advances in programming in the past few decades, and object-oriented languages such as Java implement a strong form of this separation. The first part of this separation has already been described on previous days: When you develop a Java program, first you design the classes and decide on the relationships between these classes, and then you implement the Java code needed for each of the methods in your design. If you are careful enough with both these processes, you can change your mind about aspects of the design without affecting anything but small, local pieces of your Java code, and you can change the implementation of any method without affecting the rest of the design. As you begin to explore more advanced Java programming, however, you'll find that this simple model becomes too limiting. Today you'll explore these limitations, for programming in the large and in the small, to motivate the need for packages and interfaces. Let's start with packages. What Are Packages? Packages, as mentioned a number of times in this book so far, are a way of organizing groups of classes. A package contains any number of classes that are related in purpose, in scope, or by inheritance. Why bother with packages? If your programs are small and use a limited number of classes, you may find that you don't need to explore packages at all. But the more Java programming you do, the more classes you'll find you have. And although those classes may be individually well designed, reusable, encapsulated, and with specific interfaces to other classes, you may find the need for a bigger organizational entity that allows you to group your packages. Packages are useful for several broad reasons: q They allow you to organize your classes into units. Just as you have folders or directories on your hard disk to organize your files and applications, packages allow you to organize your classes into groups so that you only use what you need for each program. q They reduce problems with conflicts in names. As the number of Java classes grows, so does the likelihood that you'll use the same class name as someone else, opening up the possibility of naming clashes and errors if you try to integrate groups of classes into a single program. Packages allow you to "hide" classes so that conflicts can be avoided. q They allow you to protect classes, variables, and methods in larger ways than on a class-by-class basis, as you learned yesterday. You'll learn more about protections with packages later today. q They can be used to identify your classes. For example, if you implemented a set of classes to perform some purpose, you could name a package of those classes with a unique identifier that identifies you or your organization. Although a package is most typically a collection of classes, packages can also contain other packages, forming yet another level of organization somewhat analogous to the inheritance hierarchy. Each "level" usually represents a smaller, more specific file:///G|/ebooks/1575211831/ch16.htm (2 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces grouping of classes. The Java class library itself is organized along these lines. The top level is called java; the next level includes names such as io, net, util, and awt. The last of these has an even lower level, which includes the package image. Note By convention, the first level of the hierarchy specifies the (globally unique) name to identify the author or owner of those packages. For example, Sun Microsystems's classes, which are not part of the standard Java environment, all begin with the prefix sun. Classes that Netscape includes with its implementation are contained in the netscape package. The standard package, java, is an exception to this rule because it is so fundamental and because it might someday be implemented by multiple companies. I'll tell you more about package-naming conventions later when you create your own packages. Using Packages You've been using packages all along in this book. Every time you use the import command, and every time you refer to a class by its full package name (java.awt.Color, for example), you've used packages. Let's go over the specifics of how to use classes from other packages in your own programs to make sure you've got it and to go into greater depth than we have in previous lessons. To use a class contained in a package, you can use one of three mechanisms: q If the class you want to use is in the package java.lang (for example, System or Date), you can simply use the class name to refer to that class. The java.lang classes are automatically available to you in all your programs. q If the class you want to use is in some other package, you can refer to that class by its full name, including any package names (for example, java.awt.Font). q For classes that you use frequently from other packages, you can import individual classes or a whole package of classes. After a class or a package has been imported, you can refer to that class by its class name. What about your own classes in your own programs that don't belong to any package? The rule is that if you don't specifically define your classes to belong to a package, they're put into an unnamed default package. You can refer to those classes simply by class name from anywhere in your code. Full Package and Class Names To refer to a class in some other package, you can use its full name: the class name preceded by any package names. You do not have to import the class or the package to use it this way: java.awt.Font f = new java.awt.Font() For classes that you use only once or twice in your program, using the full name makes the most sense. If, however, you use that class multiple times, or if the package name is really long with lots of subpackages, you'll want to import that class instead to save yourself some typing. The import Command To import classes from a package, use the import command, as you've used throughout the examples in this book. You can either import an individual class, like this: import java.util.Vector; or you can import an entire package of classes, using an asterisk (*) to replace the individual class names: import java.awt.* file:///G|/ebooks/1575211831/ch16.htm (3 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces Note Actually, to be technically correct, this command doesn't import all the classes in a package-it only imports the classes that have been declared public, and even then only imports those classes that the code itself refers to. You'll learn more on this in the section titled "Packages and Class Protection." Note that the asterisk (*) in this example is not like the one you might use at a command prompt to specify the contents of a directory or to indicate multiple files. For example, if you ask to list the contents of the directory classes/java/awt/*, that list includes all the .class files and subdirectories, such as image and peer. Writing import java.awt.* imports all the public classes in that package, but does not import subpackages such as image and peer. To import all the classes in a complex package hierarchy, you must explicitly import each level of the hierarchy by hand. Also, you cannot indicate partial class names (for example, L* to import all the classes that begin with L). It's all the classes in a package or a single class. The import statements in your class definition go at the top of the file, before any class definitions (but after the package definition, as you'll see in the next section). So should you take the time to import classes individually or just import them as a group? It depends on how specific you want to be. Importing a group of classes does not slow down your program or make it any larger; only the classes you actually use in your code are loaded as they are needed. But importing a package does make it a little more confusing for readers of your code to figure out where your classes are coming from. Using individual imports or importing packages is mostly a question of your own coding style. Technical Note Java's import command is not at all similar to the #include command in C-like languages, although they accomplish similar functions. The C preprocessor takes the contents of all the included files (and, in turn, the files they include, and so on) and stuffs them in at the spot where the #include was. The result is an enormous hunk of code that has far more lines than the original program did. Java's import behaves more like a linker; it tells the Java compiler and interpreter where (in which files) to find classes, variables, method names, and method definitions. It doesn't bring anything into the current Java program. Name Conflicts After you have imported a class or a package of classes, you can usually refer to a class name simply by its name, without the package identifier. I say "usually" because there's one case where you may have to be more explicit: when there are multiple classes with the same name from different packages. Here's an example. Let's say you import the classes from two packages from two different programmers (Joe and Eleanor): import joesclasses.*; import eleanorsclasses.*; Inside Joe's package is a class called Name. Unfortunately, inside Eleanor's package there is also a class called Name that has an entirely different meaning and implementation. You would wonder whose version of Name would end up getting used if you referred to the Name class in your own program like this: Name myName = new Name("Susan"); The answer is neither; the Java compiler will complain about a naming conflict and refuse to compile your program. In this case, despite the fact that you imported both classes, you still have to refer to the appropriate Name class by full package name: file:///G|/ebooks/1575211831/ch16.htm (4 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces joesclasses.Name myName = new joesclasses.Name("Susan"); A Note About CLASSPATH and Where Classes Are Located Before I go on to explain how to create your own packages of classes, I'd like to make a note about how Java finds packages and classes when it's compiling and running your classes. For Java to be able to use a class, it has to be able to find it on the file system. Otherwise, you'll get an error that the class does not exist. Java uses two things to find classes: the package name itself and the directories listed in your CLASSPATH variable. First, the package names. Package names map to directory names on the file system, so the class java.applet.Applet will actually be found in the applet directory, which in turn will be inside the java directory (java/applet/Applet.class, in other words). Java looks for those directories, in turn, inside the directories listed in your CLASSPATH variable. If you remember back to Day 1, "An Introduction to Java Programming," when you installed the JDK, you had to set up a CLASSPATH variable to point to the various places where your Java classes live. CLASSPATH usually points to the java/lib directory in your JDK release, a class directory in your development environment if you have one, perhaps some browser-specific classes, and to the current directory. When Java looks for a class you've referenced in your source, it looks for the package and class name in each of those directories and returns an error if it can't find the class file. Most "cannot load class" errors result because of missed CLASSPATH variables. Note If you're using the Macintosh version of the JDK, you're probably wondering what I'm talking about. The Mac JDK doesn't use a CLASSPATH variable; it knows enough to be able to find the default classes and those contained in the current directory. However, if you do a lot of Java development, you may end up with classes and packages in other directories. The Java compiler contains a Preferences dialog box that lets you add directories to Java's search path. Creating Your Own Packages Creating your own packages is a difficult, complex process, involving many lines of code, long hours late at night with lots of coffee, and the ritual sacrifice of many goats. Just kidding. To create a package of classes, you have three basic steps to follow, which I'll explain in the following sections. Pick a Package Name The first step is to decide what the name of your package is going to be. The name you choose for your package depends on how you are going to be using those classes. Perhaps your package will be named after you, or perhaps after the part of the Java system you're working on (like graphics or hardware_interfaces). If you're intending your package to be distributed to the Net at large, or as part of a commercial product, you'll want to use a package name (or set of package names) that uniquely identifies you or your organization or both. One convention for naming packages that has been recommended by Sun is to use your Internet domain name with the elements reversed. So, for example, if Sun were following its own recommendation, its packages would be referred to using the name com.sun.java rather than just java. If your Internet domain name is fooblitzky.eng.nonsense.edu, your package name might be edu.nonsense.eng.fooblitzky (and you might add another package name onto the end of that to refer to the product or to you, specifically). The idea is to make sure your package name is unique. Although packages can hide conflicting class names, the protection stops there. There's no way to make sure your package won't conflict with someone else's package if you both use the same package name. By convention, package names tend to begin with a lowercase letter to distinguish them from class names. Thus, for example, in file:///G|/ebooks/1575211831/ch16.htm (5 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces the full name of the built-in String class, java.lang.String, it's easier to separate the package name from the class name visually. This convention helps reduce name conflicts. Create the Directory Structure Step two in creating packages is to create a directory structure on your disk that matches the package name. If your package has just one name (mypackage), you'll only have to create a directory for that one name. If the package name has several parts, however, you'll have to create directories within directories. For the package name edu.nonsense.eng.fooblitzky, you'll need to create an edu directory and then create a nonsense directory inside edu, an eng directory inside nonsense, and a fooblitzky directory inside eng. Your classes and source files can then go inside the fooblitzky directory. Use package to Add Your Class to a Package The final step to putting your class inside packages is to add the package command to your source files. The package command says "this class goes inside this package," and is used like this: package myclasses; package edu.nonsense.eng.fooblitzky; package java.awt; The single package command, if any, must be the first line of code in your source file, after any comments or blank lines and before any import commands. As mentioned before, if your class doesn't have a package command in it, that class is contained in the default package and can be used by any other class. But once you start using packages, you should make sure all your classes belong to some package to reduce the chance of confusion about where your classes belong. Packages and Class Protection Yesterday you learned all about the four Ps of protection and how they apply (primarily) to methods and variables and their relationship to other classes. When referring to classes and their relationship to other classes in other packages, you only have two Ps to worry about: package and public. By default, classes have package protection, which means that the class is available to all the other classes in the same package but is not visible or available outside that package-not even to subpackages. It cannot be imported or referred to by name; classes with package protection are hidden inside the package in which they are contained. Package protection comes about when you define a class as you have throughout this book, like this: class TheHiddenClass extends AnotherHiddenClass { ... } To allow a class to be visible and importable outside your package, you'll want to give it public protection by adding the public modifier to its definition: public class TheVisibleClass { ... } Classes declared as public can be imported by other classes outside the package. Note that when you use an import statement with an asterisk, you import only the public classes inside that package. Hidden classes remain hidden and can be used only by the other classes in that package. Why would you want to hide a class inside a package? For the same reason you want to hide variables and methods inside a class: so you can have utility classes and behavior that are useful only to your implementation, or so you can limit the interface of your program to minimize the effect of larger changes. As you design your classes, you'll want to take the whole package into file:///G|/ebooks/1575211831/ch16.htm (6 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces consideration and decide which classes will be declared public and which will be hidden. Listing 16.1 shows two classes that illustrate this point. The first is a public class that implements a linked list; the second is a private node of that list. Listing 16.1. The public class LinkedList. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: package collections; public class LinkedList { private Node root; public void add(Object o) { root = new Node(o, root); } ... } class Node { // not public private Object contents; private Node next; Node(Object o, Node n) { contents = o; next = n; } ... } Note Notice here that I'm including two class definitions in one file. I mentioned this briefly on Day 13, "Creating User Interfaces with the awt," and it bears mentioning here as well: You can include as many class definitions per file as you want, but only one of them can be declared public, and that filename must have the same name as the one public class. When Java compiles the file, it'll create separate .class files for each class definition inside the file. In reality, I find the one-to-one correspondence of class definition to file much more easily maintained because I don't have to go searching around for the definition of a class. The public LinkedList class provides a set of useful public methods (such as add()) to any other classes that might want to use them. These other classes don't need to know about any support classes LinkedList needs to get its job done. Node, which is one of those support classes, is therefore declared without a public modifier and will not appear as part of the public interface to the collections package. Note Just because Node isn't public doesn't mean LinkedList won't have access to it once it's been imported into some other class. Think of protections not as hiding classes entirely, but more as checking the permissions of a given class to use other classes, variables, and methods. When you import and use LinkedList, the Node class will also be loaded into the system, but only instances of LinkedList will have permission to use it. One of the great powers of hidden classes is that even if you use them to introduce a great deal of complexity into the file:///G|/ebooks/1575211831/ch16.htm (7 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces implementation of some public class, all the complexity is hidden when that class is imported or used. Thus, creating a good package consists of defining a small, clean set of public classes and methods for other classes to use, and then implementing them by using any number of hidden (package) support classes. You'll see another use for hidden classes later today. What Are Interfaces? Interfaces, like the abstract classes and methods you saw yesterday, provide templates of behavior that other classes are expected to implement. Interfaces, however, provide far more functionality to Java and to class and object design than do simple abstract classes and methods. The rest of this lesson explores interfaces: what they are, why they're crucial to getting the most out of the Java language for your own classes, and how to use and implement them. The Problem of Single Inheritance When you first begin to design object-oriented programs, the concept of the class hierarchy can seem almost miraculous. Within that single tree you can express a hierarchy of different types of objects, many simple to moderately complex relationships between objects and processes in the world, and any number of points along the axis from abstract/general to concrete/specific. The strict hierarchy of classes appears, at first glance, to be simple, elegant, and easy to use. After some deeper thought or more complex design experience, however, you may discover that the pure simplicity of the class hierarchy is restrictive, particularly when you have some behavior that needs to be used by classes in different branches of the same tree. Let's look at a few examples that will make the problems clearer. Way back on Day 2, "Object-Oriented Programming and Java," when you first learned about class hierarchies, we discussed the Vehicle hierarchy, as shown in Figure 16.1. Figure 16.1 : The Vechicle hierarchy. Now let's add to that hierarchy and create the classes BritishCar and BritishMotorcycle underneath Car and Motorcycle, respectively. The behavior that makes a car or motorcycle British (which might include methods for leakOil() or electricalSystemFailure()) is common to both these classes, but because they are in very different parts of the class hierarchy, you can't create a common superclass for both of them. And you can't put the British behavior further up in the hierarchy because that behavior isn't common to all motorcycles and cars. Other than physically copying the behavior between the two classes (which breaks the object-oriented programming [OOP] rules of code reuse and shared behavior), how can you create a hierarchy like this? Let's look at an even thornier example. Say you have a biological hierarchy with Animal at the top, and the classes Mammal and Bird underneath. Things that define a mammal include bearing live young and having fur. Behavior or features of birds include having a beak and laying eggs. So far, so good, right? So how do you go about creating a class for the platypus, which has fur, has a beak, and lays eggs? You'd need to combine behavior from two classes to form the Platypus class. And, because classes can have only one immediate superclass in Java, this sort of problem simply cannot be solved elegantly. Other OOP languages include the concept of multiple inheritance, which solves this problem. With multiple inheritance, a class can inherit from more than one superclass and get behavior and attributes from all its superclasses at once. Using multiple inheritance, you could simply factor the common behavior of BritishCar and BritishMotorcycle into a single class (BritishThing) and then create new classes that inherit from both their primary superclass and the British class. The problem with multiple inheritance is that it makes a programming language far more complex to learn, to use, and to implement. Questions of method invocation and how the class hierarchy is organized become far more complicated with multiple inheritance, and more open to confusion and ambiguity. And because one of the goals for Java was that it be simple, multiple inheritance was rejected in favor of the simpler single inheritance. So how do you solve the problem of needing common behavior that doesn't fit into the strict class hierarchy? Java, borrowing from Objective-C, has another hierarchy altogether separate from the main class hierarchy, a hierarchy of mixable behavior classes. Then, when you create a new class, that class has only one primary superclass, but it can pick and choose different common behaviors from the other hierarchy. This other hierarchy is the interface hierarchy. A Java interface is a collection of abstract behavior that can be mixed into any class to add to that class behavior that is not supplied by its superclasses. Specifically, a Java interface contains nothing but file:///G|/ebooks/1575211831/ch16.htm (8 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces abstract method definitions and constants-no instance variables and no method implementations. Interfaces are implemented and used throughout the Java class library whenever a behavior is expected to be implemented by a number of disparate classes. The Java class hierarchy, for example, defines and uses the interfaces java.lang.Runnable, java.util.Enumeration, java.util.Observable, java.awt.image.ImageConsumer, and java.awt.image.ImageProducer. Some of these interfaces you've seen before; others you'll see later in this book. Still others may be useful to you in your own programs, so be sure to examine the API to see what's available to you. Abstract Design and Concrete Implementation Throughout this book you've gotten a taste of the difference between design and implementation in object-oriented programming, where the design of a thing is its abstract representation and its implementation is the concrete counterpart of the design. You saw this with methods, where a method's signature defines how it's used, but the method implementation can occur anywhere in the class hierarchy. You saw this with abstract classes, where the class's design provides a template for behavior, but that behavior isn't implemented until further down in the hierarchy. This distinction between the design and the implementation of a class or a method is a crucial part of object-oriented programming theory. Thinking in terms of design when you organize your classes allows you to get the big picture without being bogged down in implementation details. And having the overall design already defined when you actually start implementing allows you to concentrate on those details solely for the class you're working on. This programming version of "think globally, act locally" provides a powerful way of thinking about how your classes and your programs and your overall designs are organized and how they interrelate. An interface is made up of a set of method signatures with no implementations, making it the embodiment of pure design. By mixing an interface in with your class, you're encompassing that design into your implementation. That design can then be safely included anywhere in the class hierarchy because there are no class-specific details of how an interface behaves-nothing to override, nothing to keep track of, just the name and arguments for a method. What about abstract classes? Don't abstract classes provide this same behavior? Yes and no. Abstract classes and the abstract methods inside them do provide a separation of design and implementation, allowing you to factor common behavior into an abstract superclass. But abstract classes can, and often do, contain some concrete data (such as instance variables), and you can have an abstract superclass with both abstract and regular methods, thereby confusing the distinction. Even a pure abstract class with only abstract methods isn't as powerful as an interface. An abstract class is simply another class; it inherits from some other class and has its place in the hierarchy. Abstract classes cannot be shared across different parts of the class hierarchy the way interfaces can, nor can they be mixed into other classes that need their behavior. To attain the sort of flexibility of shared behavior across the class hierarchy, you need an interface. You can think of the difference between the design and the implementation of any Java class as the difference between the interface hierarchy and the design hierarchy. The singly inherited class hierarchy contains the implementations where the relationships between classes and behavior are rigidly defined. The multiply inherited mixable interface hierarchy, however, contains the design and can be freely used anywhere it's needed in the implementation. This is a powerful way of thinking about the organization of your program, and although it takes a little getting used to, it's also a highly recommended one. Interfaces and Classes Classes and interfaces, despite their different definitions, have an awful lot in common. Interfaces, like classes, are declared in source files, one interface to a file. Like classes, they also are compiled using the Java compiler into .class files. And, in most cases, anywhere you can use a class (as a data type for a variable, as the result of a cast, and so on), you can also use an interface. Almost everywhere that this book has a class name in any of its examples or discussions, you can substitute an interface name. Java programmers often say "class" when they actually mean "class or interface." Interfaces complement and extend the power of classes, and the two can be treated almost exactly the same. One of the few differences between them is that an interface cannot be instantiated: new can only create an instance of a class. file:///G|/ebooks/1575211831/ch16.htm (9 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces Implementing and Using Interfaces Now that you've grasped what interfaces are and why they're powerful (the "programming in the large" part), let's move on to actual bits of code ("programming in the small"). There are two things you can do with interfaces: use them in your own classes and define your own. Let's start with the former. The implements Keyword To use an interface, you include the implements keyword as part of your class definition. You did this back on Day 11, "More Animation, Images, and Sound," when you learned about threads and included the Runnable interface in your applet definition: // java.applet.Applet is the superclass public class Neko extends java.applet.Applet implements Runnable { // but it also has Runnable behavior ... } Because interfaces provide nothing but abstract method definitions, you then have to implement those methods in your own classes, using the same method signatures from the interface. Note that once you include an interface, you have to implement all the methods in that interface-you can't pick and choose the methods you need. By implementing an interface you're telling users of your class that you support all of that interface. (Note that this is another difference between interfaces and abstract classes-subclasses of the latter can pick which methods to implement or override and can ignore others.) After your class implements an interface, subclasses of your class will inherit those new methods (and can override or overload them) just as if your superclass had actually defined them. If your class inherits from a superclass that implements a given interface, you don't have to include the implements keyword in your own class definition. Let's examine one simple example-creating the new class Orange. Suppose you already have a good implementation of the class Fruit and an interface, Fruitlike, that represents what Fruits are expected to be able to do. You want an orange to be a fruit, but you also want it to be a spherical object that can be tossed, rotated, and so on. Here's how to express it all (don't worry about the definitions of these interfaces for now; you'll learn more about them later today): interface Fruitlike { void decay(); void squish(); ... } class Fruit implements Fruitlike { private Color myColor; private int daysTilIRot; ... } interface Spherelike { void toss(); void rotate(); ... } class Orange extends Fruit implements Spherelike { . . . // toss()ing may squish() me (unique to me) } Note that the class Orange doesn't have to say implements Fruitlike because, by extending Fruit, it already has! One file:///G|/ebooks/1575211831/ch16.htm (10 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces of the nice things about this structure is that you can change your mind about what class Orange extends (if a really great Sphere class is suddenly implemented, for example), yet class Orange will still understand the same two interfaces: class Sphere implements Spherelike { private float radius; ... } // extends Object class Orange extends Sphere implements Fruitlike { ... // users of Orange never need know about the change! } Implementing Multiple Interfaces Unlike the singly inherited class hierarchy, you can include as many interfaces as you need in your own classes, and your class will implement the combined behavior of all the included interfaces. To include multiple interfaces in a class, just separate their names with commas: public class Neko extends java.applet.Applet implements Runnable, Eatable, Sortable, Observable { ... } Note that complications may arise from implementing multiple interfaces-what happens if two different interfaces both define the same method? There are three ways to solve this: q If the methods in each of the interfaces have identical signatures, you implement one method in your class and that definition satisfies both interfaces. q If the methods have different parameter lists, it is a simple case of method overloading; you implement both method signatures, and each definition satisfies its respective interface definition. q If the methods have the same parameter lists but differ in return type, you cannot create a method that satisfies both (remember, method overloading is triggered by parameter lists, not by return type). In this case, trying to compile a class that implements both interfaces will produce a compiler error. Running across this problem suggests that your interfaces have some design flaws that might need re-examining. Other Uses of Interfaces Remember that almost everywhere that you can use a class, you can use an interface instead. So, for example, you can declare a variable to be of an interface type: Runnable aRunnableObject = new MyAnimationClass() When a variable is declared to be of an interface type, it simply means that any object the variable refers to is expected to have implemented that interface-that is, it is expected to understand all the methods that interface specifies. It assumes that a promise made between the designer of the interface and its eventual implementors has been kept. In this case, because aRunnableObject contains an object of the type Runnable, the assumption is that you can call aRunnableObject.run(). The important thing to realize here is that although aRunnableObject is expected to be able to have the run() method, you could write this code long before any classes that qualify are actually implemented (or even created!). In traditional object-oriented programming, you are forced to create a class with "stub" implementations (empty methods, or methods that print silly messages) to get the same effect. You can also cast objects to an interface, just as you can cast objects to other classes. So, for example, let's go back to that definition of the Orange class, which implemented both the Fruitlike interface (through its superclass, Fruit) and the Spherelike interface. Here we'll cast instances of Orange to both classes and interfaces: file:///G|/ebooks/1575211831/ch16.htm (11 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces Orange Fruit Fruitlike Spherelike anOrange aFruit aFruitlike aSpherelike = = = = new Orange(); (Fruit)anOrange; (Fruitlike)anOrange; (Spherelike)anOrange; aFruit.decay(); aFruitlike.squish(); // fruits decay // and squish aFruitlike.toss(); aSpherelike.toss(); // things that are fruitlike do not toss // but things that are spherelike do anOrange.decay(); anOrange.squish(); anOrange.toss(); anOrange.rotate(); // oranges can do it all Declarations and casts are used in this example to restrict an orange's behavior to acting more like a mere fruit or sphere. Finally, note that although interfaces are usually used to mix in behavior to other classes (method signatures), interfaces can also be used to mix in generally useful constants. So, for example, if an interface defined a set of constants, and then multiple classes used those constants, the values of those constants could be globally changed without having to modify multiple classes. This is yet another example of where the use of interfaces to separate design from implementation can make your code more general and more easily maintainable. Creating and Extending Interfaces After using interfaces for a while, the next step is to define your own interfaces. Interfaces look a lot like classes; they are declared in much the same way and can be arranged into a hierarchy, but there are rules for declaring interfaces that must be followed. New Interfaces To create a new interface, you declare it like this: public interface Growable { ... } This is, effectively, the same as a class definition, with the word interface replacing the word class. Inside the interface definition you have methods and constants. The method definitions inside the interface are public and abstract methods; you can either declare them explicitly as such, or they will be turned into public and abstract methods if you do not include those modifiers. You cannot declare a method inside an interface to be either private or protected. So, for example, here's a Growable interface with one method explicitly declared public and abstract (growIt()) and one implicitly declared as such (growItBigger()). public interface Growable { public abstract void growIt(); //explicity public and abstract void growItBigger(); // effectively public and abstract } Note that, as with abstract methods in classes, methods inside interfaces do not have bodies. Remember, an interface is pure design; there is no implementation involved. In addition to methods, interfaces can also have variables, but those variables must be declared public, static, and final file:///G|/ebooks/1575211831/ch16.htm (12 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces (making them constant). As with methods, you can explicitly define a variable to be public, static, and final, or it will be implicitly defined as such if you don't use those modifiers. Here's that same Growable definition with two new variables: public interface Growable { public static final int increment = 10; long maxnum = 1000000; // becomes public static and final public abstract void growIt(); //explicitly public and abstract void growItBigger(); // effectively public and abstract } Interfaces must have either public or package protection, just like classes. Note, however, that interfaces without the public modifier do not automatically convert their methods to public and abstract nor their constants to public. A non-public interface also has non-public methods and constants that can be used only by classes and other interfaces in the same package. Interfaces, like classes, can belong to a package by adding a package statement to the first line of the class file. Interfaces can also import other interfaces and classes from other packages, just as classes can. Methods Inside Interfaces One trick to note about methods inside interfaces: Those methods are supposed to be abstract and apply to any kind of class, but how can you define parameters to those methods? You don't know what class will be using them! The answer lies in the fact that you use an interface name anywhere a class name can be used, as you learned earlier. By defining your method parameters to be interface types, you can create generic parameters that apply to any class that might use this interface. So, for example, take the interface Fruitlike, which defines methods (with no arguments) for decay() and squish(). There might also be a method for germinateSeeds(), which has one argument: the fruit itself. Of what type is that argument going to be? It can't be simply Fruit, because there may be a class that's Fruitlike (that is, implements the Fruitlike interface) without actually being a fruit. The solution is to declare the argument as simply Fruitlike in the interface: public interface Fruitlike { public abstract germinate(Fruitlike self) { ... } } Then, in an actual implementation for this method in a class, you can take the generic Fruitlike argument and cast it to the appropriate object: public class Orange extends Fruit { public germinate(Fruitlike self) { Orange theOrange = (Orange)self; ... } } Extending Interfaces As with classes, interfaces can be organized into a hierarchy. When one interface inherits from another interface, that "subinterface" acquires all the method definitions and constants that its "superinterface" defined. To extend an interface, you use the extends keyword just as you do in a class definition: file:///G|/ebooks/1575211831/ch16.htm (13 of 16) [11/06/2000 7:45:57 PM] Day 16 -- Packages and Interfaces public interface Fruitlike extends Foodlike { ... } Note that, unlike classes, the interface hierarchy has no equivalent of the Object class; this hierarchy is not rooted at any one point. Interfaces can either exist entirely on their own or inherit from another interface. Note also that, unlike the class hierarchy, the inheritance hierarchy is multiply inherited. So, for example, a single interface can extend as many classes as it needs to (separated by commas in the extends part of the definition), and the new interface will contain a combination of all its parent's methods and constants. Here's an interface definition for an interface called BusyInterface that inherits from a whole lot of other interfaces: public interface BusyInterface extends Runnable, Growable, Fruitlike, Observable { ...} In multiply inherited interfaces, the rules for managing method name conflicts are the same as for classes that use multiple interfaces; methods that differ only in return type will result in a compiler error. An Example: Enumerating Linked Lists To finish up today's lesson, here's an example that uses packages, package protection, and defines a class that implements the Enumeration interface (part of the java.util package). Listing 16.2 shows the code. Listing 16.2. Packages, classes, and interfaces. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: package collections; public class LinkedList { private Node root; ... public Enumeration enumerate() { return new LinkedListEnumerator(root); } } class Node { private Object private Node contents; next; ... public Object contents() { return contents; } public Node next() { return next; } } class LinkedListEnumerator implements Enumeration { private Node currentNode; LinkedListEnumerator(Node currentNode = root; root) { } file:///G|/ebooks/1575211831/ch16.htm (14 of 16) [11/06/2000 7:45:58 PM] Day 16 -- Packages and Interfaces 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: } public boolean hasMoreElements() { return currentNode != null; } public Object nextElement() { Object anObject = currentNode.contents(); currentNode = currentNode.next(); return anObject; } Here is a typical use of the enumerator: collections.LinkedList aLinkedList = createLinkedList(); java.util.Enumeration e = aLinkedList.enumerate(); while (e.hasMoreElements()) { Object anObject = e.nextElement(); // do something useful with anObject } Notice that, although you are using the Enumeration e as though you know what it is, you actually do not. In fact, it is an instance of a hidden class (LinkedListEnumerator) that you cannot see or use directly. By using a combination of packages and interfaces, the LinkedList class has managed to provide a transparent public interface to some of its most important behavior (via the already defined interface java.util.Enumeration) while still encapsulating (hiding) its two implementation (support) classes. Handing out an object like this is sometimes called vending. Often the "vendor" gives out an object that a receiver can't create itself but that it knows how to use. By giving it back to the vendor, the receiver can prove it has a certain capability, authenticate itself, or do any number of useful tasks-all without knowing much about the vended object. This is a powerful metaphor that can be applied in a broad range of situations. Summary Today you have learned how packages can be used to collect and categorize classes into meaningful groups. Packages are arranged in a hierarchy, which not only better organizes your programs but allows you and the millions of Java programmers out on the Net to name and share their projects uniquely with one another. You have also learned how to use packages, both your own and the many preexisting ones in the Java class library. You then discovered how to declare and use interfaces, a powerful mechanism for extending the traditional single inheritance of Java's classes and for separating design inheritance from implementation inheritance in your programs. Interfaces are often used to call common (shared) methods when the exact class involved is not known. You'll see further uses of interfaces tomorrow and the day after. Finally, you learned that packages and interfaces can be combined to provide useful abstractions, such as LinkedList, that appear simple yet are actually hiding almost all their (complex) implementation from their users. This is a powerful technique. Q&A Q: A: Q: Can you use import some.package.B* to import all the classes in that package that begin with B? No, the import asterisk (*) does not act like a command-line asterisk. Then what exactly does importing with an * mean? file:///G|/ebooks/1575211831/ch16.htm (15 of 16) [11/06/2000 7:45:58 PM] Day 16 -- Packages and Interfaces A: Q: A: Q: A: Q: A: Combining everything said previously, this precise definition emerges: It imports all the public classes you use in your Java code that are directly inside the package named, and not inside one of its subpackages. (You can only import exactly this set of classes, or exactly one explicitly named class, from a given package.) By the way, Java only "loads" the information for a class when you actually refer to that class in your code, so the * form of import is no less efficient than naming each class individually. Why is full multiple inheritance so complex that Java abandoned it? It's not so much that it is too complex, but that it makes the language overly complicated-and as you'll learn on Day 21, "Under the Hood," this can cause larger systems to be less trustworthy and thus less secure. For example, if you were to inherit from two different parents, each having an instance variable with the same name, you would be forced to allow the conflict and explain how the exact same reference to that variable name in each of your superclasses, and in you (all three), are now different. Instead of being able to call "super" methods to get more abstract behavior accomplished, you would always need to worry about which of the (possibly many) identical methods you actually wished to call in which parent. Java's run-time method dispatching would have to be more complex as well. Finally, because so many people would be providing classes for reuse on the Net, the normally manageable conflicts that would arise in your own program would be confounded by millions of users mixing and matching these fully multiply inherited classes at will. In the future, if all these issues are resolved, more powerful inheritance may be added to Java, but its current capabilities are already sufficient for 99 percent of your programs. abstract classes don't have to implement all the methods in an interface themselves, but don't all their subclasses have to? Actually, no. Because of inheritance, the precise rule is that an implementation must be provided by some class for each method, but it doesn't have to be your class. This is analogous to when you are the subclass of a class that implements an interface for you. Whatever the abstract class doesn't implement, the first non-abstract class below it must implement. Then, any further subclasses need do nothing further. You didn't mention callbacks. Aren't they an important use of interfaces? Yes, but I didn't mention them because a good example would be too bulky. Callbacks are often used in user interfaces (such as window systems) to specify what set of methods is going to be sent whenever the user does a certain set of things (such as clicking the mouse somewhere, typing, and so forth). Because the user interface classes should not "know" anything about the classes using them, an interface's ability to specify a set of methods separate from the class tree is crucial in this case. Callbacks using interfaces are not as general as using, for example, the perform: method of Smalltalk, however, because a given object can only request that a user interface object "call it back" using a single method name. Suppose that object wanted two user interface objects of the same class to call it back, using different names to tell them apart? It cannot do this in Java, and it is forced to use special state and tests to tell them apart. (I warned you that it was complicated!) So although interfaces are quite valuable in this case, they are not the ideal callback facility. file:///G|/ebooks/1575211831/ch16.htm (16 of 16) [11/06/2000 7:45:58 PM] Day 13 -- Creating User Interfaces with the awt Day 13 Creating User Interfaces with the awt by Laura Lemay CONTENTS q An awt Overview q The Basic User Interface Components r r Buttons r Check Boxes r Radio Buttons r Choice Menus r q Labels Text Fields Panels and Layout r Layout Managers: An Overview r The FlowLayout Class r Grid Layouts r Border Layouts r Card Layouts r Grid Bag Layouts r Insets q Handling UI Actions and Events q Nesting Panels and Components r r q Nested Panels Events and Nested Panels More UI Components r Text Areas r Scrolling Lists r Scrollbars and Sliders r Canvases q More UI Events q Fun with Components q A Complete Example: RGB-to-HSB Converter r r Defining the Subpanels r Handling the Actions r Updating the Result r q Designing and Creating the Applet Layout The Complete Source Code Up and Coming in Java 1.1 file:///G|/ebooks/1575211831/ch13.htm (1 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt q Summary q Q&A For the past five days you've concentrated on creating applets that do very simple things: display text, play an animation or a sound, or interact with the user. When you get past that point, however, you may want to start creating more complex applets that behave like real applications embedded in a Web page-applets that start to look like real GUI applications with buttons, menus, text fields, and other elements. It's this sort of real work in Java applets and applications for which Java's Abstract Windowing Toolkit, or awt, was designed. You've actually been using the awt all along, as you might have guessed from the classes you've been importing. The Applet class and most of the classes you've been using this week are all integral parts of the awt. The awt provides the following: q A full set of user interface (UI) widgets and other components, including windows, menus, buttons, check boxes, text fields, scrollbars, and scrolling lists q Support for UI containers, which can contain other embedded containers or UI widgets q An event system for managing system and user events among parts of the awt q Mechanisms for laying out components in a way that enables platformindependent UI design Today you'll learn about how to use all these things in your Java applets. Tomorrow you'll learn about creating windows, menus, and dialog boxes, which enable you to pop up separate windows from the browser window. In addition, you can use the awt in standalone applications, so everything you've learned so far this week can still be used. If you find the framework of the Web browser too limiting, you can take your awt background and start writing full-fledged Java applications. Today, however, you'll continue focusing on applets. Note This is by far the most complex lesson so far, and it's a long chapter as well. There's a lot to cover and a lot of code to go through today, so if it starts becoming overwhelming, you might want to take two days (or more) for this one. An awt Overview The basic idea behind the awt is that a graphical Java program is a set of nested components, starting from the outermost window all the way down to the smallest UI component. Components can include things you can actually see on the screen, such as windows, menu bars, buttons, and text fields, and they can also include containers, which in turn can contain other components. Figure 13.1 shows how a sample page in a Java browser might include several different components, all of which are managed through the awt. Figure 13.1 : awt components. This nesting of components within containers within other components creates a hierarchy of components, from the smallest check box inside an applet to the overall window on the screen. The hierarchy of components determines the arrangement of items on the screen and inside other items, the order in which they are painted, and how events are passed from one component to another. These are the major components you can work with in the awt: q Containers. Containers are generic awt components that can contain other components, including other containers. The most common form of container is the panel, which represents a container that can be displayed onscreen. Applets are a form of panel (in fact, the Applet class is a subclass of the Panel class). q Canvases. A canvas is a simple drawing surface. Although you can draw on panels (as you've been doing all along), canvases are good for painting images or performing other graphics operations. q UI components. These can include buttons, lists, simple pop-up menus, check boxes, test fields, and other typical elements of a user interface. q Window construction components. These include windows, frames, menu bars, and dialog boxes. They are listed separately from the other UI components because you'll use these less often-particularly in applets. In applets, the browser provides the main window and menu bar, so you don't have to use these. Your applet may create a new window, however, or you may want file:///G|/ebooks/1575211831/ch13.htm (2 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt to write your own Java application that uses these components. (You'll learn about these tomorrow.) The classes inside the java.awt package are written and organized to mirror the abstract structure of containers, components, and individual UI components. Figure 13.2 shows some of the class hierarchy that makes up the main classes in the awt. The root of most of the awt components is the class Component, which provides basic display and event-handling features. The classes Container, Canvas, TextComponent, and many of the other UI components inherit from Component. Inheriting from the Container class are objects that can contain other awt components-the Panel and Window classes, in particular. Note that the java.applet.Applet class, even though it lives in its own package, inherits from Panel, so your applets are an integral part of the hierarchy of components in the awt system. Figure 13.2 : A Partial awt class Hierarchy. A graphical user interface-based application that you write by using the awt can be as complex as you like, with dozens of nested containers and components inside each other. The awt was designed so that each component can play its part in the overall awt system without needing to duplicate or keep track of the behavior of other parts in the system. In addition to the components themselves, the awt also includes a set of layout managers. Layout managers determine how the various components are arranged when they are displayed onscreen, and their various sizes relative to each other. Because Java applets and applications that use the awt can run on different systems with different displays, different fonts, and different resolutions, you cannot just stick a particular component at a particular spot on the window. Layout managers help you create UI layouts that are dynamically arranged and can be displayed anywhere the applet or application might be run. The Basic User Interface Components The simplest form of awt component is the basic UI component. You can create and add these to your applet without needing to know anything about creating containers or panels-your applet, even before you start painting and drawing and handling events, is already an awt container. Because an applet is a container, you can put other awt components-such as UI components or other containers-into it. In this section, you'll learn about the basic UI components: labels, buttons, check boxes, choice menus, and text fields. In each case, the procedure for creating the component is the same-you first create the component and then add it to the panel that holds it, at which point it is displayed on the screen. To add a component to a panel (such as your applet, for example), use the add() method: public void init() { Button b = new Button("OK"); add(b); } Here the add() method refers to the current applet-in other words, it means "add this element to me." You can also add elements to other containers, as you'll learn later. Note that where the component appears in the panel depends on the layout manager that panel is defined to have. In these examples I've used both flow layouts and grid layouts, depending on which makes the applet look better. You'll learn more about panels and layouts in the next section. Note also that each of these components has an action associated with it-that is, something that component does when it's activated. Actions generally trigger events or other activities in your applet (they are often called callbacks in other window toolkits). In this section, you'll focus on creating the components themselves; you'll learn about adding actions to them later in today's lesson. On to the components! Labels The simplest form of UI component is the label, which is, effectively, a text string that you can use to label other UI components. Labels are not editable; they just label other components on the screen. The advantages that a label has over an ordinary text string (that you'd draw using drawString() in the paint() method) are q You don't have to redraw labels yourself. Labels are an awt element, and the awt keeps track of drawing them. q Labels follow the layout of the panel in which they're contained and can be aligned with other UI components. Panel layout is determined by the layout manager, which you'll learn about later, in the section "Panels and Layout." file:///G|/ebooks/1575211831/ch13.htm (3 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt A label is an uneditable text string that acts as a description for other awt components. To create a label, use one of the following constructors: q Label() creates an empty label, with its text aligned left. q Label(String) creates a label with the given text string, also aligned left. q Label(String, int) creates a label with the given text string and the given alignment. The available alignment numbers are stored in class variables in Label, making them easier to remember: Label.RIGHT, Label.LEFT, and Label.CENTER. You can change the label's font with the setFont() method, either called on the label itself to change the individual label, or on the enclosing component to change all the labels. Here's some simple code to create a few labels in Helvetica Bold (Figure 13.3 shows how this looks onscreen): Figure 13.3 : Three labels with various alignments. Note This code uses the setLayout method to create a new layout manager. Don't worry about that line right now; you'll learn more about layout managers in the next section. import java.awt.*; public class LabelTest extends java.applet.Applet { public void init() { setFont(new Font ("Helvetica", Font.BOLD, 14)); setLayout(new GridLayout(3,1)); add(new Label("aligned left", Label.LEFT)); add(new Label("aligned center", Label.CENTER)); add(new Label("aligned right", Label.RIGHT)); } } When you have a Label object, you can use methods defined in the Label class to get and set the values of the text, as shown in Table 13.1. Table 13.1. Label methods. Method getText() setText(String) getAlignment() Action Returns a string containing this label's text Changes the text of this label Returns an integer representing the alignment of this label: 0 is Label.LEFT 1 is Label.CENTER 2 is Label.RIGHT setAlignment(int) Changes the alignment of this label to the given integer-use the class variables listed in the getAlignment() method Buttons The second user interface component to explore is the button. Buttons are simple UI components that trigger some action in your interface when they are pressed. For example, a calculator applet might have buttons for each number and operator, or a dialog box might have buttons for OK and Cancel. A button is a UI component that, when "pressed" (selected) with the mouse, triggers some action. file:///G|/ebooks/1575211831/ch13.htm (4 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt To create a button, use one of the following constructors: q Button() creates an empty button with no label. q Button(String) creates a button with the given string as a label. Once you have a Button object, you can get the value of the button's label by using the getLabel() method and set the label using the setLabel(String) method. Figure 13.4 shows some simple buttons, created using the following code: Figure 13.4 : Four buttons in Netscape. public class ButtonTest extends java.applet.Applet { public void init() { add(new Button("Rewind")); add(new Button("Play")); add(new Button("Fast Forward")); add(new Button("Stop")); } } Check Boxes Check boxes are user-interface components that have two states: on and off (or checked and unchecked, selected and unselected, true and false, and so on). Unlike buttons, check boxes usually don't trigger direct actions in a UI, but instead are used to indicate optional features of some other action. Check boxes can be used in two ways: q Nonexclusive: Given a series of check boxes, any of them can be selected. q Exclusive: Given a series, only one check box can be selected at a time. The latter kind of check boxes are called radio buttons or check box groups, and are described in the next section. Check boxes are UI components that can be selected or deselected (checked or unchecked) to provide options. Nonexclusive check boxes can be checked or unchecked independently of other check boxes. Exclusive check boxes, sometimes called radio buttons, exist in groups; only one in the group can be checked at one time. Nonexclusive check boxes can be created by using the Checkbox class. You can create a check box using one of the following constructors: q Checkbox() creates an empty check box, unselected. q Checkbox(String) creates a check box with the given string as a label. q Checkbox(String, null, boolean) creates a check box that is either selected or deselected based on whether the boolean argument is true or false, respectively. (The null is used as a placeholder for a group argument. Only radio buttons have groups, as you'll learn in the next section.) Figure 13.5 shows a few simple check boxes (only Underwear is selected) generated using the following code: Figure 13.5 : Five check boxes, one selected. import java.awt.*; public class CheckboxTest extends java.applet.Applet { public void init() { setLayout(new FlowLayout(FlowLayout.LEFT)); add(new Checkbox("Shoes")); add(new Checkbox("Socks")); add(new Checkbox("Pants")); file:///G|/ebooks/1575211831/ch13.htm (5 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt add(new Checkbox("Underwear", null, true)); add(new Checkbox("Shirt")); } } Table 13.2 lists some of the check box methods. Table 13.2. Check box methods. Method getLabel() setLabel(String) getState() Action Returns a string containing this check box's label Changes the text of the check box's label Returns true or false, based on whether the check box is selected setState(boolean) Changes the check box's state to selected (true) or unselected (false) Radio Buttons Radio buttons have the same appearance as check boxes, but only one in a series can be selected at a time. To create a series of radio buttons, first create an instance of CheckboxGroup: CheckboxGroup cbg = new CheckboxGroup(); Then create and add the individual check boxes using the constructor with three arguments (the first is the label, the second is the group, and the third is whether that check box is selected). Note that because radio buttons, by definition, have only one in the group selected at a time, the last true to be added will be the one selected by default: add(new Checkbox("Yes", cbg, true); add(new Checkbox("No", cbg, false); Here's a simple example (the results of which are shown in Figure 13.6): Figure 13.6 : Six radio buttons (exclusive check boxes), one selected. import java.awt.*; public class CheckboxGroupTest extends java.applet.Applet { public void init() { setLayout(new FlowLayout(FlowLayout.LEFT)); CheckboxGroup cbg = new CheckboxGroup(); add(new add(new add(new add(new add(new add(new Checkbox("Red", cbg, false)); Checkbox("Blue", cbg, false)); Checkbox("Yellow", cbg, false)); Checkbox("Green", cbg, true)); Checkbox("Orange", cbg, false)); Checkbox("Purple", cbg, false)); } } All the check box methods shown in Table 13.2 in the previous section can be used with the check boxes in the group. In addition, you can use the getCheckboxGroup() and setCheckboxGroup() methods (defined in the Checkbox() class) to access and change the group of any given check box. Finally, the getCurrent() and setCurrent(Checkbox) methods, defined in CheckboxGroup, can be used to get or set file:///G|/ebooks/1575211831/ch13.htm (6 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt the currently selected check box. Choice Menus The choice menu is a more complex UI component than labels, buttons, or check boxes. Choice menus are pop-up (or pull-down) menus from which you can select an item. The menu then displays that choice on the screen. The function of a choice menu is the same across platforms, but its actual appearance may vary from platform to platform. Note that choice menus can have only one item selected at a time. If you want to be able to choose multiple items from the menu, use a scrolling list instead (you'll learn more about scrolling lists later today, in the section "More UI Components"). Choice menus are pop-up menus of items from which you can choose one item. To create a choice menu, create an instance of the Choice class and then use the addItem() method to add individual items to it in the order in which they should appear. Finally, add the entire choice menu to the panel in the usual way. Here's a simple program that builds a choice menu of fruits; Figure 13.7 shows the result (with the menu pulled down): Figure 13.7 : A choice menu. import java.awt.*; public class ChoiceTest extends java.applet.Applet { public void init() { Choice c = new Choice(); c.addItem("Apples"); c.addItem("Oranges"); c.addItem("Strawberries"); c.addItem("Blueberries"); c.addItem("Bananas"); add(c); } } Even after your choice menu has been added to a panel, you can continue to add items to that menu with the addItem() method. Table 13.3 shows some other methods that may be useful in working with choice menus. Table 13.3. Choice menu methods. Method getItem(int) Action Returns the string item at the given position (items inside a choice begin at 0, just like arrays) Returns the number of items in the menu countItems() getSelectedIndex() Returns the index position of the item that's selected getSelectedItem() Returns the currently selected item as a string Selects the item at the given position select(int) Selects the item with the given string select(String) Text Fields Unlike the UI components up to this point, which only enable you to select among several options to perform an action, text fields allow you to enter and edit text. Text fields are generally only a single line and do not have scrollbars; text areas, which you'll learn about later today, are better for larger amounts of text. Text fields are different from labels in that they can be edited; labels are good for just displaying text, text fields for getting text input from the user. file:///G|/ebooks/1575211831/ch13.htm (7 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt Text fields provide an area where you can enter and edit a single line of text. To create a text field, use one of the following constructors: q TextField() creates an empty TextField that is 0 characters wide (it will be resized by the current layout manager). q TextField(int) creates an empty text field. The integer argument indicates the minimum number of characters to display. q TextField(String) creates a text field initialized with the given string. The field will be automatically resized by the current layout manager. q TextField(String, int) creates a text field some number of characters wide (the integer argument) containing the given string. If the string is longer than the width, you can select and drag portions of the text within the field, and the box will scroll left or right. For example, the following line creates a text field 30 characters wide with the string "Enter Your Name" as its initial contents: TextField tf = new TextField("Enter Your Name", 30); add(tf); Tip Text fields include only the editable field itself. You usually need to include a label with a text field to indicate what belongs in that text field. You can also create a text field that obscures the characters typed into it-for example, for password fields. To do this, first create the text field itself; then use the setEchoCharacter() method to set the character that is echoed on the screen. Here is an example: TextField tf = new TextField(30); tf.setEchoCharacter('*'); Figure 13.8 shows three text boxes (and labels) that were created using the following code: Figure 13.8 : Three text fields to allow input from the user. add(new Label("Enter your Name")); add(new TextField("your name here", 45)); add(new Label("Enter your phone number")); add(new TextField(12)); add(new Label("Enter your password")); TextField t = new TextField(20); t.setEchoCharacter('*'); add(t); The text in the first field (your name here) was initialized in the code; I typed the text in the remaining two boxes just before taking the snapshot. Text fields inherit from the class TextComponent and have a whole suite of methods, both inherited from that class and defined in their own class, that may be useful to you in your Java programs. Table 13.4 shows a selection of those methods. Table 13.4. Text field methods. Method getText() setText(String) getColumns() select(int, int) selectAll() isEditable() Action Returns the text this text field contains (as a string) Puts the given text string into the field Returns the width of this text field Selects the text between the two integer positions (positions start from 0) Selects all the text in the field Returns true or false based on whether the text is editable file:///G|/ebooks/1575211831/ch13.htm (8 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt setEditable(boolean) true (the default) enables text to be edited; false freezes the text Returns the character used for masking input getEchoChar() echoCharIsSet() Returns true or false based on whether the field has a masking character Note The descriptions of the getEchoChar() and echoCharIsSet() methods refer to masking user input. User input masking is a technique of limiting user input to a specific type, such as a number. Other types of user input masking include dates and phone numbers, where there are a specific number of numeric digits arranged in a constant format. Panels and Layout awt panels can contain UI components or other panels. The question now is how those components are actually arranged and displayed onscreen. In other windowing systems, UI components are often arranged using hard-coded pixel measurements-put a text field at the position 10,30, for example-the same way you used the graphics operations to paint squares and ovals on the screen. In the awt, your UI design may be displayed on many different window systems on many different screens and with many different kinds of fonts with different font metrics. Therefore, you need a more flexible method of arranging components on the screen so that a layout that looks nice on one platform isn't a jumbled, unusable mess on another. For just this purpose, Java has layout managers, insets, and hints that each component can provide to help dynamically lay out the screen. Note that the nice thing about awt components and user-interface items is that you don't have to paint them-the awt system manages all that for you. If you have graphical components or images, or you want to create animation inside panels, you still have to do that by hand, but for most of the basic components, all you have to do is put them on the screen and Java will handle the rest. Layout Managers: An Overview The actual appearance of the awt components on the screen is usually determined by two things: how those components are added to the panel that holds them (either the order or through arguments to add()) and the layout manager that panel is currently using to lay out the screen. The layout manager determines how portions of the screen will be sectioned and how components within that panel will be placed. The layout manager determines how awt components are dynamically arranged on the screen. Each panel on the screen can have its own layout manager. By nesting panels within panels, and using the appropriate layout manager for each one, you can often arrange your UI to group and arrange components in a way that is functionally useful and that looks good on a variety of platforms and windowing systems. You'll learn about nesting panels in a later section. The awt provides five basic layout managers: FlowLayout, GridLayout, BorderLayout, CardLayout, and GridBagLayout. To create a layout manager for a given panel, create an instance of that layout manager and then use the setLayout() method for that panel. This example sets the layout manager of the entire enclosing applet panel: public void init() { setLayout(new FlowLayout()); } Setting the default layout manager, like creating user-interface components, is best done during the applet's initialization, which is why it's included here. After the layout manager is set, you can start adding components to the panel. The order in which components are added or the arguments you use to add those components is often significant, depending on which layout manager is currently active. Read on for information about the specific layout managers and how they present components within the panel to which they apply. file:///G|/ebooks/1575211831/ch13.htm (9 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt The following sections describe the five basic Java awt layout managers. The FlowLayout Class The FlowLayout class is the most basic of layouts. Using flow layout, components are added to the panel one at a time, row by row. If a component doesn't fit onto a row, it's wrapped onto the next row. The flow layout also has an alignment, which determines the alignment of each row. By default, each row is centered. Flow layout arranges components from left to right in rows. The rows are aligned left, right, or centered. To create a basic flow layout with a centered alignment, use the following line of code in your panel's initialization (because this is the default pane layout, you don't need to include this line if that is your intent): setLayout(new FlowLayout()); With the layout set, the order in which you add elements to the layout determines their position. The following code creates a simple row of six buttons in a centered flow layout (Figure 13.9 shows the result): Figure 13.9 : Six buttons, arranged using a flow layout manager. import java.awt.*; public class FlowLayoutTest extends java.applet.Applet { public void init() { setLayout(new FlowLayout()); add(new Button("One")); add(new Button("Two")); add(new Button("Three")); add(new Button("Four")); add(new Button("Five")); add(new Button("Six")); } } To create a flow layout with an alignment other than centered, add the FlowLayout.RIGHT or FlowLayout.LEFT class variable as an argument: setLayout(new FlowLayout(FlowLayout.LEFT)); You can also set horizontal and vertical gap values by using flow layouts. The gap is the number of pixels between components in a panel; by default, the horizontal and vertical gap values are three pixels, which can be very close indeed. Horizontal gap spreads out components to the left and to the right; vertical gap spreads them to the top and bottom of each component. Add integer arguments to the flow layout constructor to increase the gap. Figure 13.10 shows the result of adding a gap of 30 points in the horizontal and 10 in the vertical directions, like this: Figure 13.10: Flow layout with a gap of 10 points. setLayout(new FlowLayout(FlowLayout.LEFT, 30, 10)); Grid Layouts Grid layouts offer more control over the placement of components inside a panel. Using a grid layout, you portion off the display area of the panel into rows and columns. Each component you then add to the panel is placed in a cell of the grid, starting from the top row and progressing through each row from left to right (here's where the order of calls to the add() method are very relevant to how the screen is laid out). To create a grid layout, indicate the number of rows and columns you want the grid to have when you create a new instance of the GridLayout class. Here's a grid layout with three rows and two columns (Figure 13.11 shows the result): file:///G|/ebooks/1575211831/ch13.htm (10 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt Figure 13.11: Six buttons displayed using a grid layout of three rows and two columns. import java.awt.*; public class GridLayoutTest extends java.applet.Applet { public void init() { setLayout(new GridLayout(3,2); add(new Button("One")); add(new Button("Two")); add(new Button("Three")); add(new Button("Four")); add(new Button("Five")); add(new Button("Six")); } } Grid layouts can also have a horizontal and vertical gap between components. To create gaps, add those pixel values: setLayout(new GridLayout(3, 3, 10, 30)); Figure 13.12 shows a grid layout with a 10-pixel horizontal gap and a 30-pixel vertical gap. Figure 13.12: A grid layout with horizontal and vertical gaps. Border Layouts Border layouts behave differently from flow and grid layouts. When you add a component to a panel that uses a border layout, you indicate its placement as a geographic direction: north, south, east, west, or center. (See Figure 13.13.) The components around all the edges are laid out with as much size as they need; the component in the center, if any, gets any space left over. Figure 13.13: Where components go in a border layout. To use a border layout, you create it as you do the other layouts; then you add the individual components with a special add() method that has two arguments. The first argument is a string indicating the position of the component within the layout, and the second is the component to add: add("North", new TextField("Title", 50)); You can also use this form of add() for the other layout managers; the string argument will just be ignored if it's not needed. Here's the code to generate the border layout shown in Figure 13.13: import java.awt.*; public class BorderLayoutTest extends java.applet.Applet { public void init() { setLayout(new BorderLayout()); add("North", new Button("One")); add("East", new Button("Two")); add("South", new Button("Three")); add("West", new Button("Four")); add("Center", new Button("Five")); add(new Button("Six")); } } Border layouts can also have horizontal and vertical gaps. Note that the north and south components extend all the way to the edge of file:///G|/ebooks/1575211831/ch13.htm (11 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt the panel, so the gap will result in less vertical space for the east, right, and center components. To add gaps to a border layout, include those pixel values in the constructor as with the other layout managers: setLayout(new BorderLayout(10, 10)); Card Layouts Card layouts behave much differently from the other layouts. When you add components to one of the other layout managers, all those components appear on the screen at once. Card layouts are used to produce slide shows of components, one at a time. If you've ever used the HyperCard program on the Macintosh, or seen dialog boxes on windows with several different tabbed pages, you've worked with the same basic idea. When you create a card layout, the components you add to the outer panel will be other container components-usually other panels. You can then use different layouts for those individual cards so that each screen has its own look. Cards, in a card layout, are different panels added one at a time and displayed one at a time. If you think of a card file, you'll get the idea; only one card can be displayed at once, but you can switch between cards. When you add each card to the panel, you can give it a name. Then, to flip between the container cards, you can use methods defined in the CardLayout class to move to a named card, move forward or back, or move to the first card or to the last card. Typically you'll have a set of buttons that call these methods to make navigating the card layout easier. Here's a simple snippet of code that creates a card layout containing three cards: setLayout(new CardLayout()); //add the cards Panel one = new Panel() add("first", one); Panel two = new Panel() add("second", two); Panel three = new Panel() add("third", three); // move around show(this, "second"); //go to the card named "second" show(this, "third"); //go to the card named "third" previous(this); //go back to the second card first(this); // got to the first card Grid Bag Layouts I've saved grid bag layouts for last because although they are the most powerful way of managing awt layout, they are also extremely complicated. Using one of the other four layout managers, it can sometimes be difficult to get the exact layout you want without doing a lot of nesting of panels within panels. Grid bags provide a more general-purpose solution. Like grid layouts, grid bag layouts allow you to arrange your components in a grid-like layout. However, grid bag layouts also allow you to control the span of individual cells in the grid, the proportions between the rows and columns, and the arrangement of components inside cells in the grid. To create a grid bag layout, you actually use two classes: GridBagLayout, which provides the overall layout manager, and GridBagConstraints, which defines the properties of each component in the grid-its placement, dimensions, alignment, and so on. It's the relationship between the grid bag, the constraints, and each component that defines the overall layout. In its most general form, creating a grid bag layout involves the following steps: q Creating a GridBagLayout object and defining it as the current layout manager, as you would any other layout manager q Creating a new instance of GridBagConstraints q Setting up the constraints for a component q Telling the layout manager about the component and its constraints q Adding the component to the panel file:///G|/ebooks/1575211831/ch13.htm (12 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt Here's some simple code that sets up the layout and then creates constraints for a single button (don't worry about the various values for the constraints; I'll cover these later on in this section): // set up layout GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints constraints = new GridBagConstraints(); setLayout(gridbag); // define constraints for the button Button b = new Button("Save"); constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 30; constraints.weighty = 30; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.CENTER; // attach constraints to layout, add button gridbag.setConstraints(b, constraints); add(b); By far, the most tedious part of this process is setting up the constraints for each component (as you can see from this example, you have to set all those constraints for every component you want to add to the panel). In addition to the tedium, constraints aren't all that easy to understand; they have many different values, many of which are interrelated, which means that changing one may have strange effects on others. Given the numerous constraints, it helps to have a plan and to deal with each kind of constraint one at a time. There are four steps I like to follow in this process. Let's walk through each of them. Step One: Design the Grid The first place to start in the grid bag layout is on paper. Sketching out your UI design beforehand-before you write even a single line of code-will help enormously in the long run with trying to figure out where everything goes. So put your editor aside for a second, pick up a piece of paper and a pencil, and let's build the grid. Figure 13.14 shows the panel layout we'll be building in this example. Figure 13.15 shows the same layout with a grid imposed on top of it. Your layout will have a grid similar to this one, with rows and columns forming individual cells. Figure 13.14: A grid bag layout. Figure 13.15: The grid bag layout from Figure 13.14, with grid imposed. Keep in mind as you draw your grid that each component must have its own cell. You cannot put more than one component into the same cell. The reverse is not true, however; one component can span multiple cells in the x or y directions (as in the OK button in the bottom row, which spans two columns). Note in Figure 13.15 that the labels and text fields have their own grids and that the button spans two column cells. While you're still working on paper, something that will help you later is to label the cells with their x and y coordinates. These aren't pixel coordinates; rather, they're cell coordinates. The top-left cell is 0,0. The next cell to the right of that in the top row is 1,0. The cell to the right of that is 2,0. Moving to the next row, the leftmost cell is 1,0, the next cell in the row is 1,1, and so on. Label your cells on the paper with these numbers; you'll need them later when we do the code for this example. Figure 13.16 shows the numbers for each of the cells in this example. Figure 13.16: The grid bag layout from Figure 13.14, with cell coordinates. Step Two: Create the Grid in Java Let's go back to Java and start implementing the layout you've just drawn on paper. Initially we're going to focus exclusively on the file:///G|/ebooks/1575211831/ch13.htm (13 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt layout-getting the grid and the proportions right. For that, it helps to not work with actual UI elements. I like to use buttons as placeholders for the actual elements in the layout until I can get everything set up right, and then change the buttons to the right elements. To cut down on the amount of typing we have to do to set up all those constraints, I'm going to start by defining a helper method that takes several values and sets the constraints for those values. buildConstraints() takes seven arguments: a GridBagConstraints object and six integers representing the GridBagConstraints instance variables gridx, gridy, gridwidth, gridheight, weightx, and weighty. You'll learn what these actually do soon; for now, here's the code to the helper method that we'll use further on in this example: void buildConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; gbc.gridheight = gh; gbc.weightx = wx; gbc.weighty = wy; } Now let's move on to the init() method, where all the layout actually occurs. Here's the basic method definition, where we'll define the GridBagLayout to be the initial layout manager and create a constraints object (an instance of GridBagConstraints): public void init() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints constraints = new GridBagConstraints(); setLayout(gridbag); constraints.fill = GridBagConstraints.BOTH; } One more small note of explanation: That last line, which sets the value of constraints.fill, will be removed (and explained) later. It's there so that the components will fill the entire cell in which they're contained, which makes it easier to see what's going on. Add it for now and you'll get a clearer idea of what it's for later. Now we'll add the button placeholders to the layout (remember, we're focusing on basic grid organization at the moment, so we'll use buttons as placeholders for the actual UI elements you'll add later). Let's start with a single button so you can get a feel for setting its constraints. This code will go into the init() method just after the setLayout line: // Name label buildConstraints(constraints, 0, 0, 1, 1, 100, 100); Button label1 = new Button("Name:"); gridbag.setConstraints(label1, constraints); add(label1); These four lines set up the constraints for an object, create a new button, attach those constraints to that button, and then add it to the panel. Note that constraints for a component are stored in the GridBagConstraints object, so the component doesn't even have to exist to set up its constraints. Now let's get down to details: Just what are the values for the constraints that we've plugged into the helper method buildConstraints? The first two integer arguments are the gridx and gridy values of the constraints. These are the cell coordinates of the cell that contains this component. Remember how you wrote these down on the paper in step one? With the cells nearly numbered on paper, all you have to do is plug in the right values. Note that if you have a component that spans multiple cells, the cell coordinates are those of the cell in the top-left corner. Here this button is in the top-left corner, so its gridx and gridy (the first two arguments to buildConstraints()) are 0 and 0, respectively. file:///G|/ebooks/1575211831/ch13.htm (14 of 39) [11/06/2000 7:46:05 PM] Day 13 -- Creating User Interfaces with the awt The second two integer arguments are the gridwidth and gridheight. These are not the pixel widths and heights of the cells; rather, they are the number of cells this component spans: gridwidth for the columns and gridheight for the rows. Here this component spans only one cell, so the values for both are 1. The last two integer arguments are for weightx and weighty. These are used to set up the proportions of the rows and columns-that is, how wide or deep they will be. Weights can become very confusing, so for now just set both values to 100. You'll deal with weights in step three. After the constraints have been built, you can attach them to an object using the setConstraints() method. setConstraints90, which is a method defined in GridBagLayout, takes two arguments: the component (here a button) and the constraints for that button. Finally, you can add the button to the panel. After you've set and assigned the constraints to one component, you can reuse that GridBagConstraints object to set up the constraints for the next object. This effectively means duplicating those four lines for each component in the grid, with different values for the buildConstraints() method. To save space, I'm just going to show you the buildConstraints() methods for the last four cells. The second cell we'll add is the one that will hold the text box for the name. The cell coordinates for this one are 1,0 (second column, first row); it too spans only one cell, and the weights (for now) are also both 100: buildConstraints(constraints, 1, 0, 1, 1, 100, 100); The next two components, which will be a label and a text field, are nearly exactly the same as the previous two; the only difference is in their cell coordinates. The password label is at 0,1 (first column, second row), and the password text field is at 1,1 (second column, second row): buildConstraints(constraints, 0, 1, 1, 1, 100, 100); buildConstraints(constraints, 1, 1, 1, 1, 100, 100); And, finally, there is the OK button, which is a component that spans two cells in the bottom row of the panel. Here the cell coordinates are the left and topmost cell where the span starts (0,2). Here, unlike the previous components, we'll set gridwidth and gridheight to be something other than 1 because this cell spans multiple columns. The gridweight is 2 (it spans two cells), and the gridheight is 1 (it spans only one row): buildConstraints(constraints, 0, 2, 2, 1, 100, 100); Got it? Those are the placement constraints for all the components that you'll add to the grid layout. You will also need to assign each component's constraints to the layout manager and then add each component to the panel. Figure 13.17 shows the result so far. Note that you're not concerned about exact proportions here, or about making sure everything lines up. What you should keep track of at this point is making sure the grid is working, that there are the right number of rows and columns, that the spans are correct, and that nothing strange is going on (cells in the wrong place, cells overlapping, that kind of thing). Figure 13.17: Grid bag layout, first pass. Step Three: Determine the Proportions The next step is to determine the proportions of the rows and columns in relation to other rows and columns. For example, in this case you'll want the labels (name and password) to take up less space than the text boxes. And you might want the OK button at the bottom to be only half the height of the two text boxes above it. You arrange the proportions of the cells within your layout using the weightx and weighty constraints. The easiest way to think of weightx and weighty is that their values are either percentages of the total width and height of the panel, or 0 if the weight or height has been set by some other cell. The values of weightx and weighty for all your components, therefore, should sum to 100. Technical Note file:///G|/ebooks/1575211831/ch13.htm (15 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Actually, the weightx and weighty values are not percentages; they're simply proportions-they can have any value whatsoever. When the proportions are calculated, all the values in a direction are summed so that each individual value is in proportion to that total (in other words, divided into the total to actually get a percentage). Because this is incredibly non-intuitive, I find it far easier to look at the weights as percentages and to make sure they all sum up to 100 to make sure it's all coming out right. So which cells get values and which cells get 0? Cells that span multiple rows or columns should always be 0 in the direction they span. Beyond that, it's simply a question of picking a cell to have a value, and then all the other cells in that row or columns should be 0. Let's look at the five calls to buildConstraints() we made in the last step: buildConstraints(constraints, buildConstraints(constraints, buildConstraints(constraints, buildConstraints(constraints, buildConstraints(constraints, 0, 1, 0, 1, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 100, 100, 100, 100, 100, 100); 100); 100); 100); 100); //name //name text //password //password text //OK button We'll be changing those last two arguments in each call to buildConstraints to be either a value or 0. Let's start with the x direction (the proportions of the columns), which is the second-to-last argument in that list. If you look back to Figure 13.15 (the picture of the panel with the grid imposed), you'll note that the second column is much larger than the first. If you were going to pick theoretical percentages for those columns, you might say that the first is 10 percent and the second is 90 percent (I'm making a guess here; that's all you need to do as well). With those two guesses, let's assign them to cells. We don't want to assign any values to the cell with the OK button because that cell spans both columns, and percentages there wouldn't work. So let's add them to the first two cells, the name label and the name text field: buildConstraints(constraints, 0, 0, 1, 1, 10, 100); //name buildConstraints(constraints, 1, 0, 1, 1, 90, 100); //name text And what about the values of the remaining two cells, the password label and text field? Because the proportions of the columns have already been set up by the name label and field, we don't have to reset them here. We'll give both of these cells and the one for the OK box 0 values: buildConstraints(constraints, 0, 1, 1, 1, 0, 100); //password buildConstraints(constraints, 1, 1, 1, 1, 0, 100); //password text buildConstraints(constraints, 0, 2, 2, 1, 0, 100); //OK button Note here that a 0 value does not mean that the cell has 0 width. These are proportions, not pixel values. A 0 simply means that the proportion has been set somewhere else; all 0 says is "stretch it to fit." Now that the totals of all the weightx constraints are 100, let's move onto the weightys. Here there are three rows; glancing over the grid we drew, it looks like the button has about 20 percent and the text fields have the rest (40 percent each). As with the x values, we only have to set the value of one cell per row (the two labels and the button), with all the other cells having a weightx of 0. Here are the final five calls to buildConstraints() with the weights in place: buildConstraints(constraints, buildConstraints(constraints, buildConstraints(constraints, buildConstraints(constraints, buildConstraints(constraints, 0, 1, 0, 1, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, Figure 13.18 shows the result with the correct proportions. Figure 13.18: Grid bag layout second pass. file:///G|/ebooks/1575211831/ch13.htm (16 of 39) [11/06/2000 7:46:06 PM] 10, 40); //name 90, 0); //name text 0, 40); //password 0, 0); //password text 0, 20); //OK button Day 13 -- Creating User Interfaces with the awt At this step, the goal here is to try to come up with some basic proportions for how the rows and cells will be spaced on the screen. You can make some basic estimates based on how big you expect the various components to be, but chances are you're going to use a lot of trial and error in this part of the process. Step Four: Add and Arrange the Components With the layout and the proportions in place, now you can replace the button placeholders with actual labels and text fields. And because you set everything up already, it should all work perfectly, right? Well, almost. Figure 13.19 shows what you get if you use the same constraints as before and replace the buttons with actual components. Figure 13.19: Grid bag layout, almost there. It's close, but it's weird. The text boxes are too tall, and the OK button stretches the width of the cell. What's missing are the constraints that arrange the components inside the cell. There are two of them: fill and anchor. The fill constraint determines, for components that can stretch in either direction (like text boxes and buttons), in which direction to stretch. fill can have one of four values, defined as class variables in the GridBagConstraints class: q GridBagConstraints.BOTH, which stretches the component to fill the cell in both directions. q GridBagConstraints.NONE, which causes the component to be displayed in its smallest size. q GridBagConstraints.HORIZONTAL, which stretches the component in the horizontal direction. q GridBagConstraints.VERTICAL, which stretches the component in the vertical direction. Note Keep in mind that this is dynamic layout. You're not going to set up the actual pixel dimensions of any components; rather, you're telling these elements in which direction they can grow given a panel that can be of any size. By default, the fill constraint for all components is NONE. So why are those text fields and labels filling the cells? If you remember way back to the start of the code for this example, I added this line to the init() method: constraints.fill = GridBagConstraints.BOTH; Now you know what it does. For the final version of this applet, you'll want to remove that line and add fill values for each independent component. The second constraint that affects how a component appears in the cell is anchor. This constraint applies only to components that aren't filling the whole cell, and it tells the awt where inside the cell to place the component. The possible values for the anchor constraint are GridBagConstraints.CENTER, which aligns the component both vertically and horizontally inside the cell, or one of eight direction values: GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, GridBagConstraints.SOUTHEAST, GridBagConstraints.SOUTH, GridBagConstraints.SOUTHWEST, GridBagConstraints.WEST, or GridBagConstraints.NORTHWEST. The default value of anchor is GridBagConstraints.CENTER. You set these constraints in the same way you did all the other ones: by changing instance variables in the GridBagConstraints object. Here you can change the definition of buildConstraints() to take two more arguments (they're ints), or you could just set them in the body of the init() method. I prefer the latter way. Be careful with defaults. Keep in mind that because you're reusing the same GridBagConstraints object for each component, there may be some values left over after you're done with one component. On the other hand, if a fill or anchor from one object is the same as the one before it, you don't have to reset that object. For this example, I'm going to make three changes to the fills and anchors of the components: q The labels will have no fill and will be aligned east (so they hug the right side of the cell) q The text fields will be filled horizontally (so they start one line high, but stretch to the width of the cell) q The button will have no fill and will be center aligned I'm not going to show you all the code for this here; the full code for the example is at the end of this section. You can see the changes I've made there. file:///G|/ebooks/1575211831/ch13.htm (17 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Step Five: Futz with It I added this step to the list because in my own experimentation with grid bag layouts, I found that even by following all the steps, usually the resulting layout wasn't quite right, and I needed to do a considerable amount of tinkering and playing with various values of the constraints in order to get it to come out right (that's what futzing means) There's nothing wrong with that; the goal of the previous three steps was to get things fairly close to their final positions, not to come out with a perfect layout each and every time. The Code Listing 13.1 shows the complete code for the panel layout we've been building up in this section. If you had trouble following the discussion up to this point, you might find it useful to go through this code line by line to make sure you understand the various bits. Listing 13.1. The panel with the final grid bag layout. 1:import java.awt.*; 2: 3:public class GridBagTestFinal extends java.applet.Applet { 4: 5: void buildConstraints(GridBagConstraints gbc, int gx, int gy, 6: int gw, int gh, 7: int wx, int wy) { 8: gbc.gridx = gx; 9: gbc.gridy = gy; 10: gbc.gridwidth = gw; 11: gbc.gridheight = gh; 12: gbc.weightx = wx; 13: gbc.weighty = wy; 14: } 15: 16: public void init() { 17: GridBagLayout gridbag = new GridBagLayout(); 18: GridBagConstraints constraints = new GridBagConstraints(); 19: setLayout(gridbag); 20: 21: // Name label 22: buildConstraints(constraints, 0, 0, 1, 1, 10, 40); 23: constraints.fill = GridBagConstraints.NONE; 24: constraints.anchor = GridBagConstraints.EAST; 25: Label label1 = new Label("Name:", Label.LEFT); 26: gridbag.setConstraints(label1, constraints); 27: add(label1); 28: 29: // Name text field 30: buildConstraints(constraints, 1, 0, 1, 1, 90, 0); 31: constraints.fill = GridBagConstraints.HORIZONTAL; 32: TextField tfname = new TextField(); 33: gridbag.setConstraints(tfname, constraints); 34: add(tfname); 35: 36: // password label 37: buildConstraints(constraints, 0, 1, 1, 1, 0, 40); 38: constraints.fill = GridBagConstraints.NONE; 39: constraints.anchor = GridBagConstraints.EAST; 40: Label label2 = new Label("Password:", Label.LEFT); 41: gridbag.setConstraints(label2, constraints); 42: add(label2); 43: file:///G|/ebooks/1575211831/ch13.htm (18 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: } 60:} // password text field buildConstraints(constraints, 1, 1, 1, 1, 0, 0); constraints.fill = GridBagConstraints.HORIZONTAL; TextField tfpass = new TextField(); tfpass.setEchoCharacter('*'); gridbag.setConstraints(tfpass, constraints); add(tfpass); // OK Button buildConstraints(constraints, 0, 2, 2, 1, 0, 20); constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.CENTER; Button okb = new Button("OK"); gridbag.setConstraints(okb, constraints); add(okb); ipadx and ipady Before finishing up with grid bag layouts (isn't it over yet?), there are a two more constraints that deserve mentioning: ipadx and ipady. These two constraints control the padding-that is, the extra space around an individual component. By default, no components have extra space around them (which is easiest to see in components that fill their cells). ipadx adds space to either side of the component, and ipady adds it above and below. Insets Horizontal and vertical gap, created when you create a new layout manager (using ipadx and ipady in grid bag layouts), are used to determine the amount of space between components in a panel. Insets, however, are used to determine the amount of space around the panel itself. The Insets class includes values for the top, bottom, left, and right insets, which are then used when the panel itself is drawn. Insets determine the amount of space between the edges of a panel and that panel's components. To include an inset, override the insets() method in your class (your Applet class or other class that serves as a panel). Inside the insets() method, create a new Insets object, where the constructor to the Insets class takes four integer values representing the insets on the top, left, bottom, and right of the panel. The insets() method should then return that Insets object. Here's some code to add insets for a grid layout, 10 to the top and bottom, and 30 to the left and right. (Figure 13.20 shows the inset): Figure 13.20: A panel with insets of 10 pixels on the top and bottom and 30 pixels to the left and right. public Insets insets() { return new Insets(10, 30, 10, 30); } The arguments to the Insets constructor provide pixel insets for the top, bottom, left, and right edges of the panel, respectively. This particular example provides an inset of 10 pixels on all four sides of the panel. Handling UI Actions and Events If you stopped reading today's lesson right now, you could go out and create an applet that had lots of little UI components, nicely laid out on the screen with the proper layout manager, gap, and insets. If you did stop right here, however, your applet would be really dull, because none of your UI components would actually do anything when they were pressed, typed into, or selected. For your UI components to do something when they are activated, you need to hook up the UI's action with an operation. Actions are a form of event, and testing for an action by a UI component involves event management. Everything you learned yesterday about file:///G|/ebooks/1575211831/ch13.htm (19 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt events will come in handy here. UI actions are events that occur when a UI component is activated-pressed, selected, typed into, and so on. To intercept an action event generated by any UI component, you define an action() method in your applet or class: public boolean action(Event evt, Object arg) { ... } The action() method should look similar to the basic mouse and keyboard event methods. Like those methods, it gets passed the event object that represents this event. It also gets an extra object (in this code, the parameter arg), which can be of any class type. What kind of object that second argument to the action method is depends on the UI component that's generating the action. The basic definition is that it's any arbitrary argument-when a component generates an event, it can pass along any extra information that might be useful for you to use in processing that action. All the basic UI components (except for labels, which have no action) have different actions and arguments: q Buttons create actions when they are pressed and released with the mouse, and a button's extra argument is the label string of that button. q Check boxes, both exclusive and nonexclusive, generate actions when a box is checked. The extra argument is always true. q Choice menus generate an action when a menu item is selected, and the extra argument is the label string of that item. q Text fields create actions when the user presses Return or Enter inside that text field. Note that if the user tabs to a different text field or uses the mouse to change the input focus, an action is not generated. Pressing Return or Enter is the only thing that triggers the action. Note that with actions, unlike with ordinary events, you can have many different kinds of objects generating the action event, as opposed to a single movement (a mouse press) generating a single event (such as a mouseDown). To deal with those different UI components and the actions they generate, you have to test for the type of object that sent/created the event in the first place inside the body of your action() method. That object is stored in the event's target instance variable, and you can use the instanceof operator to find out what kind of UI component sent it: public boolean action(Event evt, Object arg) { if (evt.target instanceof TextField) return handleText(evt.target); else if (evt.target instanceof Choice) return handleChoice(arg); ... } Although you can handle UI actions in the body of the action() method, it's much more common simply to define a special method in your action() method and call that method instead. Here, there are two special methods: one to handle the action on the text field (handleText()) and one to handle the action on the choice menu (handleChoice()). Depending on the action you want to handle, you may also want to pass on the argument from the action, the UI component that sent it, or any other information that the event might contain. As with the other event methods, action() returns a boolean value. As with all the event methods, you should return true if action() itself deals with the method, or false if it passes the method on somewhere else (or ignores it). Listing 13.2 shows a simple applet that has five buttons labeled with colors. The action() method tests for a button action and then passes control to a method called changeColor(), which changes the background color of the applet based on which button was pressed (see Figure 13.21 to see the applet in action). Figure 13.21: The ButtonAction applet. Listing 13.2. The ButtonActionsTest applet. 1:import java.awt.*; 2: 3:public class ButtonActionsTest extends java.applet.Applet { file:///G|/ebooks/1575211831/ch13.htm (20 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:} public void init() { setBackground(Color.white); add(new add(new add(new add(new add(new Button("Red")); Button("Blue")); Button("Green")); Button("White")); Button("Black")); } public boolean action(Event evt, Object arg) { if (evt.target instanceof Button) { changeColor((String)arg); return true; } else return false; } void changeColor(String bname) { if (bname.equals("Red")) setBackground(Color.red); else if (bname.equals("Blue")) setBackground(Color.blue); else if (bname.equals("Green")) setBackground(Color.green); else if (bname.equals("White")) setBackground(Color.white); else setBackground(Color.black); repaint(); } As with most awt-based applets, this one starts with an init() method that initializes the applet's state and creates and adds components to the layout. The init() method defined in lines 8 through 13 here sets the applet's background color to white and creates five new buttons with color labels. Here we'll use the default layout manager, which is a FlowLayout. The buttons will appear all in a row at the top of the screen. With the buttons in place, the second step is to attach actions to those buttons. The action() method, defined in lines 15 through 20, does this. The first thing to check is to make sure it's a button action that's been generated (line 16) and, if so, to pass the extra argument (cast to a string) to the changeColor() method, which will do all the work to change the color. If the event is indeed a button action, we'll return true to intercept that event. Otherwise, we'll return false and let some other component handle the event. The changeColor() method is where all the work goes on. Here we test for each of the button labels in turn to see which button it was that was pressed and to set the background to the appropriate color. A final repaint at the end does the actual change (setting the background color does not automatically trigger a repaint; you'll have to do it yourself). Nesting Panels and Components Adding UI components to individual panels or applets is fun, but working with the awt begins to turn into lots of fun when you start working with nested panels. By nesting different panels inside your applet, and panels inside those panels, you can create different layouts for different parts of the overall applet area, isolate background and foreground colors and fonts to individual parts of an applet, and manage the design of your UI components individually and in distinct groups. The more complex the layout of your applet, the more likely you're going to want to use nested panels. Nested Panels Panels, as you've already learned, are components that can be actually displayed onscreen; Panel's superclass Container provides the generic behavior for holding other components inside it. The Applet class, from which your applets all inherit, is a subclass of Panel. To nest other panels inside an applet, you merely create a new panel and add it to the applet, just as you would file:///G|/ebooks/1575211831/ch13.htm (21 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt add any other UI component: setLayout(new GridLayout(1, 2, 10, 10)); Panel panel1 = new Panel(); Panel panel2 = new Panel(); add(panel1); add(panel2); You can then set up an independent layout for those subpanels and add awt components to them (including still more subpanels) by calling the add() method in the appropriate panel: panel1.setLayout(new FlowLayout()); panel1.add(new Button("Up")); panel1.add(new Button("Down")); Although you can do all this in a single class, it's common in graphical applets and applications that make heavy use of subpanels to factor out the layout and behavior of the subpanels into separate classes and to communicate between the panels by using methods. You'll look at an extensive example of this later in today's lesson in the section "A Complete Example: RGB-to-HSB Converter." Events and Nested Panels When you create applets with nested panels, those panels form a hierarchy from the outermost panel (the applet, usually) to the innermost UI component. This hierarchy is important to how each component in the interface interacts with other components; for example, the component hierarchy determines the order in which those components are painted to the screen. More importantly, however, the hierarchy also affects event handling, particularly for user-input events such as mouse and keyboard events. Events are received by the innermost component in the component hierarchy and passed up the chain to the applet's panel (or to the root window in Java applications). Suppose, for example, that you have an applet with a subpanel that can handle mouse events (using the mouseDown() and mouseUp() methods), and that panel contains a button. Clicking the button means that the button receives the event before the panel does; if the button isn't interested in that mouseDown(), the event gets passed to the panel, which can then process it or pass it further up the hierarchy. Remember the discussion about the basic event methods yesterday? You learned that the basic event methods all return boolean values. Those boolean values become important when you're talking about handling events or passing them on. An event-handling method, whether it is the set of basic event methods or the more generic handleEvent(), can do one of three things, given any random event: q Ignore the event entirely, if the event doesn't match whatever criteria the event-handling method set-for example, the mouseDown wasn't in the right area, or the action wasn't a button action. If this is the case, the event handler should return false so the event is passed up the hierarchy until a component processes it (or it is ignored altogether). q Intercept the event, process it, and return true. In this case, the event stops with that event method. q Intercept the method, process it, and pass it on to another, more specific event handler-for example, as handleEvent passes events onto mouseDown(). More UI Components After you master the basic UI components and how to add them to panels, organize their layout, and manage their events, you can add more UI components. In this section, you'll learn about text areas, scrolling lists, scrollbars, and canvases. Note that most of the components in this section do not produce actions, so you can't use the action() method to handle their behavior. Instead, you have to use a generic handleEvent() method to test for specific events that these UI components generate. You'll learn more about this in the next section. file:///G|/ebooks/1575211831/ch13.htm (22 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Text Areas Text areas are like text fields, except they have more functionality for handling large amounts of text. Because text fields are limited in size and don't scroll, they are better for one-line responses and simple data entry; text areas can be any given width and height and have scrollbars by default, so you can deal with larger amounts of text more easily. Text areas are larger, scrollable text-entry components. Whereas text fields only provide one line of text, text areas can hold any amount of editable text. To create a text area, use one of the following constructors: q TextArea() creates an empty text area 0 rows long and 0 characters wide (the text area will be automatically resized based on the layout manager). q TextArea(int, int) creates an empty text area with the given number of rows and columns (characters). q TextArea(String) creates a text area displaying the given string, which will be sized according to the current layout manager. q TextArea(String, int, int) creates a text area displaying the given string and with the given dimensions. Figure 13.22 shows a simple text area generated from the following code: Figure 13.22: A text area. import java.awt.*; public class TextAreaTest extends java.applet.Applet { public void init() { String str = "Once upon a midnight dreary, while I pondered, weak and weary,\n" + "Over many a quaint and curious volume of forgotten lore,\n" + "While I nodded, nearly napping, suddenly there came a tapping,\n" + "As of some one gently rapping, rapping at my chamber door.\n" + "\"'Tis some visitor,\" I muttered, \"tapping at my chamber door-\n" + "Only this, and nothing more.\"\n\n"; // more text deleted for space add(new TextArea(str,10,50)); } } Both text areas and text fields inherit from the TextComponent class, so a lot of the behavior for text fields (particularly getting and setting text and selections) is usable on text areas as well (refer to Table 13.4). Text areas also have a number of their own methods that you may find useful. Table 13.5 shows a sampling of those methods. Table 13.5. Text area methods. Method getColumns() Action Returns the width of the text area, in characters or columns Returns the number of rows in the text area getRows() (not the number of rows of text that the text area contains) insertText(String, int) Inserts the string at the given position in the text (text positions start at 0) Replaces the text between the given integer replaceText(String, positions with the new string int, int) file:///G|/ebooks/1575211831/ch13.htm (23 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Scrolling Lists Remember the choice menu, with which you could choose one of several different options? A scrolling list is functionally similar to a choice menu in that it lets you pick several options from a list, but scrolling lists differ in two significant ways: q Scrolling lists are not pop-up menus. They're displayed as a list of items from which you can choose one or more items. If the number of items is larger than the list box, a scrollbar is automatically provided so that you can see the other items. q You can choose more than one item in the list (if the list has been defined to allow it). Scrolling lists provide a menu of items that can be selected or deselected. Unlike choice menus, scrolling lists are not pop-up menus and can be defined to allow multiple selections. To create a scrolling list, create an instance of the List class and then add individual items to that list. The List class has two constructors: q List() creates an empty scrolling list that enables only one selection at a time. q List(int, boolean) creates a scrolling list with the given number of visible lines on the screen (you're unlimited as to the number of actual items you can add to the list). The boolean argument indicates whether this list enables multiple selections (true) or not (false). After creating a List object, add items to it using the addItem() method and then add the list itself to the panel that contains it. Here's an example that creates a list five items high that allows multiple selections (the result of this code is shown in Figure 13.23): Figure 13.23: A scrolling list. import java.awt.*; public class ListsTest extends java.applet.Applet { public void init() { List lst = new List(5, true); lst.addItem("Hamlet"); lst.addItem("Claudius"); lst.addItem("Gertrude"); lst.addItem("Polonius"); lst.addItem("Horatio"); lst.addItem("Laertes"); lst.addItem("Ophelia"); add(lst); } } Scrolling lists generate actions when the user double-clicks a list item (single-clicking generates a LIST_SELECT or LIST_DESELECT event ID; you'll learn more about these in the section "More UI Events"). A scrolling list action has the argument of the string of the item that was double-clicked. Table 13.6 shows some of the methods available to scrolling lists. See the API documentation for a complete set. Table 13.6. Scrolling list methods. Method getItem(int) countItems() getSelectedIndex() Action Returns the string item at the given position Returns the number of items in the menu Returns the index position of the item that's selected (used for lists that allow only single selections) getSelectedIndexes() Returns an array of index positions (used for lists that allow multiple selections) file:///G|/ebooks/1575211831/ch13.htm (24 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt getSelectedItem() getSelectedItems() select(int) select(String) Returns the currently selected item as a string Returns an array of strings containing all the selected items Selects the item at the given position Selects the item with that string Scrollbars and Sliders Text areas and scrolling lists come with their own scrollbars, which are built into those UI components and enable you to manage both the body of the area or the list and its scrollbar as a single unit. You can also create individual scrollbars, or sliders, to manipulate a range of values. Scrollbars are used to select a value between a maximum and a minimum value. To change the current value of that scrollbar, you can use three different parts of the scrollbar (seeFigure 13.24): Figure 13.24: Scrollbar parts. q q q Arrows on either end, which increment or decrement the values by some small unit (1 by default). A range in the middle, which increments or decrements the value by a larger amount (10 by default). A box in the middle, often called an elevator or thumb, whose position shows where in the range of values the current value is located. Moving this box with the mouse causes an absolute change in the value, based on the position of the box within the scrollbar. Choosing any of these visual elements causes a change in the scrollbar's value; you don't have to update anything or handle any events. All you have to do is give the scrollbar a maximum and minimum, and Java will handle the rest. A scrollbar is a visual UI element that allows you to choose a value between some minimum and some maximum. Scrollbars are sometimes called sliders. To create a scrollbar, you can use one of three constructors: q Scrollbar() creates a scrollbar with its initial maximum and minimum values both 0, in a vertical orientation. q Scrollbar(int) creates a scrollbar with its initial maximum and minimum values both 0. The argument represents an orientation, for which you can use the class variables Scrollbar.HORIZONTAL and Scrollbar.VERTICAL. q Scrollbar(int, int, int, int, int) creates a scrollbar with the following arguments (each one is an integer, and they must be presented in this order): q The first argument is the orientation of the scrollbar: Scrollbar.HORIZONTAL and Scrollbar.VERTICAL. q The second argument is the initial value of the scrollbar, which should be a value between the scrollbar's maximum and minimum values. q The third argument is the overall width (or height, depending on the orientation) of the scrollbar's box. In user-interface design, a larger box implies that a larger amount of the total range is currently showing (applies best to things such as windows and text areas). q The fourth and fifth arguments are the minimum and maximum values for the scrollbar. Here's a simple example of a scrollbar that increments a single value (see Figure 13.25). The label to the left of the scrollbar is updated each time the scrollbar's value changes: Figure 13.25: A scrollbar. import java.awt.*; public class SliderTest extends java.applet.Applet { Label l; public void init() { setLayout(new GridLayout(1,2)); l = new Label("0", Label.CENTER); add(l); file:///G|/ebooks/1575211831/ch13.htm (25 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt add(new Scrollbar(Scrollbar.HORIZONTAL,0,0,1,100)); } public Insets insets() { return new Insets(15,15,15,15); } public boolean handleEvent(Event evt) { if (evt.target instanceof Scrollbar) { int v = ((Scrollbar)evt.target).getValue(); l.setText(String.valueOf(v)); repaint(); return true; } else return false; } } The Scrollbar class provides several methods for managing the values within scrollbars. (See Table 13.7.) Table 13.7. Scrollbar methods. Method getMaximum() getMinimum() getOrientation() getValue() setValue(int) setLineIncrement(int inc) getLineIncrement() setPageIncrement(int inc) getPageIncrement() Action Returns the maximum value. Returns the minimum value. Returns the orientation of this scrollbar: 0 is Scrollbar.HORIZONTAL; 1 is Scrollbar.VERTICAL. Returns the scrollbar's current value. Sets the current value of the scrollbar. Change the increment for how far to scroll when the endpoints of the scrollbar are selected. The default is 1. Returns the increment for how far to scroll when the endpoints of the scrollbar are selected. Change the increment for how far to scroll when the inside range of the scrollbar is selected. The default is 10. Returns the increment for how far to scroll when the inside range of the scrollbar is selected. Canvases Although you can draw on most awt components such as panels using the graphics methods you learned about on Day 11, "More Animation, Images, and Sound," canvases do little except let you draw on them. They can't contain other components, but they can accept events, and you can create animation and display images on them. If you have a panel that doesn't need to do anything except display images or animation, a canvas would make a lighter-weight surface than a panel would. A canvas is a component that you can draw on. To create a canvas, use the Canvas class and add it to a panel as you would any other component: Canvas can = new Canvas(); add(can); file:///G|/ebooks/1575211831/ch13.htm (26 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt More UI Events Yesterday, you learned about some basic event types that are generated from user input to the mouse or the keyboard. These event types are stored in the Event object as the event ID, and can be tested for in the body of a handleEvent() method by using class variables defined in Event. For many basic events, such as mouseDown() and keyDown(), you can define methods for those events to handle the event directly. You learned a similar mechanism today for UI actions where creating an action() method handled a specific action generated by a UI component. The most general way of managing events, however, continues to be the handleEvent() method. For events relating to scrollbars and scrolling lists, the only way to intercept these events is to override handleEvent(). To intercept a specific event, test for that event's ID. The available IDs are defined as class variables in the Event class, so you can test them by name. You learned about some of the basic events yesterday; Table 13.8 shows additional events that may be useful to you for the components you've learned about today (or that you might find useful in general). Table 13.8. Additional events. Event ID ACTION_EVENT GOT_FOCUS LOST_FOCUS What It Represents Generated when a UI component action occurs Generated when the user clicks inside a text area Generated when the user clicks anywhere outside a text area (after being inside one) Generated when an item in a scrolling list is deselected LIST_DESELECT Generated when an item in a scrolling list is selected LIST_SELECT SCROLL_ABSOLUTE Generated when a scrollbar's box has been moved SCROLL_LINE_DOWN Generated when a scrollbar's bottom or left endpoint (button) is selected Generated when a scrollbar's top or right endpoint SCROLL_LINE_UP (button) is selected SCROLL_PAGE_DOWN Generated when the scrollbar's field below (or to the left of) the box is selected Generated when the scrollbar's field above (or to the SCROLL_PAGE_UP right of) the box is selected Fun with Components The Component class is the root of all the awt objects: all the UI elements, panels, canvases, even applets. Just about everything you can display, lay out, change the color of, draw to, or interact with using events in the awt is a component. Components have a set of methods that allow you to modify their appearance or change their behavior. You've seen the use of a few of these methods already (setBackground(), setFont, size()), applied specifically to applets. But the methods defined in Component can be used with any component, allowing you to modify the appearance or the behavior of just about any element in your program. You can also create custom components (classes that inherit from Panel or Canvas) to make your own special awt elements or user interface widgets. Table 13.9 summarizes some of the methods you can use with individual components. For more methods, check out the Java API documentation for the class Component. The JDK 1.0.2 documentation is online at http://java.sun.com:80/products/JDK/CurrentRelease/api/ Table 13.9. Component methods. Returns a Color object representing the component's background color. setBackground(Color) Sets the component's background color. getBackground() file:///G|/ebooks/1575211831/ch13.htm (27 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Returns a Color object representing the component's current foreground color. setForeground(Color) Sets the component's foreground color getFont() Returns a Font object representing the component's current font. Changes the component's current font. setFont(Font) size() Returns a Dimension object representing the component's current size. You can then get to the individual width and height using size().width() and size().height(). The component's smallest possible size as a minimumSize() Dimension object. minimumSize() is usually only used by layout managers to determine how small it can draw a component; if you create a custom component you'll want to override this method to return the minimum size of that component. The component's preferred size (usually equal to or preferredSize() larger than the component's minimumSize()) as a Dimension object. Changes the size of the applet to be the current size. resize(Dimension) For custom components you'll want to also call validate() after resizing the applet so that the layout can be redrawn. inside(x, y) Returns true if the given x and y coordinates are inside the component. Hides the component. Hidden components do not hide() show up onscreen. Shows a component previously hidden. show() isVisible() Returns true or false depending on whether this component is visible (not hidden). Disables the component-that is, stops generating disable() events. Disabled components cannot be pressed, selected from, typed into, and so on. Enables a previously disabled object. enable() isEnabled() Returns true or false depending on whether the component is enabled. getForeground() A Complete Example: RGB-to-HSB Converter Let's take a break here from theory and smaller examples to create a larger example that puts together much of what you've learned so far. The following applet example demonstrates layouts, nesting panels, creating user-interface components, and catching and handling actions, as well as using multiple classes to put together a single applet. In short, it's the most complex applet you've created so far. Figure 13.26 shows the applet you'll be creating in this example. The ColorTest applet enables you to pick colors based on RGB (red, green, and blue) and HSB (hue, saturation, and brightness) values. Figure 13.26: The ColorTest applet. Note file:///G|/ebooks/1575211831/ch13.htm (28 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt A very quick summary in case you're not familiar with basic color theory: RGB color defines a color by its red, green, and blue values; some combination of these values can produce any color in the spectrum (red, green, and blue are called additive colors; that's how your monitor and your TV represent different colors). HSB stands for hue, saturation, and brightness and is a different way of indicating color. Hue is the actual color in the spectrum you're representing (think of it as values along a color wheel). Saturation is the amount of that color; low saturation results in pastels; high-saturation colors are more vibrant and "colorful." Brightness, finally, is the lightness or darkness of the color. No brightness is black; full brightness is white. A single color can be represented either by its RGB values or by its HSB values, and there are mathematical algorithms to convert between them. The ColorTest applet provides a graphical converter between the two. The ColorTest applet has three main parts: a colored box on the left side and two groups of text fields on the right. The first group indicates RGB values; the right, HSB. By changing any of the values in any of the text boxes, the colored box is updated to the new color, as are the values in the other group of text boxes. Note If you try this applet, be aware that you have to press Enter or Return after changing a number for the updating to occur. Using the Tab key to move between text fields or clicking with the mouse will not cause the applet to update. This applet uses two classes: q ColorTest, which inherits from Applet. This is the controlling class for the applet itself. q ColorControls, which inherits from Panel. You'll create this class to represent a group of three text fields and to handle actions from those text fields. Two instances of this class, one for the RGB values and one for the HSB ones, will be created and added to the applet. Let's work through this step by step, because it's very complicated and can get confusing. All the code for this applet will be shown at the end of this section. Designing and Creating the Applet Layout The best way to start creating an applet that uses awt components is to worry about the layout first and then worry about the functionality. When dealing with the layout, you should start with the outermost panel first and work inward. Making a sketch of your UI design can help you figure out how to organize the panels inside your applet or window to best take advantage of layout and space. Paper designs are helpful even when you're not using grid bag layouts, but doubly so when you are (we'll be using a simple grid layout for this applet). Figure 13.27 shows the ColorTest applet with a grid drawn over it so that you can get an idea of how the panels and embedded panels work. Figure 13.27: The ColorTest applet panels and components. Let's start with the outermost panel-the applet itself. This panel has three parts: the color box on the left, the RGB text fields in the middle, and the HSB fields on the right. Because the outermost panel is the applet itself, your ColorTest class will be the applet class and will inherit from Applet. You'll also import the awt classes here (note that because you use so many of them in this program, it's easiest to just import the entire package): import java.awt.*; file:///G|/ebooks/1575211831/ch13.htm (29 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt public class ColorTest extends java.applet.Applet { ... } This applet has three main things to keep track of: the color box and the two subpanels. The two subpanels each refer to different things, but they're essentially the same panel and behave in the same ways. Rather than duplicate a lot of code here in this class, this is a perfect opportunity to create another class just for the subpanels, use instances of that class here in the applet, and communicate between everything using methods. In a bit we'll define that new class, called ColorControls. For now, however, we know we need to keep a handle to all three parts of the applet so you can update them when they change. So let's create three instance variables: one of type Canvas for the color box, and the other two of type ColorControls for the control panels: ColorControls RGBcontrols, HSBcontrols; Canvas swatch; Now we'll move onto the init() method, where all the basic initialization and layout of the applet takes place. There are three steps to initializing this applet: 1. Create the layout for the big parts of the panel. Although a flow layout would work, a grid layout with one row and three columns is a much better idea. 2. Create and initialize the three components of this applet: a canvas for the color box and two subpanels for the text fields. 3. Add those components to the applet. Step one is the layout. Let's use a grid layout and a gap of 10 points to separate each of the components: setLayout(new GridLayout(1, 3, 5, 15)); Step two is creating the components-the canvas first. You have an instance variable to hold that one. Here we'll create the canvas and initialize its background to black: swatch = new Canvas(); swatch.setBackground(Color.black); You need to create two instances of your as-of-yet nonexistent ColorControls panels here as well, but because we haven't created the class yet we don't know what the constructors to that class will look like. Let's put in some placeholder constructors here; we'll fill in the details later: RGBcontrols = new ColorControls(...) HSBcontrols = new ColorControls(...); Step three is adding all three components to the applet panel: add(swatch); add(RGBcontrols); add(HSBcontrols); While you're working on layout, let's add insets for the applet: 10 points along all the edges: public Insets insets() { return new Insets(10, 10, 10, 10); } Got it so far? At this point you have three instance variables, an init() method with two incomplete constructors, and an insets() method in your ColorTest class. Let's move on now to creating the subpanel layout in the ColorControls class so we can fill in those constructors and finish up the layout. file:///G|/ebooks/1575211831/ch13.htm (30 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Defining the Subpanels The ColorControls class will have behavior for laying out and handling the subpanels that represent the RGB and HSB values for the color. ColorControls doesn't need to be a subclass of Applet because it isn't actually an applet; it's just a panel. Define it to inherit from Panel: import java.awt.* class ColorControls extends Panel { ... } Note I've put the ColorControls source code into its own file, called ColorControls.java. However, you can put the ColorControls class in the same file as the ColorTest class. Up to this point, you've only defined one class per file, with the filename the same name as the class. In Java you can have multiple class definitions in a file as long as only one of those classes is declared public (and the name of the source file is the same as that public class). In this case, the ColorTest class is public (it's an applet, so it has to be), but the ColorControls class isn't public, so it can be in the same source file. When you compile the file, Java will create the appropriate multiple class files for each class definition. You'll learn more about public classes on Day 15, "Modifiers, Access Control, and Class Design," and Day 16, "Packages and Interfaces." In general, however, I prefer to use separate source files for my classes. It makes it easier for me to find the source for a particular class because I don't have to remember which file I defined it in. The ColorControls class will need a number of instance variables so that information from the panel can get back to the applet. The first of these instance variables is a hook back up to the applet class that contains this panel. Because it's the outer applet class that controls the updating of each panel, this panel will need a way to tell the applet that something has changed. And to call a method in that applet, you need a reference to that object. So, instance variable number one is a reference an instance of the class ColorTest: ColorTest applet; If you figure that the applet class is the one that's going to be updating everything, that class if going to be interested in the individual text fields in this subpanel. We'll create instance variables for those text fields: TextField tfield1, tfield2, tfield3; Now let's move on to the constructor for this class. Because this class isn't an applet, we won't use init() to initialize it; instead we'll use a constructor method. Inside the constructor you'll do much of what you did inside init(): create the layout for the subpanel, create the text fields, and add them to the panel. The goal here is to make the ColorControls class generic enough so that you can use it for both the RGB fields and the HSB fields. Those two panels differ in only one respect: the labels for the text. That's three values to get before you can create the object. You can pass those three values in through the constructors in ColorTest. You also need one more: that reference to the enclosing applet, which you can get from the constructor as well. You now have four arguments to the basic constructor for the ColorControls class. Here's the signature for that constructor: file:///G|/ebooks/1575211831/ch13.htm (31 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt ColorControls(ColorTest parent, String l1, String l2, String l3) { } Let's start this constructor by first setting the value of parent to the applet instance variable: applet = parent; Next, create the layout for this panel. You can also use a grid layout for these subpanels, as you did for the applet panel, but this time the grid will have three rows (one for each of the text field and label pairs) and two columns (one for the labels and one for the fields). We'll also define a 10-point gap between the components in the grid: setLayout(new GridLayout(3,2,10,10)); Now we can create and add the components to the panel. First, we'll create the text field objects (initialized to the string "0"), and assign them to the appropriate instance variables: tfield1 = new TextField("0"); tfield2 = new TextField("0"); tfield3 = new TextField("0"); Now we'll add those fields and the appropriate labels to the panel, using the remaining three parameters to the constructor as the text for the labels: add(new Label(l1, Label.RIGHT)); add(tfield1); add(new Label(l2, Label.RIGHT)); add(tfield2); add(new Label(l3, Label.RIGHT)); add(tfield3); That finishes up the constructor for the subpanel class ColorControls. Are we done with the layout? Not quite. We'll also add an inset around the subpanel-only on the top and bottom edges-to tinker the layout. Add the inset here as you did in the ColorTest class, using the insets() method: public Insets insets() { return new Insets(10, 10, 0, 0); } You're almost there. You have 98 percent of the basic structure in place and ready to go, but there's one step left: going back to ColorTest and fixing those placeholder constructors for the subpanel so they match the actual constructors for ColorControls. The constructor for ColorControls that we just created now has four arguments: the ColorTest object and three labels (strings). Remember back to when we created the init() method for ColorTest: We added two placeholders for creating new ColorControls objects; we'll replace those placeholders with the correct versions now. Make sure you add the four arguments that constructor needs to work: the ColorTest object and three strings. To pass the ColorTest object to those constructors, we can use the this keyword: RGBcontrols = new ColorControls(this, "Red", "Green", "Blue"); HSBcontrols = new ColorControls(this, "Hue", "Saturation", "Brightness"); Note file:///G|/ebooks/1575211831/ch13.htm (32 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt For the initial values of all the text fields in this example, I used the number 0 (actually, the string "0"). For the color black, both the RGB and the HSB values are 0, which is why I can make this assumption. If you wanted to initialize the applet to be some other color, you might want to rewrite the ColorControls class to use initializer values as well as to initialize labels. This way made for a shorter example. Handling the Actions With the layout done, its time to set up event handling and updating between the various components so that when the user interacts with the applet, the applet can respond. The action of this applet occurs when the user changes a value in any of the text fields and presses Enter. By causing an action in a text field, the color changes, the color box updates to the new color, and the values of the fields in the opposite subpanel change to reflect the new color. The ColorTest class is responsible for actually doing the updating because it keeps track of all the subpanels. Because the actual event occurs in the subpanel, however, you'll need to track and intercept those events in that subpanel using the action() method in the ColorControls class: public boolean action(Event evt, Object arg) { if (evt.target instanceof TextField) { applet.update(this); return true; } else return false; } In the action() method, you test to make sure the action was indeed generated by a text field (because there are only text fields available, that's the only action you'll get, but it's a good idea to test for it anyhow). If there was indeed a text field action, we'll call a method to update all the subpanels. That method, which we'll call update(), is defined in the enclosing class, so we'll call it using the object stored in the applet instance variable (and pass along a reference to the panel so that the applet can get at our values). And, finally, we'll return either true or false so that other actions that might occur on this applet can be passed along to enclosing panels or components. Updating the Result Now comes the hard part: actually doing the updating based on the new values of whatever text field was changed. For this, you define the update() method in the ColorTest class. This update() method takes a single argument-the ColorControls instance that contains the changed value (you get that argument from the action() method in the ColorControls object). Note Won't this update() method interfere with the system's update() method? Nope. Remember, methods can have the same name, but different signatures and definitions. Because this update() has a single argument of type ColorControls, it doesn't interfere with the other version of update(). Normally, all methods called update() should mean basically the same thing; it's not true here, but it's only an example. The update() method is responsible for updating all the panels in the applet. To know which panel to update, you need to know which panel changed. You can find out by testing to see whether the argument you got passed from the panel is the same as the subpanels you have stored in the RGBcontrols and HSBcontrols instance variables: void update(ColorControls controlPanel) { if (controlPanel == RGBcontrols) { // RGB has changed, update HSB file:///G|/ebooks/1575211831/ch13.htm (33 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt ... } else { ... } // HSB has changed, update RGB } This test is the heart of the update() method. Let's start with that first case-a number has been changed in the RGB text fields. So now, based on those new RGB values, you have to generate a new Color object and update the values on the HSB panel. To reduce some typing, you create a few local variables to hold some basic values. In particular, the values of the text fields are strings whose values you can get to using the getText() method defined in the TextField objects of the ColorControls object. Because most of the time in this method we'll want to deal with those values as integers, we'll get those string values, convert them to integers, and store them in local variables (value1, value2, value3). Here's the code to do this (it looks more complicated than it actually is): int value1 = Integer.parseInt(controlPanel.tfield1.getText()); int value2 = Integer.parseInt(controlPanel.tfield2.getText()); int value3 = Integer.parseInt(controlPanel.tfield3.getText()); While we're here defining local variables, we'll also need one for the new Color object: Color c; OK. Let's assume one of the text fields in the RGB side of the applet has changed and add the code to the if part of the update() method. We'll need to create a new Color object and update the HSB side of the panel. That first part is easy; given the three RGB values, you can create a new Color object using those as arguments to the constructor: c = new Color(value1, value2, value3); Note This part of the example isn't very robust; it assumes that the user has indeed entered integers from 0 to 255 into the text fields. A better version of this would test to make sure that no data-entry errors had occurred (I was trying to keep this example small). Now we'll convert the RGB values to HSB. There are standard algorithms to convert an RGB-based color to an HSB color, but we don't have to go look them up. The Color class has a class method we can use called RGBtoHSB() that will do the work for us-or, at least, most of it. There are two problems with the RGBtoHSB() method, however: q The RGBtoHSB() method returns an array of the three HSB values, so we'll have to extract those values from the array. q The HSB values are measured in floating-point values from 0.0 to 1.0. I prefer to think of HSB values as integers, where the hue is a degree value around a color wheel (0 through 360), and saturation and brightness are percentages from 0 to 100. Neither of these problems is insurmountable; it just means some extra lines of code. Let's start by calling RGBtoHSB() with the new RGB values we have. The return type of that method is an array of floats, so we'll create a local variable (HSB) to store the results of the RBGtoHSB() method. (Note that you'll also need to create and pass in an empty array of floats as the fourth argument to RGBtoHSB()): float HSB = Color.RGBtoHSB(value1, value2, value3, (new float[3])); Now we'll convert those floating-point values that range from 0.0 to 1.0 to values that range from 0 and 100 (for the saturation and brightness) and 0 to 360 for the hue by multiplying the appropriate numbers and reassigning the value back to the array: HSB[0] *= 360; HSB[1] *= 100; HSB[2] *= 100; Now we have the numbers we want. The last part of the update is to put those values back into the text fields. Of course, those values are still floating-point numbers, so we'll have to cast them to ints before turning them into strings and storing them: file:///G|/ebooks/1575211831/ch13.htm (34 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt HSBcontrols.tfield1.setText(String.valueOf((int)HSB[0])); HSBcontrols.tfield2.setText(String.valueOf((int)HSB[1])); HSBcontrols.tfield3.setText(String.valueOf((int)HSB[2])); You're halfway there. The next part of the applet is that part that updates the RGB values where a text field on the HSB side has changed. This is the else in the big if-else that defines this method and determines what to update, given a change. It's actually easier to generate values from HSB values than it is to do it the other way around. There's a class method in the Color class, called getHSBColor(), that creates a new Color object from three HSB values, and once you have a Color object you can easily pull the RGB values out of there. The catch, of course, is that getHSBColor takes three floating-point arguments, and the values we have are the integer values I prefer to use. So in the call to getHSBColor, we'll have to cast the integer values from the text fields to floats and divide them by the proper conversion factor. The result of getHSBColor is a Color object, so we can simply assign that object to our c local variable so we can use it again later: c = Color.getHSBColor((float)value1 / 360, (float)value2 / 100, (float)value3 / 100); With the Color object all set, updating the RGB values involves extracting those values from that Color object. The getRed(), getGreen() and getBlue() methods, defined in the Color class, will do just that: RGBcontrols.tfield1.setText(String.valueOf(c.getRed())); RGBcontrols.tfield2.setText(String.valueOf(c.getGreen())); RGBcontrols.tfield3.setText(String.valueOf(c.getBlue())); And finally, regardless of whether the RGB or HSB value has changed, you'll need to update the color box on the left to reflect the new color. Because we have a new Color object stored in the variable c, we can use the setBackground method to change that color. Also note that setBackground doesn't automatically repaint the screen, so you'll want to fire off a repaint() as well: swatch.setBackground(c); swatch.repaint(); That's it! You're done. Compile both the ColorTest and ColorControls classes, create an HTML file to load the ColorTest applet, and check it out. The Complete Source Code Listing 13.3 shows the complete source code for the applet class ColorTest, and Listing 13.4 shows the source for the helper class ColorControls. Often it's easier to figure out what's going on in an applet when it's all in one place and you can follow the method calls and how values are passed back and forth. Start with the init() method in the ColorTest applet and go from there. Listing 13.3. The ColorTest applet. 1:import java.awt.*; 2: 3:public class ColorTest extends java.applet.Applet { 4: ColorControls RGBcontrols, HSBcontrols; 5: Canvas swatch; 6: 7: public void init() { 8: setLayout(new GridLayout(1,3,5,15)); 9: 10: // The color swatch 11: swatch = new Canvas(); 12: swatch.setBackground(Color.black); 13: 14: // the subpanels for the controls 15: RGBcontrols = new ColorControls(this, "Red", "Green", "Blue"); 16: HSBcontrols = new ColorControls(this, "Hue", "Saturation", "Brightness"); file:///G|/ebooks/1575211831/ch13.htm (35 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt 17: 18: //add it all to the layout 19: add(swatch); 20: add(RGBcontrols); 21: add(HSBcontrols); 22: } 23: 24: public Insets insets() { 25: return new Insets(10,10,10,10); 26: } 27: 28: void update(ColorControls controlPanel) { 29: Color c; 30: // get string values from text fields, convert to ints 31: int value1 = Integer.parseInt(controlPanel.tfield1.getText()); 32: int value2 = Integer.parseInt(controlPanel.tfield2.getText()); 33: int value3 = Integer.parseInt(controlPanel.tfield3.getText()); 34: 35: if (controlPanel == RGBcontrols) { // RGB has changed, update HSB 36: c = new Color(value1, value2, value3); 37: 38: // convert RGB values to HSB values 39: float HSB = Color.RGBtoHSB(value1, value2, value3, (new float[3])); 40: HSB[0] *= 360; 41: HSB[1] *= 100; 42: HSB[2] *= 100; 43: 44: // reset HSB fields 45: HSBcontrols.tfield1.setText(String.valueOf((int)HSB[0])); 46: HSBcontrols.tfield2.setText(String.valueOf((int)HSB[1])); 47: HSBcontrols.tfield3.setText(String.valueOf((int)HSB[2])); 48: 49: } else { // HSB has changed, update RGB 50: c = Color.getHSBColor((float)value1 / 360, 51: (float)value2 / 100, (float)value3 / 100); 52: 53: // reset RGB fields 54: RGBcontrols.tfield1.setText(String.valueOf(c.getRed())); 55: RGBcontrols.tfield2.setText(String.valueOf(c.getGreen())); 56: RGBcontrols.tfield3.setText(String.valueOf(c.getBlue())); 57: } 58: 59: //update swatch 60: swatch.setBackground(c); 61: swatch.repaint(); 62:} 63:} Listing 13.4. The ColorControls class. 1:import java.awt.*; 2: 3:class ColorControls extends Panel { 4: TextField tfield1, tfield2, tfield3; 5: ColorTest applet; 6: 7: ColorControls(ColorTest parent, file:///G|/ebooks/1575211831/ch13.htm (36 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt 8: String l1, String l2, String l3) { 9: 10: // get hook to outer applet parent 11: applet = parent; 12: 13: //do layouts 14: setLayout(new GridLayout(3,2,10,10)); 15: 16: tfield1 = new TextField("0"); 17: tfield2 = new TextField("0"); 18: tfield3 = new TextField("0"); 19: 20: add(new Label(l1, Label.RIGHT)); 21: add(tfield1); 22: add(new Label(l2, Label.RIGHT)); 23: add(tfield2); 24: add(new Label(l3, Label.RIGHT)); 25: add(tfield3); 26: } 27: 28: public Insets insets() { 29: return new Insets(10,10,0,0); 30: } 31: 32: public boolean action(Event evt, Object arg) { 33: if (evt.target instanceof TextField) { 34: applet.update(this); 35: return true; 36: } else return false; 37: } 38:} Up and Coming in Java 1.1 Everything you've learned up to this point is available in the 1.0.2 Java API. Java 1.1, however, will add many more features to the awt, as well as improve performance and robustness across platforms. The goal for the awt is to move beyond the basics that 1.0.2 provided and make the awt more suitable for large-scale application development. Note, also, that the 1.1 API will be backward-compatible with the 1.0.2 features; none of the code you write after reading this chapter will be obsolete in 1.1. Explicit details about the changes to the awt for 1.1 were not available at the time this book was being written. Sun has announced the following teasers, however for new features in 1.1: q New components for pop-up menus, buttons with images on top of them, and menu accelerators q Support for clipboard operations (copy and paste), drag and drop, and printing q The ability to set a cursor for each component (currently you can have only one cursor per window; you'll learn about this on Day 14, "Windows, Networking, and Other Tidbits") q q q A new set of graphics primitives as part of the new 2D graphics model; you'll learn more about this on Day 27, "The Standard Extension APIs" A new event model that delegates event actions to other objects, as opposed to requiring special methods (mouseDown(), action(), handleEvent(), and so on) to be overridden in the component classes themselves. Those action objects are often called callbacks in other event-driven programming systems. Performance enhancements: a complete rewrite for Windows 95 and NT, improvements in how components are laid out and painted, better scrolling of components, and a "number of bug fixes." For more information about the Java 1.1 changes to the awt, check out the 1.1 preview page at http://java.sun.com/products/JDK/1.1/designspecs/. file:///G|/ebooks/1575211831/ch13.htm (37 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt Summary The Java awt, or Abstract Windowing Toolkit, is a package of Java classes and interfaces for creating full-fledged access to a window-based graphical user interface system, with mechanisms for graphics display, event management, text and graphics primitives, user-interface components, and cross-platform layout. Applets are also an integral part of the awt. Today has been a big day; the lesson has brought together everything you've learned up to this point about simple applet management and added a lot more about creating applets, panels, and user-interface components and managing the interactions between all of them. With the information you got today and the few bits you'll learn tomorrow, you can create cross-platform Java applications that do just about anything you want. Q&A Q: A: I really dislike working with layout managers; they're either too simplistic or too complicated (grid bag layout). Even with a whole lot of tinkering, I can never get my applets to look like I want them to. All I want to do is define the sizes of my components and put them at an x and y position on the screen. Can I do this? I'm going to tell you how to do this, but not without a lecture. Java applications and the awt were designed such that the same graphical user interface could run equally well on different platforms and with different resolutions, different fonts, different screen sizes, and so on. Relying on pixel coordinates in this case is a really bad idea; variations from one platform to another or even from one Java environment to another on the same platform can mess up your careful layouts such that you can easily have components overlapping or obscuring each other, the edges of your applet cut off, or other layout disasters. Just as an example-I found significant differences in the layout of the same applet running in the JDK's appletviewer and in Netscape, both on Windows 95, side by side. Can you guarantee that your applet will always be run in precisely the same environment as the one in which you designed it? Layout managers, by dynamically placing elements on the screen, get around these problems. This does mean that your applet may end up looking not quite right on any platform-but at least it's usable on any platform. New versions of the awt promise to offer better layout and UI design controls. Still not convinced? Well, then. To make a component a specific size and to place it at a particular position, use a null layout manager and the reshape() method: setLayout(null); Button myButton (new Button("OK"); mybutton.reshape(10, 10, 30, 15); Q: A: Q: A: Q: You can find out more about reshape() in the Component class. I was exploring the awt classes, and I saw this subpackage called peer. There are also references to the peer classes sprinkled throughout the API documentation. What do peers do? Peers are responsible for the platform-specific parts of the awt. For example, when you create a Java awt window, you have an instance of the Window class that provides generic window behavior, and then you have an instance of a class implementing WindowPeer that creates the very specific window for that platform-a motif window under X Window, a Macintosh-style window under the Macintosh, or a Windows 95 window under Windows 95. These "peer" classes also handle communication between the window system and the Java window itself. By separating the generic component behavior (the awt classes) from the actual system implementation and appearance (the peer classes), you can focus on providing behavior in your Java application and let the Java implementation deal with the platform-specific details. There's a whole lot of functionality in the awt that you haven't talked about here. Why? Given that even a basic introduction took this long, I figured that if I put in even more detail than I already have, this book would turn into Teach Yourself Java in 21 Days Plus a Few Extra for the awt Stuff. As it is, I've left windows, menus, and dialog boxes until tomorrow, so you'll have to wait for those. But you can find out about a lot of the other features of awt merely by exploring the API documentation. Start with the Applet class and examine the sorts of methods you can call. Then look at Panel, from which Applet inherits-you have all that class's functionality as well. The superclass of Panel is Container, which provides still more interesting detail. Component comes next. Explore the API and see what you can do with it. You might find something interesting. I have a new button class I defined to look different from the standard awt button objects. I'd like to implement callbacks on this button (that is, to execute an arbitrary function when the button is pressed), but I can't figure out how to get Java to execute an arbitrary method. In C++ I'd just have a pointer to a function. In Smalltalk I'd use perform:. How can I do this in Java? file:///G|/ebooks/1575211831/ch13.htm (38 of 39) [11/06/2000 7:46:06 PM] Day 13 -- Creating User Interfaces with the awt A: You can't; Java doesn't have this facility. This is why normal button actions are executed from the generic action() method rather than using a mechanism for actions attached to the button itself (which would be more object-oriented, easier to extend, and wouldn't require a whole lot of if...elses inside action()). file:///G|/ebooks/1575211831/ch13.htm (39 of 39) [11/06/2000 7:46:06 PM] file:///G|/ebooks/1575211831/f13-1.gif file:///G|/ebooks/1575211831/f13-1.gif [11/06/2000 7:46:07 PM] file:///G|/ebooks/1575211831/f13-2.gif file:///G|/ebooks/1575211831/f13-2.gif [11/06/2000 7:46:08 PM] file:///G|/ebooks/1575211831/f13-3.gif file:///G|/ebooks/1575211831/f13-3.gif [11/06/2000 7:46:08 PM] file:///G|/ebooks/1575211831/f13-4.gif file:///G|/ebooks/1575211831/f13-4.gif [11/06/2000 7:46:08 PM] file:///G|/ebooks/1575211831/f13-5.gif file:///G|/ebooks/1575211831/f13-5.gif [11/06/2000 7:46:09 PM] file:///G|/ebooks/1575211831/f13-6.gif file:///G|/ebooks/1575211831/f13-6.gif [11/06/2000 7:46:09 PM] file:///G|/ebooks/1575211831/f13-7.gif file:///G|/ebooks/1575211831/f13-7.gif [11/06/2000 7:46:09 PM] file:///G|/ebooks/1575211831/f13-8.gif file:///G|/ebooks/1575211831/f13-8.gif [11/06/2000 7:46:10 PM] file:///G|/ebooks/1575211831/f13-9.gif file:///G|/ebooks/1575211831/f13-9.gif [11/06/2000 7:46:10 PM] file:///G|/ebooks/1575211831/f13-10.gif file:///G|/ebooks/1575211831/f13-10.gif [11/06/2000 7:46:11 PM] file:///G|/ebooks/1575211831/f13-11.gif file:///G|/ebooks/1575211831/f13-11.gif [11/06/2000 7:46:11 PM] file:///G|/ebooks/1575211831/f13-12.gif file:///G|/ebooks/1575211831/f13-12.gif [11/06/2000 7:46:11 PM] file:///G|/ebooks/1575211831/f13-13.gif file:///G|/ebooks/1575211831/f13-13.gif [11/06/2000 7:46:12 PM] file:///G|/ebooks/1575211831/f13-14.gif file:///G|/ebooks/1575211831/f13-14.gif [11/06/2000 7:46:12 PM] file:///G|/ebooks/1575211831/f13-15.gif file:///G|/ebooks/1575211831/f13-15.gif [11/06/2000 7:46:12 PM] file:///G|/ebooks/1575211831/f13-16.gif file:///G|/ebooks/1575211831/f13-16.gif [11/06/2000 7:46:12 PM] file:///G|/ebooks/1575211831/f13-17.gif file:///G|/ebooks/1575211831/f13-17.gif [11/06/2000 7:46:13 PM] file:///G|/ebooks/1575211831/f13-18.gif file:///G|/ebooks/1575211831/f13-18.gif [11/06/2000 7:46:13 PM] file:///G|/ebooks/1575211831/f13-19.gif file:///G|/ebooks/1575211831/f13-19.gif [11/06/2000 7:46:13 PM] file:///G|/ebooks/1575211831/f13-20.gif file:///G|/ebooks/1575211831/f13-20.gif [11/06/2000 7:46:14 PM] file:///G|/ebooks/1575211831/f13-21.gif file:///G|/ebooks/1575211831/f13-21.gif [11/06/2000 7:46:14 PM] file:///G|/ebooks/1575211831/f13-22.gif file:///G|/ebooks/1575211831/f13-22.gif [11/06/2000 7:46:14 PM] file:///G|/ebooks/1575211831/f13-23.gif file:///G|/ebooks/1575211831/f13-23.gif [11/06/2000 7:46:15 PM] file:///G|/ebooks/1575211831/f13-24.gif file:///G|/ebooks/1575211831/f13-24.gif [11/06/2000 7:46:15 PM] file:///G|/ebooks/1575211831/f13-25.gif file:///G|/ebooks/1575211831/f13-25.gif [11/06/2000 7:46:15 PM] Day 11 -- More Animation, Images, and Sound Day 11 More Animation, Images, and Sound by Laura Lemay CONTENTS q Retrieving and Using Images r r Drawing Images r A Note About Image Observers r q Getting Images Modifying Images Creating Animation Using Images r An Example: Neko q Retrieving and Using Sounds q Using Animation Packages r r q Sun's Animator Applet Dimension X's Liquid Motion More About Flicker: Double-Buffering r Creating Applets with Double-Buffering r A Note on Disposing Graphics Contexts r An Example: Checkers Revisited q Summary q Q&A Animation is fun and easy to do in Java, but there's only so much you can do with the built-in Java methods for lines and fonts and colors. For really interesting animation, you have to provide your own images for each frame of the animation-and having sounds is nice, as well. Today you'll do more with animation, incorporating images and sounds into Java applets. Specifically, you'll explore the following topics: q Using bitmap images such as GIF or JPEG files-getting them from the server, loading them into Java, and displaying them in your applet q Creating animation using images q Using sounds-getting them and playing them at the appropriate times q Using precompiled animator applets-an easy way to organize animation and sounds in Java q Double-buffering-hard-core flicker avoidance file:///G|/ebooks/1575211831/ch11.htm (1 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound Retrieving and Using Images Basic image handling in Java is easy. The Image class in the java.awt package provides abstract methods to represent common image behavior, and special methods defined in Applet and Graphics give you everything you need to load and display images in your applet as easily as drawing a rectangle. In this section, you'll learn about how to get and draw images in your Java applets. Getting Images To display an image in your applet, you first must load that image over the Net into your Java program. Images are stored as separate files from your Java class files, so you have to tell Java where to find them. The Applet class provides a method called getImage(), which loads an image and automatically creates an instance of the Image class for you. To use it, all you have to do is import the java.awt.Image class into your Java program, and then give getImage the URL of the image you want to load. There are two ways of doing the latter step: q The getImage() method with a single argument (an object of type URL) retrieves the image at that URL. q The getImage() method with two arguments: the base URL (also a URL object) and a string representing the path or filename of the actual image (relative to the base). Although the first way may seem easier (just plug in the URL as a URL object), the second is more flexible. Remember, because you're compiling Java files, if you include a hard-coded URL of an image and then move your files around to a different location, you have to recompile all your Java files. The latter form, therefore, is usually the one to use. The Applet class also provides two methods that will help with the base URL argument to getImage(): q The getDocumentBase() method returns a URL object representing the directory of the HTML file that contains this applet. So, for example, if the HTML file is located at http://www.myserver.com/htmlfiles/javahtml/, getDocumentBase() returns a URL pointing to that path. q The getCodeBase() method returns a string representing the directory in which this applet is contained-which may or may not be the same directory as the HTML file, depending on whether the CODEBASE attribute in <APPLET> is set or not. Whether you use getDocumentBase() or getCodebase() depends on whether your images are relative to your HTML files or relative to your Java class files. Use whichever one applies better to your situation. Note that either of these methods is more flexible than hard-coding a URL or pathname into the getImage() method; using either getDocumentBase() or getCodeBase() enables you to move your HTML files and applets around and Java can still find your images. (This assumes, of course, that you move the class files and the images around together. If you move the images somewhere else and leave the class files where they are, you'll have to edit and recompile your source.) Here are a few examples of getImage, to give you an idea of how to use it. This first call to getImage() retrieves the file at that specific URL (http://www.server.com/files/image.gif). If any part of that URL changes, you have to recompile your Java applet to take into account the new path: Image img = getImage( new URL("http://www.server.com/files/image.gif")); In the following form of getImage, the image.gif file is in the same directory as the HTML files that refer to this applet: file:///G|/ebooks/1575211831/ch11.htm (2 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound Image img = getImage(getDocumentBase(), "image.gif") In this similar form, the file image.gif is in the same directory as the applet itself: Image img = getImage(getCodeBase(), "image.gif") If you have lots of image files, it's common to put them into their own subdirectory. This form of getImage() looks for the file image.gif in the directory images, which, in turn, is in the same directory as the Java applet: Image img = getImage(getCodeBase(), "images/image.gif") If getImage() can't find the file indicated, it returns null. drawImage() on a null image will simply draw nothing. Using a null image in other ways will probably cause an error. Note Currently, Java supports images in the GIF and JPEG formats. Other image formats may be available later; however, for now, your images should be in either GIF or JPEG. Drawing Images All that stuff with getImage() does nothing except go off and retrieve an image and stuff it into an instance of the Image class. Now that you have an image, you have to do something with it. Technical Note Actually, the loading of images is internally a lot more complex than this. When you retrieve an image using getImage(), that method actually spawns a thread to load the image and returns almost immediately with your Image object. This gives your program the illusion of almost instantaneously having the image there ready to use. It may take some time, however, for the actual image to download and decompress, which may cause your image applets to draw with only partial images, or for the image to be drawn on the screen incrementally as it loads (all the examples in this chapter work like this). You can control how you want your applet to behave given a partial image (for example, if you want it to wait until it's all there before displaying it) by taking advantage of the ImageObserver interface. You'll learn more about ImageObserver later in this lesson in the section "A Note About Image Observers." The most likely thing you're going to want to do with an image is display it as you would a rectangle or a text string. The Graphics class provides two methods to do just this, both called drawImage(). The first version of drawImage() takes four arguments: the image to display, the x and y positions of the top left corner, and this: public void paint() { g.drawImage(img, 10, 10, this); } This first form does what you would expect it to: It draws the image in its original dimensions with the top-left corner at the given x and y positions. Listing 11.1 shows the code for a very simple applet that loads an image called file:///G|/ebooks/1575211831/ch11.htm (3 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound ladybug.gif and displays it. Figure 11.1 shows the obvious result. Figure 11.1 : The lady bug image. Listing 11.1. The Ladybug applet. 1:import java.awt.Graphics; 2:import java.awt.Image; 3: 4:public class LadyBug extends java.applet.Applet { 5: 6: Image bugimg; 7: 8: public void init() { 9: bugimg = getImage(getCodeBase(), 10: "images/ladybug.gif"); 11: } 12: 13: public void paint(Graphics g) { 14: g.drawImage(bugimg, 10, 10,this); 15: } 16:} In this example the instance variable bugimg holds the ladybug image, which is loaded in the init()method. The paint()method then draws that image on the screen. The second form of drawImage() takes six arguments: the image to draw, the x and y coordinates of the top-left corner, a width and height of the image bounding box, and this. If the width and height arguments for the bounding box are smaller or larger than the actual image, the image is automatically scaled to fit. By using those extra arguments, you can squeeze and expand images into whatever space you need them to fit in (keep in mind, however, that there may be some image degradation from scaling it smaller or larger than its intended size). One helpful hint for scaling images is to find out the size of the actual image that you've loaded, so you can then scale it to a specific percentage and avoid distortion in either direction. Two methods defined for the Image class can give you that information: getWidth() and getHeight(). Both take a single argument, an instance of ImageObserver, which is used to track the loading of the image (more about this later). Most of the time, you can use just this as an argument to either getWidth() or getHeight(). If you stored the ladybug image in a variable called bugimg, for example, this line returns the width of that image, in pixels: theWidth = bugimg.getWidth(this); Technical Note Here's another case where, if the image isn't loaded all the way, you may get different results. Calling getWidth() or getHeight() before the image has fully loaded will result in values of -1 for each one. Tracking image loading with image observers can help you keep track of when this information appears. Listing 11.2 shows another use of the ladybug image, this time scaled several times to different sizes (Figure 11.2 file:///G|/ebooks/1575211831/ch11.htm (4 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound shows the result). Figuire 11.2: The second Lady bug applet. Listing 11.2. More ladybugs, scaled. 1: import java.awt.Graphics; 2: import java.awt.Image; 3: 4: public class LadyBug2 extends java.applet.Applet { 5: 6: Image bugimg; 7: 8: public void init() { 9: bugimg = getImage(getCodeBase(), 10: "images/ladybug.gif"); 11: } 12: 13: public void paint(Graphics g) { 14: int iwidth = bugimg.getWidth(this); 15: int iheight = bugimg.getHeight(this); 16: int xpos = 10; 17: 18: // 25 % 19: g.drawImage(bugimg, xpos, 10, 20: iwidth / 4, iheight / 4, this); 21: 22: // 50 % 23: xpos += (iwidth / 4) + 10; 24: g.drawImage(bugimg, xpos , 10, 25: iwidth / 2, iheight / 2, this); 26: 27: // 100% 28: xpos += (iwidth / 2) + 10; 29: g.drawImage(bugimg, xpos, 10, this); 30: 31: // 150% x, 25% y 32: g.drawImage(bugimg, 10, iheight + 30, 33: (int)(iwidth * 1.5), iheight / 4, this); 34: } 35: } A Note About Image Observers I've been steadfastly ignoring mentioning that last argument to drawImage(): the mysterious this, which also appears as an argument to getWidth() and getHeight(). Why is this argument used? Its official use is to pass in an object that functions as an ImageObserver (that is, an object that implements the ImageObserver interface). Image observers are used to watch the progress of how far along an image is in the loading process and to make decisions when the image is only fully or partially loaded. So, for example, your applet could pause until all the images are loaded and ready, or display a "loading" message, or do something else while it was waiting. file:///G|/ebooks/1575211831/ch11.htm (5 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound The Applet class, which your applet inherits from, contains a default behavior for image observation (which it inherits from the Component superclass) that should work in the majority of cases-hence, the this argument to drawImage(), getWidth(), and getHeight(). The only reason you'll want to use an alternate argument in its place is if you want more control over what your applet will do in cases where an image may only be partially loaded, or if tracking lots of images loading asynchronously. You'll learn more about how to deal with image observers on Day 24, "Advanced Animation and Media." Modifying Images In addition to the basics of handling images described in this section, the java.awt.image package provides more classes and interfaces that enable you to modify images and their internal colors, or to create bitmap images by hand. You'll learn more about modifying images on Day 25, "Fun with Image Filters." Creating Animation Using Images Creating animation with images is much the same as creating animation with fonts, colors, or shapes-you use the same methods and the same procedures for painting, repainting, and reducing flicker that you learned about yesterday. The only difference is that you have a stack of images to flip through rather than a set of painting methods. Probably the best way to show you how to use images for animation is simply to walk through an example. Here's an extensive one of an animation of a small cat called Neko. An Example: Neko Neko was a small Macintosh animation/game written and drawn by Kenji Gotoh in 1989. "Neko" is Japanese for "cat," and the animation is of a small kitten that chases the mouse pointer around the screen, sleeps, scratches, and generally acts cute. The Neko program has since been ported to just about every possible platform, as well as rewritten as a popular screensaver. For this example, you'll implement a small animation based on the original Neko graphics. Unlike the original Neko the cat, which was autonomous (it could "sense" the edges of the window and turn and run in a different direction), this applet merely causes Neko to run in from the left side of the screen, stop in the middle, yawn, scratch its ear, sleep a little, and then run off to the right. Note This is by far the largest of the applets discussed so far in this book, and if I either print it here and then describe it, or build it up line by line, you'll be here for days. Instead, I'm going to describe the parts of this applet independently, and I'm going to leave out the basics-the stuff you learned yesterday about starting and stopping threads, what the run() method does, and so on. All the code is printed later today so that you can put it all together. Step 1: Collect Your Images Before you begin writing Java code to construct an animation, you should have all the images that form the animation itself. For this version of Neko there are nine of them (the original has 36), as shown in Figure 11.3. Figure 11.3 : The images for Neko. Note file:///G|/ebooks/1575211831/ch11.htm (6 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound The Neko images, as well as the source code for this applet, are available on the CD. For this example I've stored these images in a directory called, appropriately, images. Where you store your images isn't all that important, but you should take note of where you've put them because you'll need that information later on when you load your images. Step 2: Organize and Load the Images in Your Applet Now, on to the applet. The basic idea here is that you have a set of images and you display them one at a time, rapidly, so that they give the appearance of movement. The easiest way to manage this in Java is to store the images in an array of class Image, and then to have a special variable to keep track of the current image. As you iterate over the slots in the array (using a for loop), you can change the value of the current image each time. For the Neko applet, you'll create instance variables to implement both these things: an array to hold the images, called nekopics, and a variable of type Image called currentimg, to hold the current image being displayed: Image nekopics = new Image[9]; Image currentimg; Here the image array has nine slots, as the Neko animation has nine images. If you have a larger or smaller set of images, you'll have a different number of slots. Technical Note The java.util class contains a class (HashTable) that implements a hash table. For large numbers of images, a hash table is faster to find and retrieve images from than an array is. Because there's a small number of images here, and because arrays are better for fixed-length, repeating animation, I'll use an array here. Because the Neko animation draws the cat images in different positions on the screen, you'll also want to keep track of the current x and y positions so that the various methods in this applet know where to start drawing. The y stays constant for this particular applet (Neko runs left to right at the same y position), but the x may vary. Let's add two instance variables for those two positions: int xpos; int ypos = 50; Now, on to the body of the applet. During the applet's initialization, you'll read in all the images and store them in the nekopics array. This is the sort of operation that works especially well in an init() method. Given that you have nine images with nine different filenames, you could do a separate call to getImage() for each one. You can save at least a little typing, however, by creating a local array of the file names (nekosrc, an array of strings) and then use a for loop to iterate over each one and load them in turn. Here's the init() method for the Neko applet that loads all the images into the nekopics array: public void init() { String nekosrc = { "right1.gif", "right2.gif", "stop.gif", "yawn.gif", "scratch1.gif", "scratch2.gif","sleep1.gif", "sleep2.gif", "awake.gif" }; for (int i=0; i < nekopics.length; i++) { file:///G|/ebooks/1575211831/ch11.htm (7 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound nekopics[i] = getImage(getCodeBase(), "images/" + nekosrc[i]); } } Note here in the call to getImage() that the directory these images are stored in (the image directory) is included as part of the path. Step 3: Animate the Images With the images loaded, the next step is to start animating the bits of the applet. You do this inside the applet's thread's run() method. In this applet, Neko does five main things: q Runs in from the left side of the screen q Stops in the middle and yawns q Scratches four times q Sleeps q Wakes up and runs off to the right side of the screen Although you could animate this applet by merely painting the right image to the screen at the right time, it makes more sense to write this applet so that many of Neko's activities are contained in individual methods. This way, you can reuse some of the activities (the animation of Neko running, in particular) if you want Neko to do things in a different order. Let's start by creating a method to make Neko run. Because you're going to be using this one twice, making it generic is a good plan. Let's create a nekorun() method, which takes two arguments: the x position to start, and the x position to end. Neko then runs between those two positions (the y remains constant). void nekorun(int start, int end) { ... } There are two images that represent Neko running; to create the running effect, you need to alternate between those two images (stored in positions 0 and 1 of the image array), as well as move them across the screen. The moving part is a simple for loop between the start and end arguments, setting the x position to the current loop value. Swapping the images means merely testing to see which one is active at any turn of the loop and assigning the other one to the current image. Finally, at each new frame, you'll call repaint() and sleep() for a bit to pause the animation. Actually, given that during this animation there will be a lot of pausing of various intervals, it makes sense to create a utility method that does just that-pause for a given amount of time. The pause() method, therefore, takes one argument, a number of milliseconds. Here's its definition: void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } Back to the nekorun() method. To summarize, nekorun() iterates from the start position to the end position. For each turn of the loop, it sets the current x position, sets currentimg to the right animation frame, calls repaint(), and pauses. Got it? Here's the definition of nekorun: void nekorun(int start, int end) { file:///G|/ebooks/1575211831/ch11.htm (8 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound for (int i = start; i < end; i+=10) { xpos = i; // swap images if (currentimg == nekopics[0]) currentimg = nekopics[1]; else currentimg = nekopics[0]; repaint(); pause(150); } } Note that in that second line you increment the loop by 10 pixels. Why 10 pixels and not, say, 5 or 8? The answer is determined mostly through trial and error to see what looks right. Ten seems to work best for the animation. When you write your own animation, you have to play with both the distances and the sleep times until you get an animation you like. Speaking of repaint(), let's skip over to that paint() method, which paints each frame. Here the paint() method is trivially simple; all paint() is responsible for is painting the current image at the current x and y positions. All that information is stored in instance variables. However, we do want to make sure that the images actually exist before we draw them (the images might be in the process of loading). To catch this and make sure we don't try drawing an image that isn't there (resulting in all kinds of errors), we'll test to make sure currentimg isn't null before calling drawImage() to paint the image: public void paint(Graphics g) { if (currentimg != null) g.drawImage(currentimg, xpos, ypos, this); } Now let's back up to the run() method, where the main processing of this animation is happening. You've created the nekorun() method; in run() you'll call that method with the appropriate values to make Neko run from the left edge of the screen to the center: // run from one side of the screen to the middle nekorun(0, size().width / 2); The second major thing Neko does in this animation is stop and yawn. You have a single frame for each of these things (in positions 2 and 3 in the array), so you don't really need a separate method to draw them. All you need to do is set the appropriate image, call repaint(), and pause for the right amount of time. This example pauses for a second each time for both stopping and yawning-again, using trial and error. Here's the code: // stop and pause currentimg = nekopics[2]; repaint(); pause(1000); // yawn currentimg = nekopics[3]; repaint(); pause(1000); Let's move on to the third part of the animation: Neko scratching. There's no horizontal movement for this part of the animation. You alternate between the two scratching images (stored in positions 4 and 5 of the image array). Because file:///G|/ebooks/1575211831/ch11.htm (9 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound scratching is a distinct action, however, let's create a separate method for it. The nekoscratch() method takes a single argument: the number of times to scratch. With that argument, you can iterate, and then, inside the loop, alternate between the two scratching images and repaint each time: void nekoscratch(int numtimes) { for (int i = numtimes; i > 0; i--) { currentimg = nekopics[4]; repaint(); pause(150); currentimg = nekopics[5]; repaint(); pause(150); } } Inside the run method, you can then call nekoscratch() with an argument of (4): // scratch four times nekoscratch(4); Onward! After scratching, Neko sleeps. Again, you have two images for sleeping (in positions 6 and 7 of the array), which you'll alternate a certain number of times. Here's the nekosleep() method, which takes a single number argument, and animates for that many "turns": void nekosleep(int numtimes) { for (int i = numtimes; i > 0; i--) { currentimg = nekopics[6]; repaint(); pause(250); currentimg = nekopics[7]; repaint(); pause(250); } } Call nekosleep() in the run() method like this: // sleep for 5 "turns" nekosleep(5); Finally, to finish off the applet, Neko wakes up and runs off to the right side of the screen. The waking up image is the last image in the array (position 8), and you can reuse the nekorun method to finish: // wake up and run off currentimg = nekopics[8]; repaint(); pause(500); nekorun(xpos, size().width + 10); Step 4: Finish Up file:///G|/ebooks/1575211831/ch11.htm (10 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound There's one more thing left to do to finish the applet. The images for the animation all have white backgrounds. Drawing those images on the default applet background (a medium gray) means an unsightly white box around each image. To get around the problem, merely set the applet's background to white at the start of the run() method: setBackground(Color.white); Got all that? There's a lot of code in this applet, and a lot of individual methods to accomplish a rather simple animation, but it's not all that complicated. The heart of it, as in the heart of all forms of animation in Java, is to set up the frame and then call repaint() to enable the screen to be drawn. Note that you don't do anything to reduce the amount of flicker in this applet. It turns out that the images are small enough, and the drawing area also small enough, that flicker is not a problem for this applet. It's always a good idea to write your animation to do the simplest thing first, and then add behavior to make it run cleaner. To finish up this section, Listing 11.3 shows the complete code for the Neko applet. Listing 11.3. The final Neko applet. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: import java.awt.Graphics; import java.awt.Image; import java.awt.Color; public class Neko extends java.applet.Applet implements Runnable { Image nekopics = new Image[9]; Image currentimg; Thread runner; int xpos; int ypos = 50; public void init() { String nekosrc = { "right1.gif", "right2.gif", "stop.gif", "yawn.gif", "scratch1.gif", "scratch2.gif","sleep1.gif", "sleep2.gif", "awake.gif" }; for (int i=0; i < nekopics.length; i++) { nekopics[i] = getImage(getCodeBase(), "images/" + nekosrc[i]); } } public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void stop() { if (runner != null) { runner.stop(); file:///G|/ebooks/1575211831/ch11.htm (11 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: runner = null; } } public void run() { setBackground(Color.white); // run from one side of the screen to the middle nekorun(0, size().width / 2); // stop and pause currentimg = nekopics[2]; repaint(); pause(1000); // yawn currentimg = nekopics[3]; repaint(); pause(1000); // scratch four times nekoscratch(4); // sleep for 5 "turns" nekosleep(5); // wake up and run off currentimg = nekopics[8]; repaint(); pause(500); nekorun(xpos, size().width + 10); } void nekorun(int start, int end) { for (int i = start; i < end; i += 10) { xpos = i; // swap images if (currentimg == nekopics[0]) currentimg = nekopics[1]; else currentimg = nekopics[0]; repaint(); pause(150); } } void nekoscratch(int numtimes) { for (int i = numtimes; i > 0; i--) { currentimg = nekopics[4]; repaint(); pause(150); file:///G|/ebooks/1575211831/ch11.htm (12 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: } currentimg = nekopics[5]; repaint(); pause(150); } } void nekosleep(int numtimes) { for (int i = numtimes; i > 0; i--) { currentimg = nekopics[6]; repaint(); pause(250); currentimg = nekopics[7]; repaint(); pause(250); } void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } public void paint(Graphics g) { if (currentimg != null) g.drawImage(currentimg, xpos, ypos, this); } Retrieving and Using Sounds Java has built-in support for playing sounds in conjunction with running animation or for sounds on their own. In fact, support for sound, like support for images, is built into the Applet and awt classes, so using sound in your Java applets is as easy as loading and using images. Currently, the only sound format that Java supports is Sun's AU format, sometimes called µ-law format. AU files tend to be smaller than sound files in other formats, but the sound quality is not very good. If you're especially concerned with sound quality, you may want your sound clips to be references in the traditional HTML way (as links to external files) rather than included in a Java applet. The simplest way to retrieve and play a sound is through the play() method, part of the Applet class and therefore available to you in your applets. The play() method is similar to the getImage() method in that it takes one of two forms: q play() with one argument, a URL object, loads and plays the given audio clip at that URL. q play() with two arguments, one a base URL and one a pathname, loads and plays that audio file. The first argument can most usefully be either a call to getDocumentBase() or getCodeBase(). For example, the following line of code retrieves and plays the sound meow.au, which is contained in the audio directory. The audio directory, in turn, is located in the same directory as this applet: play(getCodeBase(), "audio/meow.au"); file:///G|/ebooks/1575211831/ch11.htm (13 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound The play() method retrieves and plays the given sound as soon as possible after it is called. If it can't find the sound, you won't get an error; you just won't get any audio when you expect it. If you want to play a sound repeatedly, start and stop the sound clip, or run the clip as a loop (play it over and over), things are slightly more complicated-but not much more so. In this case, you use the applet method getAudioClip() to load the sound clip into an instance of the class AudioClip (part of java.applet-don't forget to import it) and then operate directly on that AudioClip object. Suppose, for example, that you have a sound loop that you want to play in the background of your applet. In your initialization code, you can use this line to get the audio clip: AudioClip clip = getAudioClip(getCodeBase(), "audio/loop.au"); Then, to play the clip once, use the play() method: clip.play(); To stop a currently playing sound clip, use the stop() method: clip.stop(); To loop the clip (play it repeatedly), use the loop() method: clip.loop(); If the getAudioClip() method can't find the sound you indicate, or can't load it for any reason, it returns null. It's a good idea to test for this case in your code before trying to play the audio clip, because trying to call the play(), stop(), and loop() methods on a null object will result in an error (actually, an exception). In your applet, you can play as many audio clips as you need; all the sounds you use will mix together properly as they are played by your applet. Note that if you use a background sound-a sound clip that loops repeatedly-that sound clip will not stop playing automatically when you suspend the applet's thread. This means that even if your reader moves to another page, the first applet's sounds will continue to play. You can fix this problem by stopping the applet's background sound in your stop() method: public void stop() { if (runner != null) { if (bgsound != null) bgsound.stop(); runner.stop(); runner = null; } } Listing 11.4 shows a simple framework for an applet that plays two sounds: The first, a background sound called loop.au, plays repeatedly. The second, a horn honking (beep.au), plays every 5 seconds. (I won't bother giving you a picture of this applet because it doesn't actually display anything other than a simple string to the screen.) Listing 11.4. The AudioLoop applet. file:///G|/ebooks/1575211831/ch11.htm (14 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: import java.awt.Graphics; import java.applet.AudioClip; public class AudioLoop extends java.applet.Applet implements Runnable { AudioClip bgsound; AudioClip beep; Thread runner; public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void stop() { if (runner != null) { if (bgsound != null) bgsound.stop(); runner.stop(); runner = null; } } public void init() { bgsound = getAudioClip(getCodeBase(),"audio/loop.au"); beep = getAudioClip(getCodeBase(), "audio/beep.au"); } public void run() { if (bgsound != null) bgsound.loop(); while (runner != null) { try { Thread.sleep(5000); } catch (InterruptedException e) { } if (beep != null) beep.play(); } } public void paint(Graphics g) { g.drawString("Playing Sounds....", 10, 10); } } There are only a few things to note about this applet. First, note the init() method in lines 26 to 29, which loads both the loop.au and the beep.au sound files. We've made no attempt here to make sure these files actually load as expected, so the possibility exists that the bgsound and beep instance variables may end up with the null values if the file cannot load. In that case, we won't be able to call loop(), stop(), or any other methods, so we should make sure we test for that elsewhere in the applet. And we have tested for null several places here, particularly in the run() method in lines 32 and 36. These lines start file:///G|/ebooks/1575211831/ch11.htm (15 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound the sounds looping and playing, but only if the values of the bgsound and beep variables are something other than null. Finally, note line 20, which explicitly turns off the background sound if the thread is also being stopped. Because background sounds do not stop playing even when the thread has been stopped, you have to explicitly stop them here. Using Animation Packages Up until this point, I've described animation in a fair amount of detail, in order to help explain other topics that you can use in applets that aren't necessarily animation (for example, graphics, threads, managing bitmap images). If the purpose of your applet is animation, however, in many cases writing your own applet is overkill. General-purpose applets that do nothing but animation exist, and you can use those applets in your own Web pages with your own set of images-all you need to do is modify the HTML files to give different parameters to the applet itself. Using these packages makes creating simple animation in Java far easier, particularly for Java developers who aren't as good at the programming side of Java. Two animation packages are particularly useful in this respect: Sun's Animator applet and Dimension X's Liquid Motion. Sun's Animator Applet Sun's Animator applet, one of the examples in the 1.0.2 JDK, provides a simple, general-purpose applet for creating animation with Java. You compile the code and create an HTML file with the appropriate parameters for the animation. Using the Animator applet, you can do the following: q Create an animation loop, that is, an animation that plays repeatedly. q Add a soundtrack to the applet. q Add sounds to be played at individual frames. q Indicate the speed at which the animation is to occur. q Specify the order of the frames in the animation-which means that you can reuse frames that repeat during the course of the animation. Even if you don't intend to use Sun's Animator for your own animation, you might want to look at the code. The Animator applet is a great example of how animation works in Java and the sorts of clever tricks you can use in a Java applet. Dimension X's Liquid Motion While Sun's Animator applet is a simple (and free) example of a general-purpose animation tool, Liquid Motion from Dimension X is much more ambitious. Liquid Motion is an entire GUI application, running in Java, with which you build animation (they call them scenes) given a set of media files (images and sound). If you've ever used Macromedia Director to create multimedia presentations (or Shockwave presentations for the Web), you're familiar with the approach. To use Liquid Motion, you import your media files, and then you can arrange images on the screen, arrange them in frames over points in time, have them move along predefined paths, and add colors and backgrounds and audio tracks simply by clicking buttons. Figure 11.4 shows the main Liquid Motion screen. Figure 11.4 : Liquid Motion. When you save a Liquid Motion scene as HTML, the program saves all the Java class files you'll need to run the presentation and writes an HTML file, complete with the appropriate <APPLET> tags and parameters, to run that scene. All you need to do is move the files to your Web server and you're done-there's no Java programming involved file:///G|/ebooks/1575211831/ch11.htm (16 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound whatsoever. But even if you are a Java programmer (as you will be by the time you finish this book), you can extend the Liquid Motion framework to include new behavior and features. Because Liquid Motion is a Java application, it runs on any platform that Java runs on (Windows, UNIX, Mac). It is a commercial application, costing $149.99 for the Windows and UNIX versions (the Mac version exists, but does not appear to cost anything). Demonstration copies of the Solaris and Windows versions, which allow you to play with the interface but not to publish the files on the Web, are available at Dimension X's Web site. Liquid Motion is worth checking out if you intend to do a lot of animation-type applets in your Web pages; using Liquid Motion its fairly easy to get up and running, far faster than working directly with the code. Check out http://www.dimensionx.com/products/lm/ for more information and demonstration versions. More About Flicker: Double-Buffering Yesterday you learned two simple ways to reduce flickering in Java animation. Although you learned specifically about animation using drawing, flicker can also result from animation using images. In addition to the two flicker-reducing methods described yesterday, there is one other way to reduce flicker: double-buffering. With double-buffering, you create a second surface (offscreen, so to speak), do all your painting to that offscreen surface, and then draw the whole surface at once onto the actual applet (and onto the screen) at the end-rather than drawing to the applet's actual graphics surface. Because all the work actually goes on behind the scenes, there's no opportunity for interim parts of the drawing process to appear accidentally and disrupt the smoothness of the animation. Double-buffering is the process of doing all your drawing to an offscreen buffer and then displaying that entire screen at once. It's called double-buffering because there are two drawing buffers and you switch between them. Double-buffering isn't always the best solution. If your applet is suffering from flicker, try overriding update() and drawing only portions of the screen first; that may solve your problem. Double-buffering is less efficient than regular buffering and also takes up more memory and space, so, if you can avoid it, make an effort to do so. In terms of nearly eliminating animation flicker, however, double-buffering works exceptionally well. Creating Applets with Double-Buffering To create an applet that uses double-buffering, you need two things: an offscreen image to draw on and a graphics context for that image. Those two together mimic the effect of the applet's drawing surface: the graphics context (an instance of Graphics) to provide the drawing methods, such as drawImage (and drawString), and the Image to hold the dots that get drawn. There are four major steps to adding double-buffering to your applet. First, your offscreen image and graphics context need to be stored in instance variables so that you can pass them to the paint() method. Declare the following instance variables in your class definition: Image offscreenImage; Graphics offscreenGraphics; Second, during the initialization of the applet, you'll create an Image and a Graphics object and assign them to these variables (you have to wait until initialization so you know how big they're going to be). The createImage() method gives you an instance of Image, which you can then send the getGraphics() method in order to get a new graphics context for that image: offscreenImage = createImage(size().width, size().height); file:///G|/ebooks/1575211831/ch11.htm (17 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound offscreenGraphics = offscreenImage.getGraphics(); Now, whenever you have to draw to the screen (usually in your paint() method), rather than drawing to paint's graphics, draw to the offscreen graphics. For example, to draw an image called img at position 10,10, use this line: offscreenGraphics.drawImage(img, 10, 10, this); Finally, at the end of your paint method, after all the drawing to the offscreen image is done, add the following line to place the offscreen buffer on to the real screen: g.drawImage(offscreenImage, 0, 0, this); Of course, you most likely will want to override update() so that it doesn't clear the screen between paintings: public void update(Graphics g) { paint(g); } Let's review those four steps: 1. Add instance variables to hold the image and graphics contexts for the offscreen buffer. 2. Create an image and a graphics context when your applet is initialized. 3. Do all your applet painting to the offscreen buffer, not the applet's drawing surface. 4. At the end of your paint() method, draw the offscreen buffer to the real screen. A Note on Disposing Graphics Contexts If you make extensive use of graphics contexts in your applets or applications, be aware that those contexts will often continue to stay around after you're done with them, even if you no longer have any references to them. Graphics contexts are special objects in the awt that map to the native operating system; Java's garbage collector cannot release those contexts by itself. If you use multiple graphics contexts or use them repeatedly, you'll want to explicitly get rid of those contexts once you're done with them. Use the dispose() method to explicitly clean up a graphics context. A good place to put this might be in the applet's destroy() method (which you learned about on Day 8, "Java Applet Basics"; it was one of the primary applet methods, along with init(), start(), and stop()): public void destroy() { offscreenGraphics.dispose(); } An Example: Checkers Revisited Yesterday's example featured the animated moving red oval to demonstrate animation flicker and how to reduce it. Even with the operations you did yesterday, however, the Checkers applet still flashed occasionally. Let's revise that applet to include double-buffering. First, add the instance variables for the offscreen image and its graphics context: Image offscreenImg; Graphics offscreenG; file:///G|/ebooks/1575211831/ch11.htm (18 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound Second, add an init method to initialize the offscreen buffer: public void init() { offscreenImg = createImage(size().width, size().height); offscreenG = offscreenImg.getGraphics(); } Third, modify the paint() method to draw to the offscreen buffer instead of to the main graphics buffer: public void paint(Graphics g) { // Draw background offscreenG.setColor(Color.black); offscreenG.fillRect(0, 0, 100, 100); offscreenG.setColor(Color.white); offscreenG.fillRect(100, 0, 100, 100); // Draw checker offscreenG.setColor(Color.red); offscreenG.fillOval(xpos, 5, 90, 90); g.drawImage(offscreenImg, 0, 0, this); } Note that you're still clipping the main graphics rectangle in the update() method, as you did yesterday; you don't have to change that part. The only part that is relevant is that final line in the paint() method wherein everything is drawn offscreen before finally being displayed. Finally, in the applet's destroy() method we'll explicitly dispose of the graphics context stored in offscreenG: public void destroy() { offscreenG.dispose(); } Listing 11.5 shows the final code for the Checkers applet (Checkers3.java), which includes double-buffering. Listing 11.5. Checkers revisited, with double-buffering. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: import java.awt.Graphics; import java.awt.Color; import java.awt.Image; public class Checkers3 extends java.applet.Applet implements Runnable { Thread runner; int xpos; int ux1,ux2; Image offscreenImg; Graphics offscreenG; public void init() { offscreenImg = createImage(this.size().width, this.size().height); offscreenG = offscreenImg.getGraphics(); file:///G|/ebooks/1575211831/ch11.htm (19 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: } public void start() { if (runner == null); { runner = new Thread(this); runner.start(); } } public void stop() { if (runner != null) { runner.stop(); runner = null; } } public void run() { setBackground(Color.blue); while (true) { for (xpos = 5; xpos <= 105; xpos+=4) { if (xpos == 5) ux2 = size().width; else ux2 = xpos + 90; repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { } if (ux1 == 0) ux1 = xpos; } xpos = 5; } } public void update(Graphics g) { g.clipRect(ux1, 5, ux2 - ux1, 95); paint(g); } public void paint(Graphics g) { // Draw background offscreenG.setColor(Color.black); offscreenG.fillRect(0,0,100,100); offscreenG.setColor(Color.white); offscreenG.fillRect(100,0,100,100); // Draw checker offscreenG.setColor(Color.red); offscreenG.fillOval(xpos,5,90,90); g.drawImage(offscreenImg,0,0,this); // reset the drawing area ux1 = ux2 = 0; file:///G|/ebooks/1575211831/ch11.htm (20 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound 67: } 68: 69: public void destroy() { 70: offscreenG.dispose(); 71: } 72: } Summary Three major topics are the focus of today's lesson. First, you learned about using images in your applets-locating them, loading them, and using the drawImage() method to display them, either at their normal size or scaled to different sizes. You also learned how to create animation in Java using images. Second, you learned how to use sounds, which can be included in your applets any time you need them-at specific moments or as background sounds that can be repeated while the applet executes. You learned how to locate, load, and play sounds using both the play() and the getAudioClip() methods. Finally, you learned about double-buffering, a technique that enables you to virtually eliminate flicker in your animation, at some expense of animation efficiency and speed. Using images and graphics contexts, you can create an offscreen buffer to draw to, the result of which is then displayed to the screen at the last possible moment. Q&A Q: A: Q: A: Q: In the Neko program, you put the image loading into the init() method. It seems to me that it might take Java a long time to load all those images, and because init() isn't in the main thread of the applet, there's going to be a distinct pause there. Why not put the image loading at the beginning of the run() method instead? There are sneaky things going on behind the scenes. The getImage() method doesn't actually load the image; in fact, it returns an Image object almost instantaneously, so it isn't taking up a large amount of processing time during initialization. The image data that getImage() points to isn't actually loaded until the image is needed. This way, Java doesn't have to keep enormous images around in memory if the program is going to use only a small piece. Instead, it can just keep a reference to that data and retrieve what it needs later. I compiled and ran the Neko applet. Something weird is going on; the animation starts in the middle and drops frames. It's as if only some of the images have loaded when the applet is run. That's precisely what's going on. Because image loading doesn't actually load the image right away, your applet may be merrily animating blank screens while the images are still being loaded. Depending on how long it takes those images to load, your applet may appear to start in the middle, to drop frames, or to not work at all. There are three possible solutions to this problem. The first is to have the animation loop (that is, start over from the beginning once it stops). Eventually the images will load and the animation will work correctly. The second solution, and not a very good one, is to sleep for a while before starting the animation, to pause while the images load. The third, and best solution, is to use image observers to make sure no part of the animation plays before its images have loaded. You'll learn more about image observers on Day 24. I wrote an applet to do a background sound using the getAudioClip() and loop() methods. The sound works great, but it won't stop. I've tried suspending the current thread and killing the thread together, but the sound goes on. file:///G|/ebooks/1575211831/ch11.htm (21 of 22) [11/06/2000 7:46:19 PM] Day 11 -- More Animation, Images, and Sound A: Q: A: I mentioned this as a small note in the section on sounds; background sounds don't run in the main thread of the applet, so if you stop the thread, the sound keeps going. The solution is easy-in the same method where you stop the thread, also stop the sound, like this: runner.stop() //stop the thread bgsound.stop() //also stop the sound If I use double-buffering, do I still have to clip to a small region of the screen? Because double-buffering eliminates flicker, it seems easier to draw the whole frame every time. Easier, yes, but less efficient. Drawing only part of the screen not only reduces flicker, it often also limits the amount of work your applet has to do in the paint() method. The faster the paint() method works, the faster and smoother your animation will run. Using clip regions and drawing only what is necessary is a good practice to follow in general-not just if you have a problem with flicker. file:///G|/ebooks/1575211831/ch11.htm (22 of 22) [11/06/2000 7:46:19 PM] file:///G|/ebooks/1575211831/f11-1.gif file:///G|/ebooks/1575211831/f11-1.gif [11/06/2000 7:46:20 PM] file:///G|/ebooks/1575211831/f11-2.gif file:///G|/ebooks/1575211831/f11-2.gif [11/06/2000 7:46:20 PM] file:///G|/ebooks/1575211831/f11-3.gif file:///G|/ebooks/1575211831/f11-3.gif [11/06/2000 7:46:21 PM] file:///G|/ebooks/1575211831/f11-4.gif file:///G|/ebooks/1575211831/f11-4.gif [11/06/2000 7:46:21 PM] Day 24 -- Advanced Animation and Media Day 24 Advanced Animation and Media by Michael Morrison CONTENTS q What Is Animation? q Types of Animation r Frame-Based Animation r Cast-Based Animation q Tracking Images q The MediaTracker Class q Implementing Sprite Animation r The Sprite Class r The SpriteVector Class r The Background Classes q Sample Applet: Sharks q Summary q Q&A A lot of people were stirred when the Web first brought full-color images to the Internet. These days, color images are simply to be expected, while a growing interest is being placed on animation, or moving images. If a picture can tell a thousand words, imagine what a bunch of pictures shown very rapidly can tell! Today's lesson focuses on how the effect of animated movement is conveyed in Java using a series of images displayed rapidly. This technique is really nothing new to computers or programming, although it is pretty new to the Web. If you're thinking this description of today's lesson sounds awfully familiar, it's because you've already learned about animation in earlier lessons. The difference is that today's lesson is going to take you much further in learning about what animation is and how to do some really powerful things with it. More specifically, today you'll learn about the following: q Animation theory q The primary types of animation q Transparency, z-order, collision detection, and a few other cool terms you can lay on your friends q Tracking images using the Java media tracker q Implementing your own sprite animation classes Although part of today's lesson is theoretical, you'll finish up the lesson by creating a powerful set of reusable sprite animation classes. Don't worry if you don't know what a sprite is yet-you will soon enough! file:///G|/ebooks/1575211831/ch24.htm (1 of 27) [11/06/2000 7:46:26 PM] Day 24 -- Advanced Animation and Media What Is Animation? Before getting into animation as it relates to Java, it's important to understand the basics of what animation is and how it works. So let's begin by asking the fundamental question: What is animation? Put simply, animation is the illusion of movement. Am I telling you that every animation you've ever seen is really just an illusion? That's exactly right! And probably the most surprising animated illusion is one that captured our attention long before modern computers-the television. When you watch television, you see lots of things moving around, but what you perceive as movement is really just a trick being played on your eyes. New Term Animation is the process of simulating movement. In the case of television, the illusion of movement is created by displaying a rapid succession of images with slight changes in content. The human eye perceives these changes as movement because of its low visual acuity. I'll spare you the biology lesson of why this is so; the point is that our eyes are fairly easy to trick into falling for the illusion of animation. More specifically, the human eye can be tricked into perceiving animated movement with as low as 12 frames of movement per second. Animation speed is measured in frames per second (fps), which is the number of animation frames, or image changes, presented every second. New Term Frames per second (fps) is the number of animation frames, or image changes, presented every second. Although 12fps is technically enough to fool our eyes into seeing animation, animations at speeds this low often end up looking somewhat jerky. Most professional animations therefore use a higher frame rate. Television, for example, uses 30fps. When you go to the movies, you see motion pictures at about 24fps. It's pretty apparent that these frame rates are more than enough to captivate our attention and successfully create the illusion of movement. When programming animation in Java, you typically have the ability to manipulate the frame rate a decent amount. The most obvious limitation on frame rate is the speed at which the computer can generate and display the animation frames. In Java, this is a crucial point because Java applets aren't typically known to be speed demons. However, the recent release of just-in-time Java compilers has helped speed up Java applets, along with alleviating some of the performance concerns associated with animation. Note Currently, both Netscape Navigator 3.0 and Microsoft Internet Explorer 3.0 support just-in-time compilation of Java applets. Types of Animation I know you're probably itching to see some real animation in Java, but there are a few more issues to cover before getting into the details of animation programming. More specifically, it's important for you to understand the primary types of animation used in Java programming. There are actually a lot of different types of animation, all of which are useful in different instances. However, for the purposes of implementing animation in Java, I've broken animation down into two basic types: frame-based animation and cast-based animation. Frame-Based Animation The most simple type of animation is frame-based animation, which is the primary type of animation found on the Web. Frame-based animation involves simulating movement by displaying a sequence of pregenerated, static frame images. A movie is a perfect example of frame-based animation; each frame of the film is a frame of animation, and when the frames are shown in rapid succession, they create the illusion of movement. New Term file:///G|/ebooks/1575211831/ch24.htm (2 of 27) [11/06/2000 7:46:26 PM] Day 24 -- Advanced Animation and Media Frame-based animation simulates movement by displaying a sequence of pregenerated, static frame images. Frame-based animation has no concept of a graphical object distinguishable from the background; everything appearing in a frame is part of that frame as a whole. The result is that each frame image contains all the information necessary for that frame in a static form. This is an important point because it distinguishes frame-based animation from cast-based animation, which you'll learn about next. Note Much of the animation used in Web sites is implemented using animated GIF images, which involves storing multiple animation frames in a single GIF image file. Animated GIFs are a very good example of frame-based animation. Cast-Based Animation A more powerful animation technique often employed in games and educational software is cast-based animation, which is also known as sprite animation. Cast-based animation involves graphical objects that move independently of a background. At this point, you may be a little confused by my usage of the term "graphical object" when referring to parts of an animation. In this case, a graphical object is something that logically can be thought of as a separate entity from the background of an animation image. For example, in an animation of the solar system, the planets would be separate graphical objects that are logically independent of the starry background. New Term Cast-based animation simulates movement using graphical objects that move independently of a background. Each graphical object in a cast-based animation is referred to as a sprite and can have a position that varies over time. In other words, sprites have a velocity associated with them that determines how their position changes over time. Almost every computer game uses sprites to some degree. For example, every object in the classic Asteroids game is a sprite that moves independently of the black background. New Term A sprite is a graphical object that can move independently of a background or other objects. Note You may be wondering where the term cast-based animation comes from. It comes from